生产者消费者--wait、notify实现
[TOC]
一、wait
wait是Object的一个方法,调用wait方法立即释放当前对象锁,并且使得当前线程进入阻塞状态.
这也意味着一个对象调用wait方法时当前线程必须要持有该对象的锁,否则会抛异常.
1.线程持有对象锁的三种方式:
- By executing a synchronized instance method of that object.
- By executing the body of a synchronized statement that synchronizes on the object.
- By executing the body of a synchronized statement that synchronizes on the object.
- For objects of type Class, by executing a synchronized static method of that class.
2. 由wait而阻塞到进入可运行状态的四个条件:
- Some other thread invokes the notify method for this object and thread T happens to be arbitrarily chosen as the thread to be awakened.
- Some other thread invokes the notifyAll method for this object.
- Some other thread interrupts thread T.
- The specified amount of real time has elapsed, more or less. If timeout is zero, however, then real time is not taken into consideration and the thread simply waits until notified.
3.wait最好放在循环里面
A thread can also wake up without being notified, interrupted, or timing out, a so-called spurious wakeup. While this will rarely occur in practice,(这难道是java语言的bug)
为了避免线程的假醒状态,wait方法最好以一下形式出现
synchronized (obj) {
while (<condition does not hold>)
obj.wait(timeout);
... // Perform action appropriate to condition
}
原因参见:Doug Lea's "Concurrent Programming in Java (Second Edition)" (Addison-Wesley, 2000), or Item 50 in Joshua Bloch's "Effective Java Programming Language Guide" (Addison-Wesley, 2001).
一个原因:线程在wait的同时一些变量可能已经发生改变,当该线程再次获得执行权的时候,while部分的条件可能还是不成立,如果while改成if,就可能出现问题,因此,wait最好放在while循环里面,这个问题在会发生在单生产者多消费者上
二、notify
- notify用来唤醒一个由于wait而等待的线程,notifyAll用来唤醒全部由于wait而等待的线程。
- The awakened thread will not be able to proceed until the current thread relinquishes the lock on this object. The awakened thread will compete in the usual manner with any other threads that might be actively competing to synchronize on this object; for example, the awakened thread enjoys no reliable privilege or disadvantage in being the next thread to lock this object.
解释:
被唤醒的线程要不仅要等待调用notify的线程释放它持有的锁,而且之后还要 去和其它要持有该对象锁的线程竞争,换句话说被唤醒的线程还要满足两个条件才能获得执行权
被唤醒不一定就会执行,还要抢到cpu的执行权
代码验证:
package com.threadcommuncation;
/**
* Created by liouville on 3/27/16.
* 理解notify wait关键字
*/
public class NotifyWaitTest {
private final static Object lock = new Object();
private static boolean flag = false;
public static void main(String[] args) {
//lock.notify();
for (int i = 0; i < 2; i++) {
new ThreadWait().start();
}
try {
Thread.sleep(1000);
} catch (InterruptedException e) {
e.printStackTrace();
}
new ThreadA().start();
new ThreadNotify().start();
}
private static class ThreadA extends Thread{
@Override
public void run() {
while (!flag){yield();}
synchronized (lock){
System.out.println("Thread A抢到了锁");
}
}
}
private static class ThreadWait extends Thread{
@Override
public void run() {
synchronized (lock){
System.out.println("线程" + getName() + "即将等待");
try {
/**
* 调用wait方法立即释放当前对象锁,并且使得当前线程进入阻塞状态.
* 这也意味着一个对象调用wait方法时当前线程必须要持有该对象的锁,否则会抛异常.
*/
lock.wait();
} catch (InterruptedException e) {
e.printStackTrace();
}
}
System.out.println("线程" + getName() + "结束");
}
}
/**
* The awakened thread will not be able to proceed until the current
* thread relinquishes the lock on this object. The awakened thread
* will compete in the usual manner with any other threads that might
* be actively competing to synchronize on this object; for example,
* the awakened thread enjoys no reliable privilege or disadvantage
* in being the next thread to lock this object. --doc
*
* 说明两点:
* 被唤醒的线程要不仅要等待调用notify的线程释放它持有的锁,而且之后还要 去和其它要持有该对象锁的
* 线程去竞争
*/
private static class ThreadNotify extends Thread{
@Override
public void run() {
synchronized (lock){
System.out.println("notify 线程抢到锁");
lock.notifyAll();
flag = true;
}
}
}
}
多运行几次,发现输出:
线程Thread-0即将等待
线程Thread-1即将等待
notify 线程抢到锁
Thread A抢到了锁
线程Thread-1结束
线程Thread-0结束
三、生产者和消费者问题
1.问题简述
生产者消费者问题(英语:Producer-consumer problem),也称有限缓冲问题(Bounded-buffer problem),是一个多线程同步问题的经典案例。该问题描述了两个共享固定大小缓冲区的线程——即所谓的“生产者”和“消费者”——在实际运行时会发生的问题。生产者的主要作用是生成一定量的数据放到缓冲区中,然后重复此过程。与此同时,消费者也在缓冲区消耗这些数据。
该问题的关键就是要保证生产者不会在缓冲区满时加入数据,消费者也不会在缓冲区中空时消耗数 -- 维基百科
我的说法:生产者、消费者、仓库
生产者负责往仓库里面加物品,消费者负责取,仓库为空时不能取物品,满时不呢加物品.如何协调生产者和消费者满足这个条件?可以用线程间通信原语wait notify解决
2.单生产单消费
package com.threadcommuncation.PAndC;
import java.util.ArrayList;
/**
* Created by liouville on 3/27/16.
* 单生产者单消费者,
*
* 用wait notify实现线程间通信
*
* Res.list.size == 1时,只能消费;等于0时,只能生产
*/
public class SimplePAndC {
private static final Object lock = new Object();
public static void main(String[] args) {
new ProductThread().start();
new ConsumerThread().start();
}
private static class ProductThread extends Thread{
@Override
public void run() {
super.run();
while (true){
synchronized (lock){
if (Res.list.size() != 0){
try {
lock.wait();
} catch (InterruptedException e) {
e.printStackTrace();
}
}
Res.list.add("h");
System.out.println(getName() + ": 生产者生产一个元素");
try {
Thread.sleep(1000);
} catch (InterruptedException e) {
e.printStackTrace();
}
lock.notify();
}
}
}
}
private static class ConsumerThread extends Thread{
@Override
public void run() {
super.run();
while(true){
synchronized (lock){
while(Res.list.size() == 0){
try {
lock.wait();
} catch (InterruptedException e) {
e.printStackTrace();
}
}
Res.list.remove(0);
System.out.println(getName() + ": 消费者消费一个元素");
try {
Thread.sleep(1000);
} catch (InterruptedException e) {
e.printStackTrace();
}
lock.notify();
}
}
}
}
private static class Res{
static ArrayList<String> list = new ArrayList<>();
}
}
3.在单生产单消费的基础之上实现单生产多消费
package com.threadcommuncation.PAndC;
import java.util.ArrayList;
/**
* Created by liouville on 3/27/16.
* 单生产者多消费者,
*
* Res.list.size == 1时,只能消费;等于0时,只能生产
*
* 两个问题:
*
* 1.数组越界:因为有这么一种情况,
*
* 由于最开始list.size == 0,如果消费者A得到了执行,进入等待状态.
* 生产者生产完一个元素以后唤醒消费者A,但是唤醒不一定代表能抢到对象锁,
* 若消费者B抢到了执行权,此时消费B者顺利消费一个元素,之后唤醒的是消费者A.
* 由于此时list.size == 0,消费一个元素就会报空指针异常.
*
* 解决办法:改if为while
*
* 2.所有线程都在等待的问题:因为改if为while,所以可能出现消费者唤醒消费者的情况,使得被唤醒的生产者
* 继续等待,而生产者也处于等待状态,所以所有线程都处于等待状态,
* 解决办法:改notify为notifyAll即可
*/
public class PAndCS {
private static final Object lock = new Object();
public static void main(String[] args) {
new ProductThread().start();
for (int i = 0; i < 3; i++) {
new ConsumerThread().start();
}
}
private static class ProductThread extends Thread{
@Override
public void run() {
super.run();
while (true){
synchronized (lock){
if (Res.list.size() != 0){
try {
lock.wait();
} catch (InterruptedException e) {
e.printStackTrace();
}
}
Res.list.add("h");
System.out.println(getName() + ": 生产者生产一个元素");
try {
Thread.sleep(1000);
} catch (InterruptedException e) {
e.printStackTrace();
}
lock.notify();
}
}
}
}
private static class ConsumerThread extends Thread{
@Override
public void run() {
super.run();
while(true){
synchronized (lock){
while(Res.list.size() == 0){
try {
lock.wait();
} catch (InterruptedException e) {
e.printStackTrace();
}
}
Res.list.remove(0);
System.out.println(getName() + ": 消费者消费一个元素");
try {
Thread.sleep(1000);
} catch (InterruptedException e) {
e.printStackTrace();
}
lock.notifyAll();
}
}
}
}
private static class Res{
static ArrayList<String> list = new ArrayList<>();
}
}
多生产和多消费与之类似.