天天看点

Lock、Condition

因为虚拟机的优化技术,使用synchronized锁带来的性能开销越来越低,也不再是之前所谓的重量级锁。但是其本身的加锁机制限制了在灵活性和细粒度方面的拓展,例如在阻塞等待获取锁时不能响应中断(当然可以添加额外的代码实现响应中断),并且synchronized维持的是一种排他锁,在加锁的策略中没有考虑任务的类型。

在JDK1.5之后引入locks包,作为对加锁策略一个补充,synchronized使用的锁是依赖于对象上,lock则是创建出一个锁来使用。

Lock在加锁方面的同步语义与synchronized相似,提供如下函数:

1、lock,类似与synchronized的阻塞等待获取锁,不响应中断

2、lockInterruptibly,相比较与lock,在阻塞等待获取锁过程中可以响应中断

3、直接返回锁的获取结果的tryLock

4、可以等待一定时间的tryLock(long time,TimeUnit unit),因为其可定时功能,所以可用于轮询

5、newCondition,返回Condition对象,可用于完善wait、notify的功能

6、unlock,lock的锁属于代码层次控制,不会像synchronized由虚拟机监控在代码块执行结束自动释放,所以一般放在finally中进行释放

示例:

public class t{
	public static void main(String[] args){
		final ArrayList<Object> arr=new ArrayList<Object>();
		final int len=3;
		final Lock lock=new ReentrantLock();
		final Condition in_condition=lock.newCondition();//两种不同的condition来分辨不同类型的任务
		final Condition out_condition=lock.newCondition();
		for(int i=0;i<10;i++){
			new Thread(){   //写入数组
				public void run(){
					lock.lock();
					try{
						while(arr.size()==len){  //满则等待
							in_condition.await();
						}
						arr.add(new Object());
						System.out.println("arr.size()= "+arr.size());
						out_condition.signal();  //唤醒移出线程
					}catch(InterruptedException e){
						e.printStackTrace();
					}finally{
						lock.unlock();  //锁的释放
					}
					
				}
			}.start();
			new Thread(){	//移出元素
				public void run(){
					lock.lock();
					try{
						while(arr.size()==0){  //空则等待
							out_condition.await();
						}
						arr.remove(arr.size()-1);
						System.out.println("arr.size()= "+arr.size());
						in_condition.signal();    //唤醒写入线程
					}catch(InterruptedException e){
						e.printStackTrace();
					}finally{
						lock.unlock();
					}
					
				}
			}.start();
		}
		
	}
}
           

利用condition的await,signal方法替代wait,notify来区分对待不同类型的任务。

ReadWriteLock接口提供两个函数readLock、writeLock,返回Lock对象,ReentrantReadWriteLock实现ReadWriteLock接口,在readLock、writeLock中分别返回ReentrantReadWriteLock的内部类ReentrantReadWriteLock.WriteLock对象和ReentrantReadWriteLock.ReadLock对象。

读写锁的特点很明显,读锁与写锁、写锁与写锁互斥,读锁与读锁不互斥。因为在任务的并发执行中,可能存在的许多对共享数据的访问,而只有少部分对数据进行修改。

示例:

public class t{
	public static void main(String[] args){
		Object[] obj=new Object[3];
		final LockArray<Object> arr=new LockArray<Object>(obj);		
		for(int i=0;i<10;i++){
			new Thread(){   
				public void run(){
					arr.add(new Object());//添加元素线程
				}
			}.start();
			
			new Thread(){   
				public void run(){
					arr.remove();//移除元素线程
				}
			}.start();
		}
	}
}
class LockArray<T>{
	private final ReentrantReadWriteLock lock=new ReentrantReadWriteLock();	
	private final Lock w_lock=lock.writeLock();
	private final Lock r_lock=lock.readLock();
	private final Condition add_condition=w_lock.newCondition();//add添加元素
	private final Condition rem_condition=w_lock.newCondition();//remove移除元素
	private final T[] array;  //基数组
	private int index=0;
	public LockArray(T[] array){
		this.array=array;
	}
	public void add(T t){
		try{
			while(!w_lock.tryLock(1L,TimeUnit.SECONDS)){} //轮询等待获取锁
			while(index==array.length){
				System.out.println("full waiting");
				add_condition.await();//数组满则等待通知
			}
			array[index++]=t;
			System.out.println("after add array size = "+index);
			rem_condition.signal(); //通知rem_condition移除元素线程
		}catch(InterruptedException e){
			e.printStackTrace();
		}finally{
			w_lock.unlock(); 
		}
	}
	
	public T remove(){
		T t=null;
		try{
			while(!w_lock.tryLock(1L,TimeUnit.SECONDS)){}//轮询等待获取锁
			while(index==0){
				System.out.println("empty waiting");
				rem_condition.await();//数组为空,等待通知
			}
			t=array[--index];
			System.out.println("after remove array size = "+index);
			add_condition.signal();//通知add_condition添加元素线程
		}catch(InterruptedException e){
			e.printStackTrace();
		}finally{
			w_lock.unlock(); 
		}
		return t;
	}
}
           

总结

API层次的lock加锁机制有着与synchronized相同的同步语义,提供了响应中断的阻塞等待机制,将加锁的范围由固定的代码块转为细粒度更高的lock锁对象本身上(JVM的优化已经使得synchronized已经成为轻量级锁,与lock的加锁性能开销几乎相同),而且在灵活性方面做出了补充,使用condition和读写锁来区别不同类型的任务。但是在锁的释放操作上需要手动释放,否则可能发生灾难。

继续阅读