等待/通知机制

一、等待/通知机制

1.1 wait/notify机制的由来

如果某一线程通过while轮询机制来检测某一条件,轮询时间间隔很小,会更浪费CPU资源;如果轮询时间间隔很大,可能会取不到想要的数据,所以就需要一种机制来减少CPU的资源浪费,而且还可以在实现多个线程之间的通信,这就是wait/notify机制的由来。

1.2 什么是等待/通知机制

比如生产者和消费者模型,消费者等待生产者生产资源,这是等待,生产者生产好资源通知等待的消费者去消费,这是通知。

1.3 等待/通知机制的实现

  • wait()方法

当前执行代码的线程进行等待;将当前线程置入"预执行队列中";并且wait所在的代码行出停止执行,直到接到通知或者被中断为止。在调用wait()之前,线程必须获得该对象的对象级别锁,即只能在同步方法或者同步代码块中调用该方法。在执行wait()方法后,当前线程释放锁,在从wait()返回前,当前线程与其他线程正常去抢占锁,如果调用wait()时没有适当的锁,则会抛出IllegalMonitorStateException,他是RuntimeException的一个子类,不需要try/catch。

  • notify()方法

在同步方法或者同步代码块中调用,调用notify()方法时,如果没有持锁,会抛出IllegalMonitorStateException,该方法用来通知那些正在等待的线程,如果有多个线程在等待,则线程规划器会随便挑出一个wait状态的线程,对其发出通知notify,执行notify后,当前线程不会立马释放锁,而是等待执行notify()方法的线程将程序执行完,才会释放锁,wait的线程才能获得锁,没有得到锁的线程会继续阻塞在wait状态。

  • notifyAll()方法

是所有处于wait状态的线程都别唤起,进入可运行状态。此时,优先级最高的线程最先执行,但也可能是随机执行,这取决于JVM的实现。

1.4 线程之间状态的切换

  1. 新建线程对象,调用它的start()方法,系统会为此分配cpu,使其处于Runnnable(可运行)状态,这是一个准备运行的阶段,如果线程抢到了cpu资源,此线程就处于运行状态。
  2. Runnable状态和Running状态可相互切换,因为线程运行一段时间后,如果有优先级更高的线程抢占了cpu,线程就会从Running状态转变为Runnable状态。
  3. 线程进入Runnable状态大致分为以下几种状态:
    • 调用sleep()方法后经过的时间大于指定的休眠的时间。
    • 线程调用的阻塞IO已返回,阻塞方法执行完毕。
    • 线程成功的获得了试图同步的监视器
    • 线程正在等待某个通知,其它线程发起了通知
    • 处于挂起的线程调用了resume恢复方法
  4. 出现阻塞状态的情况
    • 线程调用sleep方法,主动放弃占用的处理器资源
    • 线程调用了阻塞式IO方法,在该方法返回前,该线程被阻塞
    • 线程试图获得一个同步监视器,但是该同步监视器被其他线程所持有
    • 线程等待某个通知
    • 程序调用了suspend方法将该线程挂起,此方法容易导致死锁,尽量避免使用该方法。
  5. run()方法运行完毕后线程进入销毁阶段,整个线程执行完毕

1.5 锁对象拥有的队列

就绪队列:就绪队列存储了将要获得锁的线程,一个线程被唤醒后,才能进入就绪队列,等待cpu的调度。wait()方法执行后,自动释放锁。

阻塞队列:存储了被阻塞的线程。一个线程被wait后,进入阻塞队列。notify()方法执行后,不自动释放锁。

1.6注意问题

notify()方法过早执行,则会先执行notify()方法所在的同步代码块中的内容,wait线程就无法被唤醒。
如果资源到达临界值,还有线程准备操作资源,就会出错。

二、生产者与消费者模型

2.1 一生产者与一消费者

生产者:

package wait;

public class produce extends Thread {
	Object lock;

	public produce(Object lock) {
		this.lock = lock;
	}

	public void produceResource() throws InterruptedException {
		synchronized (lock) {
			while(true){
			if (resource.flag == true) {
				System.out.println("生产者有资源,等待消费者消费");
				lock.wait();
			}
			System.out.println(Thread.currentThread().getName() + "没资源,准备生产资源");
			resource.flag = true;
			lock.notify();// 通知消费者消费
		}
	}
}

	@Override
	public void run() {
		try {
			produceResource();
		} catch (InterruptedException e) {
			// TODO Auto-generated catch block
			e.printStackTrace();
		}
	}
}

消费者:

package wait;

public class cost extends Thread {
	Object lock;

	public cost(Object lock) {
		this.lock = lock;
	}

	public void costResource() throws InterruptedException {
		synchronized (lock) {
			while(true){
			if (resource.flag == false) {
				System.out.println("消费者没有资源消费,等待生产者生产");
				lock.wait();
			}
			System.out.println(Thread.currentThread().getName() + "有资源了,消费者开始消费");
			resource.flag = false;
			lock.notify();// 通知生产者生产
		}
	}
}

	@Override
	public void run() {
		try {
			costResource();
		} catch (InterruptedException e) {
			// TODO Auto-generated catch block
			e.printStackTrace();
		}
	}
}

资源:

public class resource {
  public static boolean flag = false;
}

测试代码:

package wait;

public class test {
	public static void main(String[] args) {
		Object lock = new Object();
		cost produce = new cost(lock);
		produce cost = new produce(lock);
		produce.setName("消费者");
		cost.setName("生产者");
		produce.start();
		cost.start();
	}
}

结果:

消费者没有资源消费,等待生产者生产
生产者没资源,准备生产资源
生产者有资源,等待消费者消费
消费者有资源了,消费者开始消费
消费者没有资源消费,等待生产者生产
生产者没资源,准备生产资源
生产者有资源,等待消费者消费
消费者有资源了,消费者开始消费
......

可以看到一个生产者与一个消费者的模拟中,生产者生产一次,消费者消费一次,有序的进行着。

2.2 生产者与消费者:操作栈

import java.util.ArrayList;
import java.util.List;

public class Stack_Thread {
    private List list = new ArrayList();

    synchronized public void push() throws InterruptedException {
        while (true) {
            if (list.size() == 1) {
                //System.out.println("有资源,等待消费者消费");
                this.wait();
            }
            //没资源生产
            String resource = "res";
            list.add(resource);
            System.out.println("push = " + list.size());
            //System.out.println(Thread.currentThread().getName() + "生产好资源,通知消费者消费" + list.size());
            this.notify();
        }

    }

    synchronized public void pop() throws InterruptedException {
        while (true) {
            //有资源消费
            if (list.size() == 0) {
                //System.out.println("没资源,等待生产者生产");
                this.wait();
            }
            list.remove(0);
            System.out.println("pop = " + list.size());
            //System.out.println(Thread.currentThread().getName() + "消费者消费结束,等待生产者生产"+ list.size());
            this.notify();

        }
    }
}

生产者:

public class produce extends Thread{
   Stack_Thread thread = new Stack_Thread();
   public produce(Stack_Thread thread){
       this.thread = thread;
   }

    @Override
    public void run() {
        try {
            thread.push();
        } catch (InterruptedException e) {
            e.printStackTrace();
        }
    }
}

消费者:

public class cost extends  Thread {
    Stack_Thread thread = new Stack_Thread();
    public cost(Stack_Thread thread){
        this.thread = thread;
    }

    @Override
    public void run() {
        try {
            thread.pop();
        } catch (InterruptedException e) {
            e.printStackTrace();
        }
    }
}

运行结果:

pop = 0
push = 1
pop = 0
push = 1
pop = 0
push = 1
pop = 0
push = 1
pop = 0
push = 1

说明:一个wait()方法对应一个notify()方法,wait()方法在前,notify()方法在后。

2.4 一个生产者与多个消费者

测试程序:

public class test {
    public static void main(String[] args) {
        Stack_Thread a = new Stack_Thread();
        cost cost1 = new cost(a);
        cost cost2 = new cost(a);
        cost cost3 = new cost(a);
        produce produce = new produce(a);
        cost1.start();
        cost2.start();
        cost3.start();
        produce.start();
    }
}

结果:

push = 1
pop = 0
Exception in thread "Thread-0" java.lang.IndexOutOfBoundsException: Index: 0, Size: 0
	at java.util.ArrayList.rangeCheck(ArrayList.java:657)
	at java.util.ArrayList.remove(ArrayList.java:496)
	at Stack_Thread.pop(Stack_Thread.java:30)
	at cost.run(cost.java:10)

原因:

  if (list.size() == 1) {
                //System.out.println("有资源,等待消费者消费");
                this.wait();
            }

用if条件作为语句判断,当条件发生时,即size为1时,不能够得到及时的响应,多执行了notify()方法,所以多个呈wait状态的线程被唤醒,继而执行list.remove(0)代码而出现异常。把if语句改成while判断,

解决方法:
if换成while条件:

  iwhile(list.size() == 1) {
                //System.out.println("有资源,等待消费者消费");
                this.wait();
            }

结果:

push = 1
pop = 0

程序出现假死的情况,出现假死的原因是线程唤醒的是同类的wait线程,解决方法是使用notifyAll()方法,唤醒异类。

"C:\Program Files\Java\jdk1.8.0_181\bin\java.exe" "-javaagent:C:\Program Files\JetBrains\IntelliJ IDEA 2018.2.4\lib\idea_rt.jar=52549:C:\Program Files\JetBrains\IntelliJ IDEA 2018.2.4\bin" -Dfile.encoding=UTF-8 -classpath "C:\Program Files\Java\jdk1.8.0_181\jre\lib\charsets.jar;C:\Program Files\Java\jdk1.8.0_181\jre\lib\deploy.jar;C:\Program Files\Java\jdk1.8.0_181\jre\lib\ext\access-bridge-64.jar;C:\Program Files\Java\jdk1.8.0_181\jre\lib\ext\cldrdata.jar;C:\Program Files\Java\jdk1.8.0_181\jre\lib\ext\dnsns.jar;C:\Program Files\Java\jdk1.8.0_181\jre\lib\ext\jaccess.jar;C:\Program Files\Java\jdk1.8.0_181\jre\lib\ext\jfxrt.jar;C:\Program Files\Java\jdk1.8.0_181\jre\lib\ext\localedata.jar;C:\Program Files\Java\jdk1.8.0_181\jre\lib\ext\nashorn.jar;C:\Program Files\Java\jdk1.8.0_181\jre\lib\ext\sunec.jar;C:\Program Files\Java\jdk1.8.0_181\jre\lib\ext\sunjce_provider.jar;C:\Program Files\Java\jdk1.8.0_181\jre\lib\ext\sunmscapi.jar;C:\Program Files\Java\jdk1.8.0_181\jre\lib\ext\sunpkcs11.jar;C:\Program Files\Java\jdk1.8.0_181\jre\lib\ext\zipfs.jar;C:\Program Files\Java\jdk1.8.0_181\jre\lib\javaws.jar;C:\Program Files\Java\jdk1.8.0_181\jre\lib\jce.jar;C:\Program Files\Java\jdk1.8.0_181\jre\lib\jfr.jar;C:\Program Files\Java\jdk1.8.0_181\jre\lib\jfxswt.jar;C:\Program Files\Java\jdk1.8.0_181\jre\lib\jsse.jar;C:\Program Files\Java\jdk1.8.0_181\jre\lib\management-agent.jar;C:\Program Files\Java\jdk1.8.0_181\jre\lib\plugin.jar;C:\Program Files\Java\jdk1.8.0_181\jre\lib\resources.jar;C:\Program Files\Java\jdk1.8.0_181\jre\lib\rt.jar;D:\untitled2\out\production\untitled2" test
push = 1
pop = 0
push = 1
pop = 0
push = 1

正常运行。

2.5 多生产与一消费

public class test {
    public static void main(String[] args) {
        Stack_Thread a = new Stack_Thread();
        cost cost1 = new cost(a);
        produce produce1 = new produce(a);
        produce produce2 = new produce(a);
        produce produce3 = new produce(a);
        cost1.start();
        produce1.start();
        produce2.start();
        produce3.start();
    }
}

2.6 多生产与多消费

public class test {
    public static void main(String[] args) {
        Stack_Thread a = new Stack_Thread();
        cost cost1 = new cost(a);
        cost cost2 = new cost(a);
        cost cost3 = new cost(a);
        produce produce1 = new produce(a);
        produce produce2 = new produce(a);
        produce produce3 = new produce(a);
        cost1.start();
        cost2.start();
        cost3.start();
        produce1.start();
        produce2.start();
        produce3.start();
    }
}

  • 0
    点赞
  • 1
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值