多线程 等待/通知机制的实现
2017-08-19 22:15
387 查看
等待/通知机制的实现
1. 概述
* 方法wait()的作用是使当前执行代码的线程进行等待,wait()方法是Object类的方法,
该方法用来将当前线程置入"预执行队列"中,并且在wait()所在的代码行处停止执行,
直到接到通知或被中断为止。在调用wait()之前,线程必须获得该对象的对象级别锁,
即只能在同步方法或同步块中调用wait()方法。在执行wait()方法后,当前对象释放锁。
* 方法notify()也要在同步方法或者同步块中调用,即在调用前,线程也必须获得该对象的对象
级别锁。该方法用来通知那些可能等待该对象的对象锁的其他线程,如果有多个线程等待,则由
线程规划器随机挑选出其中一个呈wait状态的线程,对其发出通知notify,并使它等待获取该对象
的对象锁。
注意:在执行notify()方法后,当前线程不会马上释放该对象锁,呈wait状态的线程也并不能
马上获取该对象锁,要等到执行notify()方法的线程将程序执行完,也就是退出synchronized
代码块后,当前线程才会释放锁,而呈wait状态所在的线程才可以获取该对象锁。
* 总结
wait使线程停止运行,而notify使停止的线程继续运行。
2. notify()方法后当前线程还是会继续执行完:
eg:
public class MyList {
private static List<String> list=new ArrayList<>();
public static void add(){
list.add("anyString");
}
public static int size(){
return list.size();
}
}
public class ThreadA extends Thread{
private Object lock;
public ThreadA(Object lock){
super();
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 (InterruptedException e){
e.printStackTrace();
}
}
}
public class ThreadB extends Thread{
private Object lock;
public ThreadB(Object lock){
super();
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 (InterruptedException e){
e.printStackTrace();
}
}
}
public class Run {
public static void main(String[] args){
try{
Object lock=new Object();
ThreadA a=new ThreadA(lock);
a.start();
Thread.sleep(50);
ThreadB b=new ThreadB(lock);
b.start();
}catch (InterruptedException e){
e.printStackTrace();
}
}
}
结果:
wait begin 1503146432420
添加了1个元素!
添加了2个元素!
添加了3个元素!
添加了4个元素!
已发出通知!
添加了5个元素!
添加了6个元素!
添加了7个元素!
添加了8个元素!
添加了9个元素!
添加了10个元素!
wait end 1503146442540
3. 方法wait()锁释放与notify()锁不释放
wait()方法执行到时,该线程就会释放锁,notify()方法执行时,线程不会释放锁,并且代码会执行完毕,执行完毕后才释放锁。
4. 当interrupt方法遇到wait方法
当线程wait()状态时,调用线程对象的interrupt()方法会出现InterruptedException异常。
eg:
public class Service {
public void testMethod(Object lock){
try{
synchronized (lock){
System.out.println("begin wait()");
lock.wait();
System.out.println(" end wait()");
}
}catch (InterruptedException e){
e.printStackTrace();
System.out.println("出现异常了,因为呈wait状态的线程被interrupt了!");
}
}
}
public class ThreadA extends Thread {
private Object lock;
public ThreadA(Object lock){
super();
this.lock=lock;
}
@Override
public void run() {
Service service=new Service();
service.testMethod(lock);
}
}
public class Test {
public static void main(String[] args){
try{
Object lock=new Object();
ThreadA a=new ThreadA(lock);
a.start();
Thread.sleep(5000);
a.interrupt();
}catch (InterruptedException e){
e.printStackTrace();
}
}
}
效果:
begin wait()
出现异常了,因为呈wait状态的线程被interrupt了!
java.lang.InterruptedException
at java.lang.Object.wait(Native Method)
at java.lang.Object.wait(Object.java:502)
at org.fkit.six.Service.testMethod(Service.java:11)
at org.fkit.six.ThreadA.run(ThreadA.java:16)
* 从上可以看出:
* 在执行同步代码块的过程中,遇到异常而导致线程终止,锁也会被释放。
* 在执行同步代码块的过程中,执行了锁所属对象的wait()方法,这个线程
会释放对象锁,而此线程对象会进入线程等待池中,等待被唤醒。
5. 只通知一个线程
调用方法notify()一次只随机通知一个线程进行唤醒
6. 唤醒所有线程
使用notifyAll()方法可以唤醒所有该锁上的线程
7. 方法wait(long)的使用
带一个参数的wait(long)方法的功能是等待某一时间内是否有线程对锁进行唤醒,
如果超过这个时间则自动唤醒。
8. 等待wait的条件发生变化(难点哦)
* 在使用wait/notify模式时,还需要注意另外一种情况,也就是wait等待的条件发生了变化,
也容易造成程序逻辑的混乱。
eg:
public class ValueObject {
public static List<String> list=new ArrayList<>();
}
public class Add {
private String lock;
public Add(String lock){
super();
this.lock=lock;
}
public void add(){
synchronized (lock){
ValueObject.list.add("anyString");
lock.notifyAll();
}
}
}
public class Subtract {
private String lock;
public Subtract(String lock){
d758
super();
this.lock=lock;
}
public void subtract(){
try{
synchronized (lock){
if(ValueObject.list.size()==0){
System.out.println("wait begin ThreadName="+Thread.currentThread().getName());
lock.wait();
System.out.println("wait end ThreadName="+Thread.currentThread().getName());
}
ValueObject.list.remove(0);
System.out.println("list size="+ValueObject.list.size());
}
}catch (InterruptedException e){
e.printStackTrace();
}
}
}
public class ThreadSubtract extends Thread{
private Subtract r;
public ThreadSubtract(Subtract r){
super();
this.r=r;
}
@Override
public void run() {
r.subtract();
}
}
public class ThreadAdd extends Thread {
private Add p;
public ThreadAdd(Add p){
super();
this.p=p;
}
@Override
public void run() {
p.add();
}
}
public class Run {
public static void main(String[] args) throws InterruptedException{
String lock=new String("");
Add add=new Add(lock);
Subtract subtract=new Subtract(lock);
ThreadSubtract subtract1Thread=new ThreadSubtract(subtract);
subtract1Thread.setName("subtract1Thread");
subtract1Thread.start();
ThreadSubtract subtract2Thread=new ThreadSubtract(subtract);
subtract2Thread.setName("subtract2Thread");
subtract2Thread.start();
Thread.sleep(1000);
ThreadAdd addThread=new ThreadAdd(add);
addThread.setName("addThread");
addThread.start();
}
}
效果:
wait begin ThreadName=subtract1Thread
wait begin ThreadName=subtract2Thread
wait end ThreadName=subtract2Thread
Exception in thread "subtract1Thread" java.lang.IndexOutOfBoundsException: Index: 0, Size: 0
list size=0
at java.util.ArrayList.rangeCheck(ArrayList.java:653)
wait end ThreadName=subtract1Thread
at java.util.ArrayList.remove(ArrayList.java:492)
at org.fkit.seven.Subtract.subtract(Subtract.java:20)
at org.fkit.seven.ThreadSubtract.run(ThreadSubtract.java:15)
分析:因为我们有两个线程在等待删除,而插入只执行了一次,却唤醒了两个线程,去删除的时候当然会出现错误,
做如下修改就可以解决问题
public class Subtract {
private String lock;
public Subtract(String lock){
super();
this.lock=lock;
}
public void subtract(){
try{
synchronized (lock){
while(ValueObject.list.size()==0){
System.out.println("wait begin ThreadName="+Thread.currentThread().getName());
lock.wait();
System.out.println("wait end ThreadName="+Thread.currentThread().getName());
}
ValueObject.list.remove(0);
System.out.println("list size="+ValueObject.list.size());
}
}catch (InterruptedException e){
e.printStackTrace();
}
}
}
分析:现在来考虑这个问题,当我们线程被唤醒成功的时候,不是立即去执行删除操作,而是回头再去判断一次,因为线程
被同步了,顺序执行,当第二个线程执行到的时候就已经为0了,重新进入等待,而不会去执行删除,就避免了这种错误。
1. 概述
* 方法wait()的作用是使当前执行代码的线程进行等待,wait()方法是Object类的方法,
该方法用来将当前线程置入"预执行队列"中,并且在wait()所在的代码行处停止执行,
直到接到通知或被中断为止。在调用wait()之前,线程必须获得该对象的对象级别锁,
即只能在同步方法或同步块中调用wait()方法。在执行wait()方法后,当前对象释放锁。
* 方法notify()也要在同步方法或者同步块中调用,即在调用前,线程也必须获得该对象的对象
级别锁。该方法用来通知那些可能等待该对象的对象锁的其他线程,如果有多个线程等待,则由
线程规划器随机挑选出其中一个呈wait状态的线程,对其发出通知notify,并使它等待获取该对象
的对象锁。
注意:在执行notify()方法后,当前线程不会马上释放该对象锁,呈wait状态的线程也并不能
马上获取该对象锁,要等到执行notify()方法的线程将程序执行完,也就是退出synchronized
代码块后,当前线程才会释放锁,而呈wait状态所在的线程才可以获取该对象锁。
* 总结
wait使线程停止运行,而notify使停止的线程继续运行。
2. notify()方法后当前线程还是会继续执行完:
eg:
public class MyList {
private static List<String> list=new ArrayList<>();
public static void add(){
list.add("anyString");
}
public static int size(){
return list.size();
}
}
public class ThreadA extends Thread{
private Object lock;
public ThreadA(Object lock){
super();
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 (InterruptedException e){
e.printStackTrace();
}
}
}
public class ThreadB extends Thread{
private Object lock;
public ThreadB(Object lock){
super();
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 (InterruptedException e){
e.printStackTrace();
}
}
}
public class Run {
public static void main(String[] args){
try{
Object lock=new Object();
ThreadA a=new ThreadA(lock);
a.start();
Thread.sleep(50);
ThreadB b=new ThreadB(lock);
b.start();
}catch (InterruptedException e){
e.printStackTrace();
}
}
}
结果:
wait begin 1503146432420
添加了1个元素!
添加了2个元素!
添加了3个元素!
添加了4个元素!
已发出通知!
添加了5个元素!
添加了6个元素!
添加了7个元素!
添加了8个元素!
添加了9个元素!
添加了10个元素!
wait end 1503146442540
3. 方法wait()锁释放与notify()锁不释放
wait()方法执行到时,该线程就会释放锁,notify()方法执行时,线程不会释放锁,并且代码会执行完毕,执行完毕后才释放锁。
4. 当interrupt方法遇到wait方法
当线程wait()状态时,调用线程对象的interrupt()方法会出现InterruptedException异常。
eg:
public class Service {
public void testMethod(Object lock){
try{
synchronized (lock){
System.out.println("begin wait()");
lock.wait();
System.out.println(" end wait()");
}
}catch (InterruptedException e){
e.printStackTrace();
System.out.println("出现异常了,因为呈wait状态的线程被interrupt了!");
}
}
}
public class ThreadA extends Thread {
private Object lock;
public ThreadA(Object lock){
super();
this.lock=lock;
}
@Override
public void run() {
Service service=new Service();
service.testMethod(lock);
}
}
public class Test {
public static void main(String[] args){
try{
Object lock=new Object();
ThreadA a=new ThreadA(lock);
a.start();
Thread.sleep(5000);
a.interrupt();
}catch (InterruptedException e){
e.printStackTrace();
}
}
}
效果:
begin wait()
出现异常了,因为呈wait状态的线程被interrupt了!
java.lang.InterruptedException
at java.lang.Object.wait(Native Method)
at java.lang.Object.wait(Object.java:502)
at org.fkit.six.Service.testMethod(Service.java:11)
at org.fkit.six.ThreadA.run(ThreadA.java:16)
* 从上可以看出:
* 在执行同步代码块的过程中,遇到异常而导致线程终止,锁也会被释放。
* 在执行同步代码块的过程中,执行了锁所属对象的wait()方法,这个线程
会释放对象锁,而此线程对象会进入线程等待池中,等待被唤醒。
5. 只通知一个线程
调用方法notify()一次只随机通知一个线程进行唤醒
6. 唤醒所有线程
使用notifyAll()方法可以唤醒所有该锁上的线程
7. 方法wait(long)的使用
带一个参数的wait(long)方法的功能是等待某一时间内是否有线程对锁进行唤醒,
如果超过这个时间则自动唤醒。
8. 等待wait的条件发生变化(难点哦)
* 在使用wait/notify模式时,还需要注意另外一种情况,也就是wait等待的条件发生了变化,
也容易造成程序逻辑的混乱。
eg:
public class ValueObject {
public static List<String> list=new ArrayList<>();
}
public class Add {
private String lock;
public Add(String lock){
super();
this.lock=lock;
}
public void add(){
synchronized (lock){
ValueObject.list.add("anyString");
lock.notifyAll();
}
}
}
public class Subtract {
private String lock;
public Subtract(String lock){
d758
super();
this.lock=lock;
}
public void subtract(){
try{
synchronized (lock){
if(ValueObject.list.size()==0){
System.out.println("wait begin ThreadName="+Thread.currentThread().getName());
lock.wait();
System.out.println("wait end ThreadName="+Thread.currentThread().getName());
}
ValueObject.list.remove(0);
System.out.println("list size="+ValueObject.list.size());
}
}catch (InterruptedException e){
e.printStackTrace();
}
}
}
public class ThreadSubtract extends Thread{
private Subtract r;
public ThreadSubtract(Subtract r){
super();
this.r=r;
}
@Override
public void run() {
r.subtract();
}
}
public class ThreadAdd extends Thread {
private Add p;
public ThreadAdd(Add p){
super();
this.p=p;
}
@Override
public void run() {
p.add();
}
}
public class Run {
public static void main(String[] args) throws InterruptedException{
String lock=new String("");
Add add=new Add(lock);
Subtract subtract=new Subtract(lock);
ThreadSubtract subtract1Thread=new ThreadSubtract(subtract);
subtract1Thread.setName("subtract1Thread");
subtract1Thread.start();
ThreadSubtract subtract2Thread=new ThreadSubtract(subtract);
subtract2Thread.setName("subtract2Thread");
subtract2Thread.start();
Thread.sleep(1000);
ThreadAdd addThread=new ThreadAdd(add);
addThread.setName("addThread");
addThread.start();
}
}
效果:
wait begin ThreadName=subtract1Thread
wait begin ThreadName=subtract2Thread
wait end ThreadName=subtract2Thread
Exception in thread "subtract1Thread" java.lang.IndexOutOfBoundsException: Index: 0, Size: 0
list size=0
at java.util.ArrayList.rangeCheck(ArrayList.java:653)
wait end ThreadName=subtract1Thread
at java.util.ArrayList.remove(ArrayList.java:492)
at org.fkit.seven.Subtract.subtract(Subtract.java:20)
at org.fkit.seven.ThreadSubtract.run(ThreadSubtract.java:15)
分析:因为我们有两个线程在等待删除,而插入只执行了一次,却唤醒了两个线程,去删除的时候当然会出现错误,
做如下修改就可以解决问题
public class Subtract {
private String lock;
public Subtract(String lock){
super();
this.lock=lock;
}
public void subtract(){
try{
synchronized (lock){
while(ValueObject.list.size()==0){
System.out.println("wait begin ThreadName="+Thread.currentThread().getName());
lock.wait();
System.out.println("wait end ThreadName="+Thread.currentThread().getName());
}
ValueObject.list.remove(0);
System.out.println("list size="+ValueObject.list.size());
}
}catch (InterruptedException e){
e.printStackTrace();
}
}
}
分析:现在来考虑这个问题,当我们线程被唤醒成功的时候,不是立即去执行删除操作,而是回头再去判断一次,因为线程
被同步了,顺序执行,当第二个线程执行到的时候就已经为0了,重新进入等待,而不会去执行删除,就避免了这种错误。
相关文章推荐
- 多线程中的线程间通信及等待/通知机制的两种实现方式
- java多线程的等待和通知机制,两种实现方法
- 多线程_生产者消费者之等待唤醒机制代码实现
- 不使用等待通知机制 实现线程间通信的 疑问分析
- 多线程中使用Condition实现等待/通知
- java多线程系列(三)---等待通知机制
- 多线程的等待/通知机制
- java多线程之线程间通信:等待/通知机制
- Condition实现生产者消费者模式(等待/通知机制)
- Condition实现生产者消费者模式(等待/通知机制)
- Condition实现生产者消费者模式(等待/通知机制)
- Java多线程之线程间通信--等待(wait)/通知(notify)机制,等待/通知之交叉备份实例
- java多线程系列(三)---等待通知机制
- 线程通信(1) - wait与notify,多线程中的等待与通知机制
- Condition实现生产者消费者模式(等待/通知机制)
- 【多线程】线程通信之等待/通知机制
- 生产者和消费者问题【java等待通知机制实现】
- 二 Java利用等待/通知机制实现一个线程池
- java多线程的等待和通知机制
- 多线程 等待/通知机制