摘要
在线程日记中由于篇幅有限不能讲线程间通讯的细节在那边描述所以特地在这片记录中详细的描述线程间通讯的特点及其实现细节。
1.多生产者和消费者出现的问题
首先来看下面的代码我们知道,在main函数中启动了2个线程执行消费者和2个线程执行生产者,会产生如下问题:生产了2个只消费1个,或者消费了2次但只生产1次。
产生问题的大概过程如下(t1,t2为生产者,t3,t4为消费者):
t1执行完成,t1没有释放执行权继续执行t1冻结进入等待线程池,t3获得执行权执行完成并调用notify使t1就绪,但此时t2获得执行权t2执行完成生产一个,由于t1已经就绪t1获得执行权此时已经没有判断flag的代码,所以t1进行生产这样就导致了生产了2次。
消费2次的问题产生过程大致和上述一致。
class CreateConsumer {
public static void main(String[] args) {
// TODO Auto-generated method stub
Consumer c = new Consumer(Item.getInstance());
Producer p = new Producer(Item.getInstance());
Thread t1 = new Thread(c);
Thread t2 = new Thread(p);
Thread t3 = new Thread(p);
Thread t4 = new Thread(c);
t1.start();
t2.start();
t3.start();
t4.start();
}
}
class Item{
private String name = null;
private int count = 0;
private boolean flag = false;
private static Item instance = new Item();
}
class Producer implements Runnable{
private Item item = null;
public Producer(Item item){
this.item = item;
}
@Override
public void run() {
// TODO Auto-generated method stub
while(true){
try{
synchronized (item) {
if(item.getFlag()){
item.wait();
}
int count = item.getCount()+1;
item.setCount(count);
System.out.println("生产者生产商品====商品"+count);
item.setFlag(true);
item.notify();
}
}catch(Exception e){
}
}
}
}
class Consumer implements Runnable{
private Item item = null;
public Consumer(Item item){
this.item = item;
}
@Override
public void run() {
// TODO Auto-generated method stub
synchronized (item) {
while(true){
try{
if(!item.getFlag()){
item.wait();
}
System.out.println("消费者消费商品==========商品"+item.getCount());
item.setFlag(false);
item.notify();
}catch(Exception e){
}
}
}
}
}
2.如何解决上述问题(while-notifyAll)
我们知道导致问题的出现是唤醒的线程没有再次去判断是否需要进行生产而直接进行了生产,那我们可以将if(item.getFlag())中的if改成while让线程唤醒后再次进行是否生产或消费的判断。修改代码执行我们发现程序不再生产和进行消费,就是生产和消费的进程一直在等待:问题产生过程大致如下:
执行步骤 | 结果 | 线程等待队列 |
T1执行 | 顺利生产 |
|
T1执行 | T1 wait | T1 |
T2执行 | T2wait | T1,T2 |
T3 | 顺利消费,激活T1 | T2 |
T3 | T3wait | T2,T3 |
T4 | T4wait | T2,T3,T4 |
T1 | 顺利生产,激活T2 | T2,T3,T4 |
T1 | T1wait | T3,T4,T1 |
T2 | 一直执行T2代码的for循环 |
|
我们知道当消费者执行完之后要唤醒的是生产者而不是消费者,同理生产者执行完成之后要唤醒的是消费者而不是生产值,但这里我们是全部唤醒,是可以唤醒对方的线程呢??
3.解决上述问题(lock和condition)
上一小节中提出的问题的关键是等待线程池中只有一个,如果有多个则可以试下唤醒对方线程,在lock中可以创建多个等待线程池换句话说可以将线程分门别类的放在对应的等待线程池当中。使用lock实现进程通讯代码如下:
import java.util.concurrent.locks.Condition;
import java.util.concurrent.locks.Lock;
import java.util.concurrent.locks.ReentrantLock;
public class CreateConsumer {
/**
* @param args
*/
public static void main(String[] args) {
// TODO Auto-generated method stub
Consumer c = new Consumer(Item.getInstance());
Producer p = new Producer(Item.getInstance());
Thread t1 = new Thread(c);
Thread t2 = new Thread(p);
Thread t3 = new Thread(p);
Thread t4 = new Thread(c);
t1.start();
t2.start();
t3.start();
t4.start();
}
}
class Item{
private String name = null;
private int count = 0;
private boolean flag = false;
private static Item instance = new Item();
private Lock lock = new ReentrantLock();
private Condition con_p = lock.newCondition();
private Condition con_c = lock.newCondition();
public static Item getInstance(){
return Item.instance;
}
}
class Producer implements Runnable{
private Item item = null;
public Producer(Item item){
this.item = item;
}
@Override
public void run() {
// TODO Auto-generated method stub
while(true){
try{
item.getLock().lock();
while(item.getFlag()){
item.getCon_p().await();
}
int count = item.getCount()+1;
item.setCount(count);
System.out.println("生产者生产商品====商品"+count);
item.setFlag(true);
item.getCon_c().signal();
}catch(Exception e){
}finally{
item.getLock().lock();
}
}
}
}
class Consumer implements Runnable{
private Item item = null;
public Consumer(Item item){
this.item = item;
}
@Override
public void run() {
// TODO Auto-generated method stub
while(true){
try{
item.getLock().lock();
while(!item.getFlag()){
item.getCon_c().await();
}
System.out.println("消费者消费商品==========商品"+item.getCount());
item.setFlag(false);
item.getCon_p().signal();
}catch(Exception e){
}finally{
item.getLock().lock();
}
}
}
}
4.synchronized和lock
主要相同点:Lock能完成synchronized实现的所有功能。
主要不同点:Lock比synchronized有更精确的线程语义和更好的性能,synchronized会自动释放锁而Lock需要需要程序员自己人工释放锁,并且需要在finally代码块中释放锁。
总结
线程间通讯的实现方法有:while notifyAll和Lock Condition实现,其中后者有更强大的功能,建议使用。