当使用多线程来同时运行多个任务时,可以通过锁来同步多个任务的行为,从而使得一个任务不会干涉另一个任务的资源。也就是说,如果多个任务在交替着访问内存中的某项资源,可以通过锁来控制同一时刻只有一个任务可以访问该资源。为了使任务可以协作,我们可以使用wait()和notify()机制来控制。
这里,我们用生产者和消费者的例子来说明:
/*
定义资源类,类中包括生产set和消费out的方法
*/
class Resource{
private String name;
private int count = 1;
private boolean flag = false;
public synchronized void set(String name){
while(flag)
try{
wait();
}catch(InterruptedException e){
e.printStackTrace();
}
this.name = name+count++;
System.out.println(Thread.currentThread().getName()+"生产"+this.name);
flag = true;
notifyAll();//此处若改为notify()方法,可能会使所有线程都陷入等待状态
}
public synchronized void out(){
while(!flag)
try{
wait();
}catch(InterruptedException e){
e.printStackTrace();
}
System.out.println("..."+Thread.currentThread().getName()+"消费...."+this.name);
flag = false;
notifyAll();
}
}
//定义生产者类,实现Runnable接口的run方法
class Producer implements Runnable{
private Resource res;
Producer(Resource res){
this.res = res;
}
public void run(){
while(true)
res.set("产品");
}
}
//定义消费者类,实现Runnable接口的run方法
class Consumer implements Runnable{
private Resource res;
Consumer(Resource res){
this.res = res;
}
public void run(){
while(true)
res.out();
}
}
public class Noname1{
public static void main(String[] args){
Resource res = new Resource();
Producer pro = new Producer(res);
Consumer con = new Consumer(res);
//创建两个生产者线程
Thread t0 = new Thread(pro);
Thread t1 = new Thread(pro);
//创建两个消费者线程
Thread s2 = new Thread(con);
Thread s3 = new Thread(con);
//分别启动线程
t0.start();
t1.start();
s2.start();
s3.start();
}
}
上面的例子定义了两个生产者和两个消费者,
生产者生产一个产品,消费者消费一个,它们可
以很和谐地工作。运行结果如图。
我们在set和out方法里使用的唤醒等待线程
的方法是notifyAll(),我们知道,如果使用notify()
的话,由于每次只能唤醒单个线程,所以最终可
能导致所有线程都陷入等待状态。
可是,问题是,如果同时有多个线程处于等
待状态,notify()方法会唤醒哪个线程呢?为此,
我查阅了一些资料,有的说是随机唤醒一个线程,
有的说是唤醒最先等待的线程,还有的对此问题
避而不谈,下面,我们通过具体实例来验证,还
是使用的生产者与消费者的例子。
为了验证上面的问题,我们将上面的代码进行了一些改变
class Resource{
private String name;
private int count = 1;
private boolean flag = false;
public synchronized void set(String name){
while(flag)
try{
wait();
}catch(InterruptedException e){
e.printStackTrace();
}
this.name = name+count++;
System.out.println(Thread.currentThread().getName()+"生产"+this.name);
flag = true;
notify();
}
public synchronized void out(){
while(!flag)
try{
wait();
}catch(InterruptedException e){
e.printStackTrace();
}
System.out.println("..."+Thread.currentThread().getName()+"消费...."+this.name);
flag = false;
notify();
}
}
class Producer implements Runnable{
private Resource res;
Producer(Resource res){
this.res = res;
}
public void run(){
while(true)
res.set("产品");
}
}
class Consumer implements Runnable{
private Resource res;
Consumer(Resource res){
this.res = res;
}
public void run(){
while(true)
res.out();
}
}
public class ProducerDemo{
public static void main(String[] args){
Resource res = new Resource();
Producer pro = new Producer(res);
Consumer con = new Consumer(res);
//创建两个生产者线程
Thread t0 = new Thread(pro);
Thread t1 = new Thread(pro);
//创建两个消费者线程
Thread t2 = new Thread(con);
Thread t3 = new Thread(con);
t0.start();
try{
Thread.sleep(10);
}catch(InterruptedException e){
e.printStackTrace();
}
t1.start();
//让主线程停止10ms,目的是使两个生产者线程都陷入等待
try{
Thread.sleep(10);
}catch(InterruptedException e){
e.printStackTrace();
}
t2.start();
try{
Thread.sleep(10);
}catch(InterruptedException e){
e.printStackTrace();
}
t3.start();
}
}
其中,其他内容不变,只有主方法中插入了一些sleep()方法,目的是使各个线程按照我们自己的意愿去运行。我们先看运行结果如图。下面我们来分析:
首先,语句 t0.start()启动t0线程,生产出产品1,t0陷入等待;
接着t1线程启动,由于flag被置为true,所以,不生产产品,t1直接等待;
然后t2线程启动,消费产品1,将flag置为flase,唤醒一个线程,t2等待;
根据输出,我们可以看到t2唤醒的是线程是t0,所以t0执行,生产产品2,唤醒一个线程,t0再次等待;
根据输出,t0唤醒的线程并没有执行,而是直接进入等待,接着执行t3,消费产品2,唤醒一个线程,t3等待;
根据输出,t3唤醒的线程也没有执行,直接陷入等待,自此,所有的线程都在等待状态,所以程序停滞不前。
为了更好的说明问题,我们通过画图来解释:
根据结果我们可以推论,用来存储等待中的线程的容器为一队列,即履行“先进先出”的原则。如上图所示,从下至上为进入等待状态线程的顺序。当执行notify()方法时,也是从下至上的顺序。可以看到,最终,t0,t1,t2,t3四个线程都进入等待状态。