从这一期开始我会持续更新一个系列即多线程编程我们之前学习JAVA的时候大多都是只有main函数一个线程从这一期开始我将带领大家进入多线程的世界。首先为什么引入多线程之前只有一个线程的时候不是也能运行的好好的吗我用生活中的例子向大家解释。假设是这么一个场景想要去吃掉100只鸡这里安排了一个人相当于是单进程且单线程这时候想完成目标是相当困难的按照我们以往的思路想要减轻负担更高效的完成任务我们要引入另一个人。也就是这种场景现在多来了一个人他们两个人一人桌子上放50只鸡这样任务就被缓解了这属于多进程但每个进程中还是只有一个线程不过我们说这种方式也是不太恰当的。可能在这张图上还看不出来但引入一个房间、一张桌子开销是很大的是远远超过你只引入一个人的开销如果再多一些进程呢要知道进程是要占据资源的每创建一个新的进程对应的就要分配内存资源等资源久而久之在这种大开销下效率会很低所以我们要引入多线程减少开销。正如此图所示房间还是原来房间桌子也是原来桌子在此基础上多加一个人一起吃这100只鸡这就是多线程它的开销是远小于多进程的。这时候你可能会想如果引入更多线程呢效率肯定会进一步提高。比如这种情况100只鸡分给四个人吃效率会进一步提升但是如果再多多到一定程度下反而会适得其反。如果像这种情况或者再多甚至一个人都分不到一只鸡有的人就开始摸鱼了这样效率反而降低所以说效率和线程的数目并不是严格的线性增长关系达到一定阈值后再增加线程个数反而会因为调度消耗过多资源导致性能变慢。我就拿一张桌子四个人的例子来说其实上边是一种理想化的方式有没有可能有两个人都想吃同一只鸡形成了一个竞争关系产生了线程之间的冲突这就是极其重要的线程安全问题在代码编写的层面出现这种情况可能会出bug。影响程序的正常运行。线程安全问题是很严重的理想情况下两个线程产生冲突顶多是他们两个都拒绝工作拖累一下运行效率而已但是如果一个线程在和其他线程产生冲突后直接抛出异常那程序就中断了其余没有产生冲突的线程也停止了工作所以线程安全问题一定是值得重视的。接下来我带大家学习一下在Java编程中怎么引入多线程。package thread.csdn; class MyThread extends Thread { Override public void run() { while (true) { try { Thread.sleep(1000); System.out.println(hello thread); } catch (InterruptedException e) { throw new RuntimeException(e); } } } } public class Demo1 { public static void main(String[] args) throws InterruptedException { Thread tnew MyThread(); t.start(); while (true) { Thread.sleep(1000); System.out.println(hello main); } } }这是我实现的一个简单的多线程代码它的效果是t线程循环打印hello threadmain线程循环打印hello main在每次循环打印前休眠1秒给大家看一下这个代码的执行效果。这只是我截取的一部分执行结果并不是完整的结果可以看到确实是两个线程都在执行。好了效果给大家演示完了接下来我简要分析一下代码的原理。大家应该注意到了在编写多线程代码时用到的一个核心类是Thread类。JAVA对操作系统提供的由C语言编写的原生线程api和不同操作系统的不同线程api进行统一封装到Thread类中供程序员使用。Thread类是在java.lang中的这个包是默认导入的所以我们不需要手动导包也可以运行代码。其次我们看到我自己实现的MyThread类继承了Thread类并重写了run方法这个run方法就是我们自己实现的线程要执行的逻辑你也可以把run方法当成自己创建的线程的入口。在main函数中我编写了t.start()的代码这行语句执行的效果是执行我们重写的run方法实际则是创建新线程并执行我们为这个新线程编写的逻辑。或许你有疑问既然我说了t.start()就是执行run方法那为啥不直接在main中调用run呢非得拐个弯反正start也得调用run我给你演示一下这么敲代码的结果。package thread.csdn; class MyThread extends Thread { Override public void run() { while (true) { try { Thread.sleep(1000); System.out.println(hello thread); } catch (InterruptedException e) { throw new RuntimeException(e); } } } } public class Demo1 { public static void main(String[] args) throws InterruptedException { Thread tnew MyThread(); //t.start(); t.run(); while (true) { Thread.sleep(1000); System.out.println(hello main); } } }看到了吗如果这么写代码只会执行thread线程里的内容main线程的逻辑不会执行或者说main线程的逻辑会等到run执行完了之后才会执行。你有没有觉得很眼熟呀这不就是我们平常敲代码时的效果吗等上一行代码执行完了在执行下一行也就是说这么写达不到多线程编程的目的。因此start的作用并不仅仅是调用run方法还有一个更重要的作用是多了一个执行流可以实现多线程的效果。核心的逻辑已经讲差不多了我接下来给大家简单介绍一下代码中调用的sleep方法吧显然这是属于Thread类的静态方法而且会抛出InterruptedException异常而我们需要针对这个异常进行处理不知道你是否观察到在上面的代码中我调用了两次sleep但是采用的确是不同的处理异常方式在run函数中我是用try-catch处理异常在main方法中我直接在函数名处声明了异常这也是暗藏玄机的首先我可以两个都用try-catch捕获异常但不能两个都声明异常不要忘了run方法不是MyThread独有的它是重写的父类方法在父类中没有声明异常子类重写当然也不能改变。接着来跟大家分析一下执行结果可以看到有时候是main线程先执行有时候又是thread线程先执行这是为什么有什么规律吗这是因为操作系统中线程调度是随机的也就是抢占式执行多线程之间如何调度按照什么顺序执行这完全取决于操作系统和程序员没什么关系你即使是想修改也改不了不过如果你为线程设置优先级理论上倒是可以指定他们的调度顺序但是操作系统也就是参考一下并不一定会严格执行。那么我咋查看多个线程的情况呢这时候需要运行一个第三方工具。在你安装jdk的bin目录下有这样一个exe文件jconsole.exe点击后可以与正在执行的进程建立连接随后点击线程你就能看到正在运行的两个线程了。main线程自不必多说Thread-0是线程的默认命名规则我们并没有指定线程名字所以它就是默认的Thread-n第一个线程就是0第二个就是1以此类推...其他的线程就是jvm自带的线程了当执行代码时这些线程都会自动执行。点击线程后可以观察到具体调用的栈以及执行的位置这就不多说了。回过头来我们接着看Thread类。点击源码后能看到这个类实现了Runnable接口Runnable接口中只有一个抽象方法就是run方法这时候你恍然大悟了吧原来刚刚我们一直重写的run方法不是Thread所独有的而是它实现的接口里边带的。所以这为我们提供了一个多线程编程的新思路既然我们继承的Thread类中最核心的run方法是Runnable接口里边的抽象方法那我们直接写一个新的类并实现Runnable接口不就行了吗反正Thread类调用start方法的时候也要调用run在哪重写还不是一样从逻辑角度上来讲是大致可行的但Thread类毕竟是系统自带类我们怎么把写的接口传给他呢Thread类里边提供了这样的构造方法吗我们接着翻源码。看到了吧Thread提供了这样的构造方法也就是说我们刚刚的方案是完全可行的。接下来我为大家编写一下代码实现的还是相同的逻辑。package thread.csdn; class MyRunnable implements Runnable { Override public void run() { while (true) { try { Thread.sleep(1000); System.out.println(hello Runnable); } catch (InterruptedException e) { throw new RuntimeException(e); } } } } public class Demo2 { public static void main(String[] args) throws InterruptedException { Runnable runnable new MyRunnable(); Thread thread new Thread(runnable); thread.start(); while (true) { Thread.sleep(1000); System.out.println(hello main); } } }显然这种方法也是可行的。从逻辑上分析其实这两种方式是差不多的本质都是Thread类实现的对象调用start方法只是start里边调用的run方法在哪里被重写的区别那为什么引入第二种写法呢假如你要对run方法的内容修改第一种方式你要跑到Thread里边去修改而这个类也同时调用了执行逻辑但是第二种方法你只需要改一下外边使用Runnable接口的类就行了根本不需要动你的Thread类这是不是就达成了解耦的效果呀。会让你的逻辑更加清晰修改起来也更加方便。好的那么第零期多线程编程就先写到这里接下来我会持续更新希望对你有所帮助。