1.java中通过wait(),notify(),notifyAll()进行的线程间通信
在JDK5.0之前,java通过使用wait(用来冻结线程),notify(用来唤醒线程),notifyAll(用来唤醒所有线程)来进行线程间的通信,这几个方法都是继承自object类,(因为调用时绑定监视器,而监视器都是对象故此),调用时,冻结或者唤醒持有相应的监视器对象的线程。当两个线程对同一个资源进行不同的操作时,我们用此来协调两者的工作,下面通过经典的消费者生产者模型的例子来说明:
class Resource
{
private String name;
private int count=1;
private boolean flag = false;
public synchronized void set(String name)
{
if(flag)
{
try
{
this.wait();
}
catch(Exception e)
{
}
}
this.name = name +"*******"+(count++);
System.out.println(Thread.currentThread().getName()+"...生产者..."+this.name);
flag = true;
this.notify();
}
public synchronized void out()
{
if(!flag)
{
try
{
this.wait();
}
catch(Exception e)
{
}
}
System.out.println(Thread.currentThread().getName()+"...消费者..."+this.name);
flag = false;
this.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();
}
}
}
class TestDemo
{
public static void main(String[] args)
{
Resource r = new Resource();
Producer pro = new Producer(r);
Consumer con = new Consumer(r);
Thread t1 = new Thread(pro);
Thread t2 = new Thread(con);
t1.start();
t2.start();
}
}
代码中的Resource类中存储了共享的资源,TestDemo的main方法中创建了两个线程来操作共享资源,当生产者操作资源时,由于flag开关和线程监视器(线程的锁)的存在,
消费者无法操作资源,当生产者生产完之后,唤醒冻结的线程(这里只有另一个消费的线程了,多个消费者和生产者线程的情况后面会讲),并使自己冻结,消费者进行类
似的循环,从而保证了生产者生产一个消费者消费一个,从打印结果可以看出。
下面,说一下多个生产者线程和消费者线程的情况,这种情况下与上述情况不一样的地方是当两个生产者都生产一次之后,当再次切换到生产者时,会绕过if语句的判断,
从而有可能产生生产两次消费一次的情况,我们可以将if语句换成while语句,让其无法绕过判断,从而实现生产一次,消费一次,然后改成while之后执行时会将所有线程置于等待状态,
从而停止执行,我们可以将唤醒函数替换为notifyAll来避免此问题,具体代码如下:
class Resource
{
private String name;
private int count=1;
private boolean flag = false;
public synchronized void set(String name)
{
while(flag)//此处替换为while,捕捉异常是因为wait抛出了异常,不然编译不通过
{
try
{
this.wait();
}
catch(Exception e)
{
}
}
this.name = name +"*******"+(count++);
System.out.println(Thread.currentThread().getName()+"...生产者..."+this.name);
flag = true;
this.notifyAll();//此处替换为notifyAll
}
public synchronized void out()
{
while(!flag)//此处替换为while,捕捉异常是因为wait抛出了异常,不然编译不通过
{
try
{
this.wait();
}
catch(Exception e)
{
}
}
System.out.println(Thread.currentThread().getName()+"...消费者..."+this.name);
flag = false;
this.notifyAll();//此处替换为notifyAll
}
}
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();
}
}
}
class TestDemo
{
public static void main(String[] args)
{
Resource r = new Resource();
Producer pro = new Producer(r);
Consumer con = new Consumer(r);
Thread t1 = new Thread(pro);
Thread t2 = new Thread(con);
Thread t3 = new Thread(pro);
Thread t4 = new Thread(con); //增加了两个线程
t1.start();t2.start();
t3.start();
t4.start();
}}
具体实现效果可以对比一下不修改为while和notifyAll之前的情况2.java中的Lock接口实现线程间通信
JDK5.0以后,提供了更为方便的方法来实现线程间通信,java提供了Lock接口,我们通过其子类ReentrantLock建立指向Lock的对象,该对象的lock和unlock方法替代了之前的同步代码块,然后
通过该对象的newCondition方法建立Condition对象我们可以绑定多个Condition从而可以更准确的控制线程的冻结和唤醒,Condition下提供了await,signal和aignalAll方法,用来取缔wait,notify和
notifyAll方法,还是上面的例子,我们建立多个消费者和生产者线程,我们用刚提到的方法来修改之前的代码。
import java.util.concurrent.locks.*;
class Resource
{
private String name;
private int count=1;
private boolean flag = false;
private Lock lock = new ReentrantLock();//建立Lock对象
private Condition condition = lock.newCondition();//建立Condition对象
public void set(String name) throws InterruptedException//必须声明异常让编译通过
{
lock.lock();//去掉同步代码块或者同步函数,用lock和unlock代替
try
{
while(flag)
condition.await();
this.name = name +"*******"+(count++);
System.out.println(Thread.currentThread().getName()+"...生产者..."+this.name);
flag = true;
condition.signalAll();
}
finally//防止try块中抛出异常后锁状态不更新
{
lock.unlock();
}
}
public synchronized void out() throws InterruptedException//必须声明异常让编译通过
{
lock.lock();//去掉同步代码块或者同步函数,用lock和unlock代替
try
{
while(!flag)
condition.await();
System.out.println(Thread.currentThread().getName()+"...消费者..."+this.name);
flag = false;
condition.signalAll();
}
finally//防止try块中抛出异常后锁状态不更新
{
lock.unlock();
}
}
}
class Producer implements Runnable
{
private Resource res;
Producer(Resource res)
{
this.res = res;
}
public void run()
{
while(true)
{
try
{
res.set("+商品+");
}
catch(Exception e)
{
}
}
}
}
class Consumer implements Runnable
{
private Resource res;
Consumer(Resource res)
{
this.res = res;
}
public void run()
{
while(true)
{
try
{
res.out();
}
catch(Exception e)
{
}
}
}
}
class TestDemo
{
public static void main(String[] args)
{
Resource r = new Resource();
Producer pro = new Producer(r);
Consumer con = new Consumer(r);
Thread t1 = new Thread(pro);
Thread t2 = new Thread(con);
Thread t3 = new Thread(pro);
Thread t4 = new Thread(con);
t1.start();
t2.start();
t3.start();
t4.start();
}}
运行上述代码应该和前面讲到的第二种情况有一样的结果,尽管多个生产线程和消费线程同时处理一个资源,依然是生产一个消费一个。当然,我们可以在创建condition对象的
时候创建两个(上面提到过可以绑定多个condirion),这样,我们在用signal唤醒线程时,只需唤醒对方的线程就行(如果两者共用一个condition而调用时用condition.signal
唤醒线程会出现1中讲到的线程全部被冻结的情况,因此代码中用的是condition.signalAll,利用Lock提供的特性我们可以很容易的解决上面的问题,生产者共用一个condition,
消费者共用一个condition当唤醒时,调用对方condition的signal方法即可,),而不会用aignalAll唤醒所有线程。代码如下:
import java.util.concurrent.locks.*;
class Resource
{
private String name;
private int count=1;
private boolean flag = false;
private Lock lock = new ReentrantLock();//建立Lock对象
private Condition condition1 = lock.newCondition();//建立Condition1对象
private Condition condition2 = lock.newCondition();//建立Condition2对象
public void set(String name) throws InterruptedException//必须声明异常让编译通过
{
lock.lock();//去掉同步代码块或者同步函数,用lock和unlock代替
try
{
while(flag)
condition1.await();//冻结持有condition1监视器的线程
this.name = name +"*******"+(count++);
System.out.println(Thread.currentThread().getName()+"...生产者..."+this.name);
flag = true;
condition2.signal();//唤醒持有condition2监视器的线程
}
finally//防止try块中抛出异常后锁状态不更新
{
lock.unlock();
}
}
public synchronized void out() throws InterruptedException//必须声明异常让编译通过
{
lock.lock();//去掉同步代码块或者同步函数,用lock和unlock代替
try
{
while(!flag)
condition2.await();//冻结持有condition2监视器的线程
System.out.println(Thread.currentThread().getName()+"...消费者..."+this.name);
flag = false;
condition1.signal();//唤醒持有condition1监视器的线程
}
finally//防止try块中抛出异常后锁状态不更新
{
lock.unlock();
}
}
}
class Producer implements Runnable
{
private Resource res;
Producer(Resource res)
{
this.res = res;
}
public void run()
{
while(true)
{
try
{
res.set("+商品+");
}
catch(Exception e)
{
}
}
}
}
class Consumer implements Runnable
{
private Resource res;
Consumer(Resource res)
{
this.res = res;
}
public void run()
{
while(true)
{
try
{
res.out();
}
catch(Exception e)
{
}
}
}
}
class TestDemo
{
public static void main(String[] args)
{
Resource r = new Resource();
Producer pro = new Producer(r);
Consumer con = new Consumer(r);
Thread t1 = new Thread(pro);
Thread t2 = new Thread(con);
t1.start();
t2.start();
}
}