Java多线系列文章是Java多线程的详解介绍,对多线程还不熟悉的同学可以先去看一下我的这篇博客Java基础系列3:多线程超详细总结,这篇博客从宏观层面介绍了多线程的整体概况,接下来的几篇文章是对多线程的深入剖析。 线程是操作系统中独立的个体,但这些个体如果不经过特殊的处理就不能成为一个整体。线程间的通信就是成为整体的必用方案之一,可以说,使线程间进行通信后,系统之间的交互性会更强大,在大大提高CPU利用率的同时还会使程序员对各线程任务在处理的过程中进行有效的把控与监督。 线程运行状态 1)新创建一个新的线程对象后,再调用它的start()方法,系统会为此线程分配CPU资源,使其处于Runnable(可运行)状态,这是一个准备运行的阶段。如果线程抢占到CPU资源,此线程就处于Running(运行)状态。   2)Runnable状态和Running状态可相互切换,因为有可能线程运行一段时间后,有其他高优先级的线程抢占了CPU资源,这时此线程就从Running状态变成Runnable状态。线程进入Runnable状态大体分为如下5种情况: 调用sleep()方法后经过的时间超过了指定的休眠时间。 线程调用的阻塞I0已经返回,阻塞方法执行完毕。 线程成功地获得了试图同步的监视器。 线程正在等待某个通知,其他线程发出了通知。 处于挂起状态的线程调用了resume恢复方法。 3)Blocked是阻塞的意思,例如遇到了一个IO操作,此时CPU处于空闲状态,可能会转而把CPU时间片分配给其他线程,这时也可以称为“暂停”状态。Blocked状态结束后,进入Runnable状态,等待系统重新分配资源。出现阻塞的情况有以下几种: 线程调用sleep()方法,主动放弃占用的处理器资源。 线程调用了阻塞式IO方法,在该方法返回前,该线程被阻塞。 线程试图获得一个同步监视器,但该同步监视器正被其他线程所持有。 线程等待某个通知。 程序调用了suspend方法将该线程挂起。此方法容易导致死锁,尽量避免使用该方法。 4)run()方法运行结束后进入销毁阶段,整个线程执行完毕。 每个锁对象都有两个队列,一个是就绪队列,一个是阻塞队列。就绪队列存储了将要获得锁的线程,阻塞队列存储了被阻塞的线程。一个线程被唤醒后,才会进入就绪队列,等待CPU的调度;反之,一个线程被wait后,就会进入阻塞队列,等待下一次被唤醒。 等待与通知机制 一、不使用等待通知机制实现线程间通信: 我们先不使用等待通知机制来看下如何实现线程间的通信: 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 import java.util.ArrayList; import java.util.List; class MyList{ private List list=new ArrayList(); public void add() { list.add("小马"); } public int size() { return list.size(); } } class ThreadA extends Thread{ private MyList list; public ThreadA(MyList list) { this.list=list; } @Override public void run() { try { for(int i=0;i<10;i++) { list.add(); System.out.println("添加了"+(i+1)+"个元素"); Thread.sleep(1000); } }catch(Exception e) { e.printStackTrace(); } } } class ThreadB extends Thread{ private MyList list; public ThreadB(MyList list) { this.list=list; } @Override public void run() { while(true) { if(list.size()==5) { System.out.println("==5了,线程b要退出了"); } } } } public class Test { public static void main(String[] args) { MyList list=new MyList(); ThreadA a=new ThreadA(list); a.setName("A"); a.start(); ThreadB b=new ThreadB(list); b.setName("B"); b.start(); } }    运行结果: 1 2 3 4 5 6 7 8 9 10 11 添加了1个元素 添加了2个元素 添加了3个元素 添加了4个元素 添加了5个元素 ==5了,线程b要退出了 添加了6个元素 添加了7个元素 添加了8个元素 添加了9个元素 添加了10个元素    上述代码要实现的是当list的size为5时,B线程进行操作,实现了AB两个线程之间的通信,但是有一个弊端,就是线程B不停的通过while语句轮询机制来检测某一个条件,造成了CPU资源的浪费。 如果轮询的时间间隔很小,更浪费CPU资源;如果轮询的时间间隔很大,有可能会取不到想要得到的数据。所以就需要有一种机制来实现减少CPU的资源浪费,而且还可以实现在多个线程间通信,它就是“wait/notify”机制。 二、wait/notify机制: 1、什么是等待通知机制: 等待/通知机制在生活中比比皆是,比如在就餐时就会实现 1)厨师做完一道菜的时间不确定,所以厨师将菜品放到“菜品传递台”上的时间也不确定。 2)服务员取到菜的时间取决于厨师,所以服务员就有“等待”(wait)的状态。 3)服务员如何能取到菜呢?这又得取决于厨师,厨师将菜放在“菜品传递台”上,其实就相当于一种通知(notify),这时服务员才可以拿到菜并交给就餐者。 4)在这个过程中出现了“等待/通知”机制。 需要说明的是,前面示例中多个线程之间也可以实现通信,原因就是多个线程共同访问同一个变量,但那种通信机制不是“等待/通知”,两个线程完全是主动式地读取一个共享变量,在花费读取时间的基础上,读到的值是不是想要的,并不能完全确定。所以现在迫切需要一种“等待/通知”机制来满足上面的需求。 2、等待通知机制的实现: wait()方法: 方法wait()的作用是使当前执行代码的线程进行等待,wait()方法是Object类的方法,该方法用来将当前线程置入“预执行队列”中,并且在wait所在的代码行处停止执行,直到接到通知或被中断为止。在调用wait()之前,线程必须获得该对象的对象级别锁,即只能在同步方法或同步块中调用wait()方法。在执行wait()方法后,当前线程释放锁。在从wait()返回前,线程与其他线程竞争重新获得锁。如果调用wait()时没有持有适当的锁,则抛出IlegalMonitorStateException,它是RuntimeException的一个子类,因此,不需要try-catch 语句进行捕捉异常。 notify()方法: 方法notify()也要在同步方法或同步块中调用,即在调用前,线程也必须获得该对象的对象级别锁。如果调用notify()时没有持有适当的锁,也会抛出llegalMonitorStateException。该方法用来通知那些可能等待该对象的对象锁的其他线程,如果有多个线程等待,则由线程规划器随机挑选出其中一个呈wait状态的线程,对其发出通知notify,并使它等待获取该对象的对象锁。需要说明的是,在执行notify()方法后,当前线程不会马上释放该对象锁,呈wait状态的线程也并不能马上获取该对象锁,要等到执行notify()方法的线程将程序执行完,也就是退出synchronized代码块后,当前线程才会释放锁,而呈wait状态所在的线程才可以获取该对象锁。当第一个获得了该对象锁的wait线程运行完毕以后,它会释放掉该对象锁,此时如果该对象没有再次使用notify语句,则即便该对象已经空闲,其他wait状态等待的线程由于没有得到该对象的通知,还会继续阻塞在wait状态,直到这个对象发出一个notify 或notifyAll。 总结:wait使线程停止运行,而notify使停止的线程继续运行 我们现在再来用等待通知机制来实现上面的案例,代码如下: 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 80 81 82 import java.util.ArrayList; import java.util.List; class MyList{ private static List list=new ArrayList(); public static void add() { list.add("小马"); } public static int size() { return list.size(); } } class ThreadA extends Thread{ private Object lock; public ThreadA(Object lock) { this.lock=lock; } @Override public void run() { try { synchronized(lock) { if(MyList.size()!=5) { System.out.println("wait begin "+System.currentTimeMillis()); lock.wait(); System.out.println("wait end "+System.currentTimeMillis()); } } }catch(Exception e) { e.printStackTrace(); } } } class ThreadB extends Thread{ private Object lock; public ThreadB(Object lock) { this.lock=lock; } @Override public void run() { try { synchronized(lock) { for(int i=0;i<10;i++) { MyList.add(); if(MyList.size()==5) { lock.notify(); System.out.println("已发出通知"); } System.out.println("添加了"+(i+1)+"个元素"); Thread.sleep(1000); } } }catch(Exception e) { e.printStackTrace(); } } } public class Test { public static void main(String[] args) throws InterruptedException { Object lock=new Object(); ThreadA a=new ThreadA(lock); a.start(); Thread.sleep(1000); ThreadB b=new ThreadB(lock); b.start(); } }    运行结果: 1 2 3 4 5 6 7 8 9 10 11 12 13 wait begin 1575266897805 添加了1个元素 添加了2个元素 添加了3个元素 添加了4个元素 已发出通知 添加了5个元素 添加了6个元素 添加了7个元素 添加了8个元素 添加了9个元素 添加了10个元素 wait end 1575266908861    从结果中我们可以看出,程序一启动时就输出了wait begin,但是并没有立即输出wait end,这是因为调用了wait()方法,使当前线程处于等待状态,暂停执行,当list的size等于5时,调用了notify()方法,释放了等待的线程,wait end 便得以输出读者可能会有疑惑,为什么已经执行了notify方法,但是wait end并没有立即输出,而是在结尾才输出,这是因为notify必须在执行完同步synchronized代码块后才释放锁。 关键字synchronized可以将任何一个Object对象作为同步对象来看待,而Java为每个Object 都实现了wait)和notify0方法,它们必须用在被synchronized同步的Object的临界区内。通过调用wait)方法可以使处于临界区内的线程进入等待状态,同时释放被同步对象的锁。而notify操作可以唤醒一个因调用了wait操作而处于阻塞状态中的线程,使其进入就绪状态。被重新换醒的线程会试图重新获得临界区的控制权,也就是锁,并继续执行临界区内wait之后的代码。如果发出notify操作时没有处于阻塞状态中的线程,那么该命令会被忽略。 wait()方法可以使调用该方法的线程释放共享资源的锁,然后从运行状态退出,进入等待队列,直到被再次唤醒。 notify()方法可以随机唤醒等待队列中等待同一共享资源的“一个”线程,并使该线程退出等待队列,进入可运行状态,也就是notify()方法仅通知“一个”线程。 notifyAll()方法可以使所有正在等待队列中等待同一共享资源的“全部”线程从等待状态退出,进入可运行状态。此时,优先级最高的那个线程最先执行,但也有可能是随机执行,因为这取决于JVM虚拟机的实现。 3、notifyAll()的使用: notify()方法每次只可以唤醒一个线程,notifyAll()方法则可以唤醒所有线程 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 80 81 82 83 84 85 86 87 88 89 90 91 92 //等待线程 class Service{ public void testMethod(Object lock) { try { synchronized(lock) { System.out.println("begin wait() ThreadName="+Thread.currentThread().getName()); lock.wait(); System.out.println("end wait() ThreadName="+Thread.currentThread().getName()); } }catch(Exception e) { e.printStackTrace(); } } } //唤醒线程 class NotifyThread extends Thread{ private Object lock; public NotifyThread(Object lock) { this.lock=lock; } @Override public void run() { synchronized(lock) { lock.notify(); } } } class ThreadA extends Thread{ private Object lock; public ThreadA(Object lock) { this.lock=lock; } @Override public void run() { Service service=new Service(); service.testMethod(lock); } } class ThreadB extends Thread{ private Object lock; public ThreadB(Object lock) { this.lock=lock; } @Override public void run() { Service service=new Service(); service.testMethod(lock); } } class ThreadC extends Thread{ private Object lock; public ThreadC(Object lock) { this.lock=lock; } @Override public void run() { Service service=new Service(); service.testMethod(lock); } } public class Test { public static void main(String[] args) throws InterruptedException { Object lock=new Object(); ThreadA a=new ThreadA(lock); a.start(); ThreadB b=new ThreadB(lock); b.start(); ThreadC c=new ThreadC(lock); c.start(); Thread.sleep(1000); NotifyThread notifyThread=new NotifyThread(lock); notifyThread.start(); } }    运行结果: 1 2 3 4 begin wait() ThreadName=Thread-0 begin wait() ThreadName=Thread-1 begin wait() ThreadName=Thread-2 end wait() ThreadName=Thread-0    在唤醒线程中我们使用了notify()方法,从结果我们可以看出只有一个线程被唤醒了,其他线程依然处于等待状态,这时我们把notify()修改成notifyAll()方法,则运行结果如下: 1 2 3 4 5 6 begin wait() ThreadName=Thread-1 begin wait() ThreadName=Thread-2 begin wait() ThreadName=Thread-0 end wait() ThreadName=Thread-1 end wait() ThreadName=Thread-0 end wait() ThreadName=Thread-2    可以看到所有等待的线程都已经被释放 4、生产者/消费者模式实现 等待/通知模式最经典的案例就是“生产者/消费者”模式。但此模式在使用上有几种“变形”,还有一些小的注意事项,但原理都是基于wait/notify的。 (1)、一个生产者和一个消费者:操作值 共同操作的值: 1 2 3 4 5 public class ValueObject { public static String value=""; }    生产者:生产者生产东西 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 public class Product { private String lock; public Product(String lock) { this.lock=lock; } public void setValue() { try { synchronized (lock) { if(!ValueObject.value.equals("")) { lock.wait(); } String value=System.currentTimeMillis()+"_"+System.nanoTime(); System.out.println("set的值是"+value); ValueObject.value=value; lock.notify(); } }catch(Exception e) { e.printStackTrace(); } } }    消费者:消费者消费东西 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 public class Customer { private String lock; public Customer(String lock) { this.lock=lock; } public void getValue() { try { synchronized(lock) { if(ValueObject.value.equals("")) { lock.wait(); } System.out.println("get的值是"+ValueObject.value); ValueObject.value=""; lock.notify(); } }catch(Exception e) { e.printStackTrace(); } } }    测试代码: 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 class ThreadP extends Thread{ private Product p; public ThreadP(Product p) { this.p=p; } @Override public void run() { while(true) { p.setValue(); } } } class ThreadC extends Thread{ private Customer c; public ThreadC(Customer c) { this.c=c; } @Override public void run() { while(true) { c.getValue(); } } } public class Run { public static void main(String[] args) { String lock=new String(""); Product p=new Product(lock); Customer c=new Customer(lock); ThreadP pThreadP=new ThreadP(p); ThreadC cThreadC=new ThreadC(c); pThreadP.start(); cThreadC.start(); } }    部分运行结果: 1 2 3 4 5 6 7 8 9 10 11 12 13 set的值是1575270909669_589770446584700 get的值是1575270909669_589770446584700 set的值是1575270909669_589770446607900 get的值是1575270909669_589770446607900 set的值是1575270909669_589770446631300 get的值是1575270909669_589770446631300 set的值是1575270909669_589770446654500 get的值是1575270909669_589770446654500 set的值是1575270909669_589770446678200 get的值是1575270909669_589770446678200 set的值是1575270909669_589770446701400 get的值是1575270909669_589770446701400 set的值是1575270909669_589770446724800    此实例生产者生产一个产品,消费者消费一个产品,在代码中就是对ValueObject中的value值进行操作 (2)多生产与多消费:操作值 上一个示例只有一个生产者和一个消费者,但现实生活中通常不会只有一个,下面我们来看一下多生产多消费的情况 共同操作的值: 1 2 3 4 5 public