多线程--生产者消费者范例,使用lock和condition

        上一篇的博客已经介绍了生产者和消费者,最后还是遗留了一个问题,就是必须Notifyall才能保证唤醒对方线程,这样降低了效率,那么,有没有什么办法可以指定我们来唤醒哪一个线程呢?

    在Jdk1.5以后将同步和锁封装成了对象,并将操作锁的隐式方式定义到了该对象当中,将隐式方式定义到了该对象中,将隐式动作变成了显示动作。

    这个对象就是lock:替代了同步代码快或者同步函数。将同步的隐式锁操作变成现实锁操作。同时更为灵活。可以一个锁上加上多组监视器。

    

  主要方法:lock():获取锁   Unlock():释放锁,通常需要定义在finally代码块中。 condition接口:出现替代了object中的wait,notify,notifyall方法。

    先看怎么用Lock解决之前的问题。

public class ProducerandCustomer {

	public static void main(String[] args) {
		// TODO Auto-generated method stub
			Resouce resouce=new Resouce();
			Producer producer=new Producer(resouce);
			Customer customer=new Customer(resouce);
			
			Thread t0=new Thread(producer);
			Thread t1=new Thread(producer);
			Thread t2=new Thread(customer);
			Thread t3=new Thread(customer);
			
			t0.start();
			t1.start();
			t2.start();
			t3.start();
	}

}

class Resouce{
	private String name;
	private int count=1;
	private boolean flag=false;
	//通过已有的锁获取两组监视器,一组监视生产者,一组监视消费者
	Lock lock=new ReentrantLock();
	Condition producer_con=lock.newCondition();
	Condition consumer_con=lock.newCondition();
	
	public  void set(String name)
	{
		lock.lock();
		try {
			while(flag){
				try {
					producer_con.await();
				} catch (InterruptedException e) {
					// TODO Auto-generated catch block
					e.printStackTrace();
				}
			}
			this.name=name+count;
			count++;
			System.out.println(Thread.currentThread().getName()+"..生产者.."+this.name);
			flag=true;
			//notifyAll();
			consumer_con.signal();//唤醒消费者线程
		} finally {
			lock.unlock();//一定要释放锁
		}
		
		
	}
	
	public  void out(){
		lock.lock();
		try {
			while(!flag){
				try {
					consumer_con.await();
				} catch (InterruptedException e) {
					// TODO Auto-generated catch block
					e.printStackTrace();
				}
			}
			System.out.println(Thread.currentThread().getName()+"..消费者.."+this.name);
			flag=false;
			//notifyAll();
			producer_con.signal();
		}finally{
			lock.unlock();
		}
		
	}
}

class Producer implements Runnable{
	Resouce resouce;
	public Producer(Resouce resouce){
		this.resouce=resouce;
	}
	public void run(){
		while(true){
			resouce.set("烤鸭");
		}
	}
}

class Customer implements Runnable{
	Resouce resouce;
	public Customer(Resouce resouce) {
		// TODO Auto-generated constructor stub
		this.resouce=resouce;
	}
	@Override
	public void run() {
		// TODO Auto-generated method stub
		while(true){
			resouce.out();
		}
		
	}
	
}

再来看一组官方api给的代码,使用Lock

public class BounderBuffer {
	final Lock lock=new ReentrantLock();
	final Condition notfull=lock.newCondition();
	final Condition notEmpty=lock.newCondition();
	
	final Object[] items=new Object[100];
	int putptr,takeptr,count;
	//putptr为生产者操作数组的角标,takeptr为消费者操作的角标,count用来计数,生产一个加1,消费一个减1.
	public void put(Object x) throws InterruptedException{
		lock.lock();
		try {
			while(count==items.length)//如果数组已经满,停止生产,冻结状态
				notfull.await();
			items[putptr]=x;
			if(++putptr==items.length)//如果索引已经到达数组长度。从0开始生产数据
				putptr=0;
			++count;
			notEmpty.signal();//唤醒消费者
		} finally{
			lock.unlock();//释放锁
		}
	}
	
	public Object take() throws InterruptedException{
		lock.lock();
		try{
			while(count==0)//若还没有生产,消费线程冻结
				notEmpty.await();
			Object x=items[takeptr];
			if(++takeptr==items.length)
				takeptr=0;
			--count;
			notfull.signal();
			return x;
		}finally{
			lock.unlock();
		}
	}

lock和synchronized的区别:

一、synchronized和lock的用法区别

 
(1)synchronized(隐式锁):在需要同步的对象中加入此控制,synchronized可以加在方法上,也可以加在特定代码块中,括号中表示需要锁的对象。
 
2)lock(显示锁):需要显示指定起始位置和终止位置。一般使用ReentrantLock类做为锁,多个线程中必须要使用一个ReentrantLock类做为对 象才能保证锁的生效。且在加锁和解锁处需要通过lock()和unlock()显示指出。所以一般会在finally块中写unlock()以防死锁。
 

二、synchronized和lock性能区别

 
synchronized是托管给JVM执行的,而lock是java写的控制锁的代码。在Java1.5中,synchronize是性能低效的。因为 这是一个重量级操作,需要调用操作接口,导致有可能加锁消耗的系统时间比加锁以外的操作还多。相比之下使用Java提供的Lock对象,性能更高一些。但 是到了Java1.6,发生了变化。synchronize在语义上很清晰,可以进行很多优化,有适应自旋,锁消除,锁粗化,轻量级锁,偏向锁等等。导致 在Java1.6上synchronize的性能并不比Lock差。

 

关于性能调优的问题,java编程思想的作者经过测试这样写到:很明显,使用Lock通常会比synchronized要高效许多,而且synchronized的开销看起来变化范围太大,而Lock相对一致。这是否意味着你永远都不应该使用synchronized关键字呢?这里有两个元素需要考虑:首先在测试代码中,互斥的方法体是非常小的。通常这是一个很好的习惯--只互斥那些必须互斥的部分。但是,在实际中,被互斥部分可能会比上面实例中的那些大许多,因此在这些方法体中花费的时间的百分比可能会明显大于进入或退出互斥的开销,这样就泯灭了提高互斥速度带来的所有好处。当然,唯一了解这一点的方式是--当你对性能调优时,应该立即--尝试各种不同的方法观察他们造成的影响。

而且和明显,synchronized关键字产生的代码,于lock锁需的“加锁-try/finally-解锁”方法所产生的代码相比,可读性提高了很多。代码被阅读的次数远多于被编写的次数。因此,以synchronized关键字入手,只有在性能调优的时候才替换为Lock对象这种做法,是具有实际意义的。

      

三、synchronized和lock机制区别


(1)synchronized原始采用的是CPU悲观锁机制,即线程获得的是独占锁
独占锁意味着其 他线程只能依靠阻塞来等待线程释放锁。
(2)Lock用的是乐观锁方式
所谓乐观锁就是,每次不加锁而是假设没有冲突而去完成某项操作,如果因为冲突失败就重试,直到成功为止。乐观锁实现的机制就 是CAS操作(Compare and Swap)。

猜你喜欢

转载自blog.csdn.net/qq_37891064/article/details/79767103