从并发的最基本的问题说起——生产者与消费者。
在此之前,我们介绍下Object中的两类方法:wait(),notify()
等待与唤醒
包括wait(), wait(long timeout), notify(), notifyAll()。
和Thread中的静态方法sleep(),join(),yield()不同,wait()和notify()是Object中的非静态方法(join也是非静态方法)。
wait()
wait()
:非静态方法,调用该方法表示当前线程放弃对于当前对象的锁的持有,且当前线程一直停下等待。对象调用该方法需要当前线程持有该对象的锁
看下面一段代码:
Object o = new Object();
System.out.println("start");
try {
o.wait();
} catch (InterruptedException e) {
e.printStackTrace();
}
System.out.println("wait");
上述代码运行会报错:
Exception in thread "main" java.lang.IllegalMonitorStateException
,因为当前线程(即主线程)没有持有Object对象的锁,而根据Object源码的解释,如果在当前线程调用对象的wait()方法,而当前线程又不持有该对象的锁,那么会出现:IllegalMonitorStateException。
所以正确的用法是:
Object o = new Object();
System.out.println("start");
synchronized (o){
try {
o.wait();
} catch (InterruptedException e) {
e.printStackTrace();
}
}
System.out.println("wait");
synchronized方法体获取了o的锁,进而才执行方法体的内容,不会报错。
如果当前线程中调用了wait()方法,即使其他线程释放了该对象的锁,当前线程也无法获取, 必须要被唤醒才行。
notify()
同样是Object的方法,非静态方法,o.notify()
用于唤醒一个调用过o.wait()
的线程,同样需要在synchronized方法体中执行,即需要当前线程持有o的锁。
另外说一下notifyAll(),肯定就是唤醒所有了。
还有wait(long timeout),表示当前线程除了放弃锁,但只等待timeout的时间,时间一道,就自己唤醒,等待获取锁,相当于等待后在调用o.notifySelf()(这个方法是我自己编的)。
生产者与消费者
我们使用wait(),notify()实现生产者消费者实例如下:
public class Test01{
private PriorityQueue<Integer> queue=new PriorityQueue<Integer>(5);
public static void main(String[] args) {
Test01 object=new Test01();
Producer producer=object.new Producer();
Consumer consumer=object.new Consumer();
producer.start();
consumer.start();
}
class Consumer extends Thread {
@Override
public void run() {consume();}
private void consume() {
while(true) {
synchronized (queue) {
while(queue.size()==0) {
try {
System.out.println("队列为空");
queue.wait();
} catch (Exception e) {
e.printStackTrace();
queue.notify();
}
}
queue.poll();
queue.notify();
System.out.println("取走一个元素,还有:"+queue.size());
}
}
}
}
class Producer extends Thread {
@Override
public void run() {produce();}
private void produce() {
while(true) {
synchronized (queue) {
while(queue.size()==5) {
try {
System.out.println("队列已经满了");
queue.wait();
} catch (InterruptedException e) {
e.printStackTrace();
}
}
queue.offer(1);
queue.notify();
System.out.println("插入一个元素,长度为"+queue.size());
}
}
}
}
}
输出结果如下(该输出结果一般是固定的):
插入一个元素,长度为1
插入一个元素,长度为2
插入一个元素,长度为3
插入一个元素,长度为4
插入一个元素,长度为5
队列已经满了
取走一个元素,还有:4
取走一个元素,还有:3
取走一个元素,还有:2
取走一个元素,还有:1
取走一个元素,还有:0
队列为空
插入一个元素,长度为1
插入一个元素,长度为2
插入一个元素,长度为3
插入一个元素,长度为4
插入一个元素,长度为5
队列已经满了
取走一个元素,还有:4
取走一个元素,还有:3
取走一个元素,还有:2
取走一个元素,还有:1
取走一个元素,还有:0
队列为空
……
另一种写法不使用notify(),而使用wait(time)如下:
public class Test01{
private PriorityQueue<Integer> queue=new PriorityQueue<Integer>(5);
public static void main(String[] args) {
Test01 object=new Test01();
Producer producer=object.new Producer();
Consumer consumer=object.new Consumer();
producer.start();
consumer.start();
}
class Consumer extends Thread {
@Override
public void run() {consume();}
private void consume() {
while(true) {
synchronized (queue) {
while(queue.size()==0) {
try {
System.out.println("队列为空");
queue.wait(3000);
} catch (Exception e) {
e.printStackTrace();
queue.notify();
}
}
queue.poll();
System.out.println("取走一个元素,还有:"+queue.size());
}
}
}
}
class Producer extends Thread {
@Override
public void run() {produce();}
private void produce() {
while(true) {
synchronized (queue) {
while(queue.size()==5) {
try {
System.out.println("队列已经满了");
queue.wait(3000);
} catch (InterruptedException e) {
e.printStackTrace();
}
}
queue.offer(1);
System.out.println("插入一个元素,长度为"+queue.size());
}
}
}
}
}
亲仔细查看代码,生产者消费者应该不难理解。
作为一个引子,本节提出了不使用concurrent包而实现同步的方法,即使用synchronized来实现同步,其实我们一般也是这么做的。在后面我们将分析concurrent里的锁等类,并使用锁实现生产者消费者。