回首翻了翻之前的文章,当时的思路和场景其实还是历历在目(os其实个人觉得自己记忆力还是不错的。。),废话不多说了,没有后续 也没有争取,有的就是坚持和执行力,过去了快6年 我发现自己还是能折腾的。
线程间的通信之 等待/通知机制
前提是多 synchronized 同步锁有一定的了解。
未借助同步锁
在多线程中,如若未借助同步锁的话。我们可以通过多个线程对同一个对象的状态进行处理,
代码示例:
/** * Service 为监控示例对象 */ public class Service { private List<Integer> list = new ArrayList<>(); public void add(){ list.add(1); } public int getSize(){ return list.size(); } }
package com.kirago.cp03.demo01; public class ThreadA extends Thread{ private Service service; public ThreadA(Service service){ super(); this.service = service; } @Override public void run(){ try { for(int i= 0;i<10;i++){ service.add(); System.out.println("list 添加了 " + (i+1) + " 个 元素"); Thread.sleep(1000); } }catch (InterruptedException e){ e.printStackTrace(); } } }
package com.kirago.cp03.demo01; public class ThreadB extends Thread{ private Service service; public ThreadB(Service service){ super(); this.service = service; } @Override public void run(){ try { while (true){ if(service.getSize() >= 5){ System.out.println(">= 5 了,线程 " + Thread.currentThread().getName() + " 要退出了" ); throw new InterruptedException(); } } }catch (InterruptedException e){ e.printStackTrace(); } } }
测试代码如下:
public class ServiceTest { @Test public void run(){ try { Service service = new Service(); ThreadA threadA = new ThreadA(service); threadA.setName("A"); threadA.start(); ThreadB threadB = new ThreadB(service); threadB.setName("B"); threadB.start(); Thread.sleep(10000); }catch (InterruptedException e) { e.printStackTrace(); } } }
线程B通过while 循环一直轮训“监控”对象的属性状态。
通过如此的处理有个很严重的问题:
我们会发现线程B的while循环一直去取 service 实例的属性来做业务逻辑的转移判断,如果 jvm 是 client 模式,那么会频繁的会从公共堆栈区域进行读取,其实这是严重的性能消耗。其实在 java 的体系中优秀的大佬们通过 wait/notify 机制来完美解决此问题。
wait/notify 机制
需要记住的一点事,wait/notify 的调用一定是在同步代码块执行。
- 方法wait()的作用是使当前执行代码的线程进行等待,wait()方法是Object类的方法,该方法用来将当前线程置入“预执行队列”中,并且在wait()所在的代码行处停止执行,直到接到通知或被中断为止。在调用wait()之前,线程必须获得该对象的对象级别锁,即只能在同步方法或同步块中调用wait()方法。在执行wait()方法后,当前线程释放锁。在从wait()返回前,线程与其他线程竞争重新获得锁。如果调用wait()时没有持有适当的锁,则抛出IllegalMonitorStateException,它是RuntimeException的一个子类,因此,不需要try-catch语句进行捕捉异常。
- 方法notify()也要在同步方法或同步块中调用,即在调用前,线程也必须获得该对象的对象级别锁。如果调用notify()时没有持有适当的锁,也会抛出IllegalMonitorStateException。该方法用来通知那些可能等待该对象的对象锁的其他线程,如果有多个线程等待,则由线程规划器随机挑选出其中一个呈wait状态的线程,对其发出通知notify,并使它等待获取该对象的对象锁。需要说明的是,在执行notify()方法后,当前线程不会马上释放该对象锁,呈wait状态的线程也并不能马上获取该对象锁,要等到执行notify()方法的线程将程序执行完,也就是退出synchronized代码块后,当前线程才会释放锁,而呈wait状态所在的线程才可以获取该对象锁。当第一个获得了该对象锁的wait线程运行完毕以后,它会释放掉该对象锁,此时如果该对象没有再次使用notify语句,则即便该对象已经空闲,其他wait状态等待的线程由于没有得到该对象的通知,还会继续阻塞在wait状态,直到这个对象发出一个notify或notifyAll。
用一句话来总结一下wait和notify:wait使线程停止运行,而notify使停止的线程继续运行。
代码示例
package com.kirago.cp03.demo04; import java.util.ArrayList; import java.util.List; public class MyList { private List list = new ArrayList(); public void add(){ list.add("item"); } public int size(){ return list.size(); } }
package com.kirago.cp03.demo04; public class ThreadA extends Thread{ private MyList myList; public ThreadA(MyList myList){ super(); this.myList = myList; } @Override public void run(){ try { synchronized (myList){ if(myList.size() != 5){ System.out.println(" wait begin " + System.currentTimeMillis()); myList.wait(); System.out.println(" wait end " + System.currentTimeMillis()); } } }catch (InterruptedException e){ e.printStackTrace(); } } }
package com.kirago.cp03.demo04; public class ThreadB extends Thread{ private MyList myList; public ThreadB(MyList myList){ super(); this.myList = myList; } @Override public void run(){ try { synchronized (myList){ for(int i=0;i<10;i++){ myList.add(); if(myList.size() == 5){ myList.notify(); System.out.println(" 已经发出通知!"); } System.out.println("添加了 " + myList.size() + " 个元素"); Thread.sleep(1000); } } }catch (InterruptedException e){ e.printStackTrace(); } } }
测试代码:
package com.kirago.cp03.demo04; import org.junit.Test; public class MyListTest { @Test public void run(){ try { MyList myList = new MyList(); ThreadA threadA = new ThreadA(myList); threadA.start(); ThreadB threadB = new ThreadB(myList); threadB.start(); Thread.sleep(14000); }catch (InterruptedException e){ e.printStackTrace(); } } }
关键字synchronized可以将任何一个Object对象作为同步对象来看待,而Java为每个Object都实现了wait()和notify()方法,它们必须用在被synchronized同步的Object的临界区内。通过调用wait()方法可以使处于临界区内的线程进入等待状态,同时释放被同步对象的锁。而notify操作可以唤醒一个因调用了wait操作而处于阻塞状态中的线程,使其进入就绪状态。被重新换醒的线程会试图重新获得临界区的控制权,也就是锁,并继续执行临界区内wait之后的代码。如果发出notify操作时没有处于阻塞状态中的线程,那么该命令会被忽略。wait()方法可以使调用该方法的线程释放共享资源的锁,然后从运行状态退出,进入等待队列,直到被再次唤醒。