1.问题引入
线程之间也是需要协作的,比如生产者-消费者模型:当队列满时, 生产者需要等待队列中有空间才能够向里面放入物品,而在等待的时间内,生产者必须释放对资源(队列)的占有权。因为如果生产者不释放对资源的占有权,那么消费者就无法从队列中取出物品,就不会让队列有空间,进而生产者就会一直无限等待下去。因此,一般情况下,当队列满时,会让生产者交出占有权,并进入挂起状态。然后等待消费者消费了物品,再通知生产者队列有空间了。同样的,当队列为空时,消费者也必须等待,等待生产者通知它队列中有物品了。这种相互通信的过程就是线程间的协作
2.wait()、notify()、notifyAll()
wait()、notify()、notifyAll()是Object类中的方法
public final native void notify();
public final native void notifyAll();
public final native void wait(long timeout) throws InterruptedException;
可以看出以下几点:
(1)wait()、notify()、notifyAll()都是native方法,并且都为final方法,无法被重写
(2)调用某个对象的wait()方法能让当前线程阻塞,并且当前线程必须拥有此对象的锁(monitor)
(3)调用某个对象的notify()方法能够唤醒一个正在等待这个对象的monitor的线程,如果有多个线程都在等待这个对象的monitor,则只能唤醒其中一个线程。
(4)调用notifyAll()方法能够唤醒所有正在等待这个对象的monitor的线程。
注意:为何wait()、notify()、notifyAll()不是定义在Thread类中,而是在Object中?
由于每个对象都拥有monitor(锁),所以让当前线程等待某个对象的锁,当然应该通过这个对象来操作了。而不是使用当前线程来操作,因为当前线程可能会等待多个线程的锁,如果通过线程来操作,会非常复杂。
1>wait
如果调用某个对象的wait()方法,则当前线程必须拥有这个对象的monitor(即锁),因此调用wait()方法必须在同步代码块或同步方法中。调用wait()方法,相当于让当前线程交出此对象的monitor,然后进入等待状态,等待后续再次获得此对象的锁(Thread类中的sleep方法使当前线程暂停执行一段时间,从而让其他线程有机会执行,但是它并不释放对象锁)
2>notify
notify方法能够唤醒一个正在等待该对象的monitor线程,当有多个线程都在等待该对象的monitor的话,则只能唤醒其中一个线程,具体唤醒哪一个不确定。同样的调用某个对象的notify方法,当前线程也必须拥有这个对象的monitor,因此调用notify方法必须在同步代码块或者同步方法中进行
3>notifyAll
notifyAll方法能够唤醒所有正在等待该对象的monitor的线程。注意:此处唤醒不等于所有线程都获得该对象的monitor,至于哪个等待的线程能够获得就不确定了。
public class Test
{
private static Object object = new Object();
public static void main(String[] args)
{
new Thread(new Runnable() {
@Override
public void run()
{
synchronized(object)
{
try
{
object.wait();
System.out.println("thread 0 get lock");
}
catch (InterruptedException e)
{
// TODO Auto-generated catch block
e.printStackTrace();
}
}
}
}).start();
new Thread(new Runnable()
{
@Override
public void run()
{
synchronized(object)
{
System.out.println("thread 1 get lock");
object.notify();
System.out.println("thread 1 release lock");
}
}
}).start();
}
}
结果为:
thread 1 get lock
thread 1 release lock
thread 0 get lock
2.Condition
用来代替传统Object的wait()、notify()实现线程间的协作,相比使用Object的wait()、notify(),使用Condition的await(),signal()这种方式实现线程间协作更加安全和高效。通常情况下推荐使用Condition。
注意:
(1)Condition是一个接口,基本的就是await()和signal()方法
(2)Condition依赖于Lock接口,生成一个Condition的代码就是lock.newCondition()
(3)调用Condition的await()和signal()方法,都必须在lock保护之内,也就是说必须在lock.lock()和lock.unlock()之间才可以使用
3.生产者-消费者模型实现
class ProducerCustomer
{
private Queue<Integer> queue = new LinkedList<Integer>();
private int size = 3;
public void produce(int i)
{
synchronized (queue)
{
try
{
while(queue.size() >= size)
{
queue.wait();
System.out.println("producer get monitor");
}
queue.offer(i);
queue.notify();
}
catch (InterruptedException e)
{
e.printStackTrace();
}
}
}
public int consume()
{
int ans = 0 ;
synchronized(queue)
{
try
{
while (queue.size() == 0)
{
queue.wait();
System.out.println("consumer get monitor");
}
ans = queue.poll();
queue.notify();
}
catch (InterruptedException e)
{
e.printStackTrace();
}
}
return ans;
}
}
public static void main(String[] args)
{
final ProducerCustomer pc = new ProducerCustomer();
new Thread(new Runnable() {
@Override
public void run()
{
for(int i=0; i<8; i++)
pc.produce(i);
}
}).start();
new Thread(new Runnable() {
@Override
public void run()
{
for(int i=0; i<8; i++)
System.out.println(pc.consume());
}
}).start();
}
结果为:
0
producer get monitor
producer get monitor
1
2
3
4
producer get monitor
consumer get monitor
5
6
7
(2)使用Condition
class ProducerCustomer
{
private Queue<Integer> queue = new LinkedList<Integer>();
private int size = 3;
private Lock lock = new ReentrantLock();
private Condition notFull = null;
private Condition notEmpty = null;
public ProducerCustomer()
{
notFull = lock.newCondition();
notEmpty = lock.newCondition();
}
public void produce(int i)
{
lock.lock();
try
{
while(queue.size()>=size)
{
System.out.println("数据满了");
notFull.await();
}
queue.offer(i);
notEmpty.signal();
}
catch (InterruptedException e)
{
e.printStackTrace();
}
finally
{
lock.unlock();
}
}
public int consume()
{
int ans = 0;
lock.lock();
try
{
while(queue.isEmpty())
{
System.out.println("数据为空");
notEmpty.await();
}
ans = queue.poll();
notFull.signal();
}
catch (InterruptedException e)
{
e.printStackTrace();
}
finally
{
lock.unlock();
}
return ans;
}
}
public static void main(String[] args)
{
final ProducerCustomer pc = new ProducerCustomer();
new Thread(new Runnable() {
@Override
public void run()
{
for(int i=0; i<8; i++)
pc.produce(i);
}
}).start();
new Thread(new Runnable() {
@Override
public void run()
{
for(int i=0; i<8; i++)
System.out.println(pc.consume());
}
}).start();
}
结果为:
数据满了
0
1
2
数据为空
数据满了
3
4
5
6
7
简单的说,编写需要通信的线程,实际上就是将if改成while,其他的按之前的思路就可以了。