对于多线程的解释有很多书籍和文章,但是都不能通俗易懂的让多数人快速理解,主要就是举得例子未能完全贴合实际生活,既然Java是面向对象,它的所有设计和思想都可以从现实生活中找到答案,我也是很不喜欢很多书籍一上来就写的跟天书一样的晦涩难懂,尤其官方文档更不用说了,当然是为了严谨,但是我们在学习java理解的时候,必须要做到知其然并知其所以然,而不是记一个标准答案,或者死记硬背,这样是无法达到知命境界的。
目录
一、成为修行者
从下面开始我们进入一个全新的世界来理解java的多线程
首先你只是一个凡人,但是机缘巧合之下,你被java的一个主线程附体,变成了修行者,达到了初始境界,看着这个世界来了一句,hello world。
public class Me { public static void main(String[] args) { System.out.println("hello world"); } }
二、创建多线程
虽然成了修行者还是得吃饭,可是传统的做饭一个人只能一样样的做,可不可以同时完成这些工作呢,比如一边洗菜,一边炒菜,一边淘米,一边煮饭,而这些如何能同时进行呢?
这时候你翻看java卷天书,从而发现了以下一段秘籍,你创建了多线程,来解决了这个问题
/** * @author xiaomianyang * @description * @date 2020-02-06 12:30 */ public class Me { public static void main(String[] args) throws Exception{ String worker[]={"我是洗菜的","我是炒菜的","我是淘米的","我是煮饭的"}; for(int i=0;i<worker.length;i++){ Work work=new Work(worker[i]); Thread thread=new Thread(work); thread.start(); } } } class Work implements Runnable{ private String out; public Work(String out) { this.out = out; } @Override public void run() { try{ Thread.sleep(1000); System.out.println(this.out); }catch(InterruptedException e){ e.printStackTrace(); } } }
三、线程为什么不按顺序执行
你分身出来四个人都开始帮你干活了,可是发现一个问题,他们没有按照顺序执行自己的工作,无论执行多少次都会发现执行顺序总是在发生变化。
为什么没有按照顺序执行呢,理想中不是谁先start,谁先开始吗?为啥会这样呢?首先从面向对象角度来解释,因为这四个分身都是你分出来的,虽然看上去这四个分身都拥有自己的想法,看上去四个是同时在做事情,实际上他们用的都是你这一个大脑,只是作为修行者,大脑控制频率切换比较快,看上去四个人都是同时在干活,实际上在同一时刻大脑只能控制一个人在工作,属于并发行为,而感官上他们似乎是同时发生的,从计算机角度也是一样的,你的大脑相当于CPU在真正的同一时刻只有一个线程再跑,只是CPU切换比较快,所以感觉好像是并行实则是并发运行,所以他们不能按顺序就是因为CPU在随机切换。
如何解决这个问题呢,既然是你创建的分身你肯定希望它按照自己的执行顺序执行,于是在翻看天书发现找到了join指令,那你就给每个分身说,你干完了他在干活。
join就是阻塞当前线程,直到当前线程执行完后才执行下一个线程。
public class Me { public static void main(String[] args) throws Exception{ String worker[]={"我是洗菜的","我是炒菜的","我是淘米的","我是煮饭的"}; for(int i=0;i<worker.length;i++){ Work work=new Work(worker[i]); Thread thread=new Thread(work); thread.start(); thread.join(); } } }
四、可重入锁可以干什么
的确达到想要的效果了,你分身出来了四个人同时在帮你干活,而你什么都不用做,可是你发现炒菜的没有菜却在炒菜,煮米的没有米却在煮米,因为洗菜的还把菜没有洗好,而淘米的也没有弄好米,这就导致炒菜的和煮米的在干无意义的工作,而且还浪费水浪费火,这不就是浪费资源吗?那么问题来了,怎么让洗完菜通知炒菜的,洗完米的通知煮饭的呢?这就涉及到了线程之间的通信,让线程互相交流而合理完成工作,我们可以利用可重入锁实现这个功能
要利用可重入锁,我们创建四个线程,采用java8的新语法创建,这里我们就让这四个线程协调通信完成工作,那为啥不用join,因为join是阻塞式的,事实上,洗菜和淘米是可以同时进行的,但是用了join,就得等前面的一个个执行完,那么利用可重入锁就可以很好的解决这个问题了
对于锁的释放要写在finally里面,防止死锁
import java.util.concurrent.locks.Condition; import java.util.concurrent.locks.Lock; import java.util.concurrent.locks.ReentrantLock; /** * @author xiaomianyang * @description * @date 2020-02-06 12:30 */ public class Me { private static Lock lock = new ReentrantLock(); private static Lock lock2 = new ReentrantLock(); private static Condition condition1 = lock.newCondition(); private static Condition condition2 = lock2.newCondition(); private static Boolean one=false; private static Boolean three=false; public static void main(String[] args){ new Thread(() -> { //加锁,执行完成后发送条件信号 lock.lock(); System.out.println("我是洗菜的"); one=true; condition1.signal(); lock.unlock(); }).start(); new Thread(() -> { try { lock.lock(); //如果一号没有执行完成就继续等待 否则 执行任务并释放锁 if (!one) { condition1.await(); } System.out.println("我是炒菜的"); }catch (InterruptedException e){ e.printStackTrace(); }finally { lock.unlock(); } }).start(); new Thread(() -> { lock2.lock(); System.out.println("我是淘米的"); three=true; condition2.signal(); lock2.unlock(); }).start(); new Thread(() -> { try { lock2.lock(); if (!three) { condition2.await(); } System.out.println("我是煮饭的"); }catch(InterruptedException e){ e.printStackTrace(); }finally { lock2.unlock(); } }).start(); } }
查看输出结果,的确按照我们想要的方式执行了
五、线程的停止
比如分身中的其中一个在炒菜,炒了五盘菜的时候,我不想让他炒了,那就得把他停止掉,该如何停止掉呢?
可以使用interrupt对当前线程终止,但是这个方法并不会立即终止该线程,所以我们使用Thread.currentThread().isInterrupted()判断是否有中断信号,以此来停止当前线程,这样程序就会break跳出循环,程序的不执行也就是真正意义上的线程终止
new Thread(() -> { try { lock.lock(); //如果一号没有执行完成就继续等待 否则 执行任务并释放锁 if (!one) { condition1.await(); } for(int i=0;i<50;i++) { if(Thread.currentThread().isInterrupted()){ break; } System.out.println("我是炒菜的"+i); Thread.sleep(1000); if(i==5){ Thread.currentThread().interrupt(); } } }catch (InterruptedException e){ e.printStackTrace(); }finally { lock.unlock(); } }).start();
那么再来一个问题,如果我想让煮米的人发现炒菜的人炒了10盘菜后,告诉炒菜的人停止炒菜该怎么做呢?