生产者与消费者模式
讲到线程间通信,很难不提及生产者与消费者模式。我先写个简单的生产者与消费者的栗子。
在此之前,先ps下Objecy的wait()和notifly()
JDK文档:
void notify()
//唤醒在此对象监视器上等待的某个线程
//Wakes up a single thread that is waiting on this object’s monitor.
void wait( )
//Causes the current thread to wait until another thread invokes the notify() method or the notifyAll( ) method for this object
//导致当前的线程等待,直到其他线程调用此对象的notify( ) 方法或 notifyAll( ) 方法
wait()和notify()不属于Thread类,属于Object类。因为每个对象都有锁,所以操作锁的方法是基础,写入Object类也不奇怪了。
public class ProducerConsumer {
static final Object Mutex=new Object();
private static int i=0;
private boolean isProduce=false;
public void Produce() {
synchronized (Mutex) {
if(isProduce) {
//如果已生产
try {
Mutex.wait();
} catch (InterruptedException e) {
// TODO Auto-generated catch block
e.printStackTrace();
}
}else {
//未生产则生产
System.out.println("Producer:"+i);
i++;
isProduce=true;
Mutex.notify();
}
}
}
public void Consumer() {
synchronized (Mutex) {
if(isProduce) {
//已生产则消费
System.out.println("Consumer"+i);
isProduce=false;
Mutex.notify();
}else {
//未生产则等待
try {
Mutex.wait();
} catch (InterruptedException e) {
// TODO Auto-generated catch block
e.printStackTrace();
}
}
}
}
}
public static void main(String[] args) throws InterruptedException {
final ProducerConsumer producerConsumer = new ProducerConsumer();
Thread thread1 = new Thread() {
@Override
public void run() {
producerConsumer.Produce();
}
};
Thread thread2 = new Thread() {
@Override
public void run() {
producerConsumer.Consumer();
}
};
thread1.start();
thread2.start();
}
wait和notify的注意事项
- 线程执行了某个对象的wait的方法之后,会加入与之对应的wait set中。每一个monitor都有一个与之关联的wait set
- 必须在同步方法中使用wait和notify方法,因为执行wait和notify的前提是必须持有同步方法的monitor所有权,否则抛出异常。
线程休息室 wait set
wait set的实现并没有统一的规定,不同JDK的实现都不同。
- 所有的对象都都会有一个waitset,用来存放该对象wait()方法之后进入blocked状态的线程
- 线程从waitset被唤醒的顺序不一定是FIFO
- 进入waitset的线程被唤醒后仍然需要去抢得锁的控制权,才能进入同步方法。由于每个线程有自己得程序计数器,线程到RUNNING状态后会切换到原来自己BLOCKED状态的位置往下执行。
上面这个生产者和消费者模式的栗子,在单Producer和单Consumer下可以正常工作,但是不适合在多Producer和多Consumer下运行。
改造下代码,引入notifyall方法
void notifyAll()
//Wakes up all threads that are waiting on this object’s monitor.
//唤醒在此对象监视器上等待的所有线程
public void Produce() {
synchronized (Mutex) {
//这里用while而不用if
//notiflAll()唤醒全部线程,如果只是if的话,全部线程都会执行下面的生产,造成逻辑混乱
while(isProduce) {
//如果已生产
try {
Mutex.wait();
} catch (InterruptedException e) {
// TODO Auto-generated catch block
e.printStackTrace();
}
}
i++;
System.out.println("Producer:"+i);
isProduce=true;
Mutex.notifyAll();
}
}
public void Consumer() {
synchronized (Mutex) {
while(!isProduce) {
try {
Mutex.wait();
} catch (InterruptedException e) {
// TODO Auto-generated catch block
e.printStackTrace();
}
}
System.out.println("Consumer:"+i);
isProduce=false;
Mutex.notifyAll();
}
}
wait()和sleep()的本质区别
- wait方法和sleep方法都可以使当前线程进入阻塞状态
- 均是可中断方法,被中断后都会收到中断异常
- wait是Object的方法,sleep是Thread特有的方法
- wait方法的执行必须在同步方法中,而sleep不需要
- 线程在同步方法执行sleep,并不会释放monito锁;而wait方法则会执行monitor的锁
- sleep方法短暂休眠后会主动退出阻塞,而wait方法(没有指定执行时间)则需要其中线程中断后才能退出阻塞
自定义显氏锁BooleanLock
synchronized关键字的缺陷
- 无法控制阻塞时长
- 阻塞不可被中断,虽然可以设置线程的interrupt标识,但是synchronized阻塞不像sleep和wait能够捕获得到中断信号。
自定义Lock
接口
public interface Lock {
public class TimeOutException extends Exception{
private static final long serialVersionUID = 1L;
public TimeOutException(String message) {
super(message);
}
}
public void lock() throws InterruptedException;
public void lock(long mills) throws InterruptedException,TimeOutException;
public void unlock();
public List<Thread> getThreads();
public int getThreadsSize();
}
实现类
public class BooleanLock implements Lock{
//false代表没有线程获取
//true代表有线程获取
private boolean lockFlag;
private Thread currentThread;
public BooleanLock() {
//默认创建lockFlag为false
this.lockFlag=false;
}
//阻塞线程List
private List<Thread> listThread=new ArrayList<Thread>();
@Override
public synchronized void lock() throws InterruptedException {
//当锁已被其他线程占用
while(lockFlag) {
//将获取不到锁的线程放进阻塞线程list
listThread.add(Thread.currentThread());
this.wait();
}
//成功获取到锁
//从阻塞线程从移出
if(listThread.contains(Thread.currentThread())) {
listThread.remove(Thread.currentThread());
}
this.lockFlag=true;
//获取当前线程,方便之后的操作
//只有lock的线程,才能进行接下来的unlock
this.currentThread=Thread.currentThread();
System.out.println(Thread.currentThread().getName()+"获取monitor");
}
@Override
public synchronized void lock(long mills) throws InterruptedException, TimeOutException {
//传入参数判断
if(mills<=0) {
lock();
}
long hasRemain=mills;
//截止时间
long endTime=System.currentTimeMillis()+mills;
while(lockFlag) {
//超出等待时间,抛出TimeOutException异常
if(hasRemain<=0)
throw new TimeOutException("Time out");
listThread.add(Thread.currentThread());
this.wait(mills);
hasRemain=System.currentTimeMillis()-endTime;
}
this.lockFlag=true;
this.currentThread=Thread.currentThread();
System.out.println(Thread.currentThread().getName()+"获取monitor");
}
@Override
public synchronized void unlock() {
//只有lock的线程才能执行unlock
if(currentThread==Thread.currentThread()) {
this.lockFlag=false;
System.out.println(Thread.currentThread().getName()+"释放该monitor");
//唤醒wait此锁的全部线程
this.notifyAll();
}
}
@Override
public List<Thread> getThreads() {
//禁止修改
return Collections.unmodifiableList(listThread);
}
}
测试类
final BooleanLock booleanlock=new BooleanLock();
Thread thread1 = new Thread() {
@Override
public void run() {
while(true) {
try {
booleanlock.lock(10);
work();
} catch (InterruptedException e) {
// TODO Auto-generated catch block
e.printStackTrace();
} catch (TimeOutException e) {
e.printStackTrace();
}
finally {
booleanlock.unlock();
}
}
}
};
Thread thread2 = new Thread() {
@Override
public void run() {
while(true) {
try {
booleanlock.lock(10);
work();
} catch (InterruptedException e) {
// TODO Auto-generated catch block
e.printStackTrace();
} catch (TimeOutException e) {
e.printStackTrace();
}
finally {
booleanlock.unlock();
}
}
}
};
Thread thread3 = new Thread() {
@Override
public void run() {
while(true) {
try {
booleanlock.lock(10);
work();
} catch (InterruptedException e) {
// TODO Auto-generated catch block
e.printStackTrace();
} catch (TimeOutException e) {
e.printStackTrace();
}
finally {
booleanlock.unlock();
}
} }
};
thread1.start();
thread2.start();
thread3.start();
}
public static void work() throws InterruptedException {
System.out.println(Thread.currentThread().getName()+"is working");
}
打印结果:
Thread-2is working
Thread-2释放该monitor
Thread-2获取monitor
Thread-2is working
Thread-2释放该monitor
…
com.Reyco.MyThread.Lock$TimeOutException: Time out
at com.Reyco.MyThread.BooleanLock.lock(BooleanLock.java:44)
at com.Reyco.MyThread.MyTest$1.run(MyTest.java:13)