学习笔记--线程间通讯详细记录

摘要

在线程日记中由于篇幅有限不能讲线程间通讯的细节在那边描述所以特地在这片记录中详细的描述线程间通讯的特点及其实现细节。

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循环

 

我们发现由于我们代码中使用的是notify()来唤醒等待的线程的,但它只唤醒第一个等待线程,导致被唤醒的线程再次计入wait而导致没有线程是非冻结状态。我们将notifyAll代替notyfy()发现程序正常执行了。

我们知道当消费者执行完之后要唤醒的是生产者而不是消费者,同理生产者执行完成之后要唤醒的是消费者而不是生产值,但这里我们是全部唤醒,是可以唤醒对方的线程呢??

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实现,其中后者有更强大的功能,建议使用。


  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值