Lock概述
- 位于java.util.concurrent.locks包内
- 主要目的是和synchronized一样,两者都是为了解决同步问题,处理资源争端而产生的技术
java.util.concurrent.locks包下常用的类
Lock
lock为一个接口,主要方法如下
lock(),tryLock(),tryLock(long time, TimeUnit unit)和lockInterruptibly()是用来获取锁。
unLock()方法用来释放锁。
newCondition()用来实现类似wait(),notify()。
lock()方法算是使用频率最高的一个方法,就是用来获取锁。如果锁被其他线程获取,则等待。因为发生异常锁也不会被释放,所以lock必须在try{}catch{}块中进行,并且在finally块中释放锁,防止死锁发生,一般使用方式如下:
Lock lock = new ReentrantLock(); lock.lock(); try{ // 处理任务 }catch(Exception e){ }finally{ lock.unlock(); //释放锁 }
tryLock()方法,尝试获取锁,如果获取成功,返回true,获取失败(被其他线程占用)则返回false,立即返回,不会等待
tryLock(long time, TimeUnit unit)和tryLock方法类似,区别在于获取锁的时候指定等待一定的时间,如果过了这个时间还没有获取锁,则返回false,一般情况下使用方式如下:
Lock lock = new ReentrantLock(); if(lock.tryLock()) { try{ //处理任务 }catch(Exception ex){ }finally{ lock.unlock(); //释放锁 } }else { //如果不能获取锁,则直接做其他事情 }
lockInterruptibly()方法获取锁的时候,如果线程正在等待获取锁,则中断这个线程的等待状态。打个比方,两个线程同时通过lock.lockInterruptibly()方法获取锁时,如果线程A获取了锁,线程B只能等待,那么对线程B调用b.interrupt()方法能够终端线程B的等待过程,使用方式如下
public void method() throws InterruptedException { //如果需要正确中断等待锁的过程,必须将锁放在try外面,然后将异常抛出 lock.lockInterruptibly(); try { //..... } finally { lock.unlock(); } }
注意:当一个线程获取锁之后,是不会被interrupt()方法中断,只能中断阻塞过程中的线程,所以当通过lockInterruptibly()方法获取某个锁的时候,如果不能获取到,只有在等待的情况下,才可以响应中断
而使用synchronized修饰的话,当一个线程处理等待获取锁的状态,是无法被中断的,只有一只等待下去
ReentrantLock
为可重入锁。可重入的意义在于持有锁的线程可以继续持有,并且要释放对等的次数后才真正释放该锁。意味着线程可以进入它已经拥有的锁的同步代码块儿。
lock一般声明为成员变量,局部变量的话,属于每次方法调用产生的实例,调用lock()方法自然获取的是不同的锁
- 先new一个实例
static ReentrantLock r=new ReentrantLock();
- 加锁
r.lock()或r.lockInterruptibly(); 此处不同,后者可被打断。当a线程lock后,b线程阻塞,此时如果是lockInterruptibly,
那么在调用b.interrupt()之后,b线程退出阻塞,并放弃对资源的争抢,进入catch块。(如果使用后者,必须throw interruptable exception或catch) - 释放锁
r.unlock(); 必须做!要放在finally里面。以防止异常跳出了正常流程,导致灾难。(哪怕发生了OutofMemoryError,finally块中的语句执行也能够得到保证)
其他一些方法
isFair() // 是否公平锁
isLocked() // 是否被任何线程获取了
isHeldByCurrentThread() // 是否被当前线程获取了
hasQueuedThreads() // 是否有线程在等待该锁
ReadWriteLock
也是一个接口,顾名思义为读写锁,声明如下
将读写分离,变成了两把锁,从而形成读写不互斥
ReentrantReadWriteLock
是ReadWriteLock的具体实现
- 可重入读写锁(读写锁的一个实现)
ReentrantReadWriteLock lock = new ReentrantReadWriteLock() ReadLock r = lock.readLock();
WriteLock w = lock.writeLock(); - 两者都有lock,unlock方法。写写,写读互斥;读读不互斥。可以实现并发读的高效线程安全代码
Lock和synchronized的区别
其实本质上两则是一样的,只不过lock更加强大
- Lock是jdk中一组类库,synchronized是Java语言的关键字,属于语言的特性
- 提供读写锁,公平锁
- lock可以知道有没有获取锁,synchronized不行
- synchronized发生异常时,会自动释放线程占有的锁,而Lock必须主动通过unLock()来释放,不然可能造成死锁,因此使用unLock()方法需要放在finally块中
- lock可以让等待锁的线程中断,synchronized不行,需要一直等待
至于两者之间的选择,对并发量大,资源竞争激烈的场景,使用Lock下面的类库性能还是不错的,并发量小,两者差不多,synchronized足矣
《Java并发编程实践》一书给出了使用 ReentrantLock的最佳时机:
当你需要以下高级特性时,才应该使用:可定时的、可轮询的与可中断的锁获取操作,公平队列,或者非块结构的锁。否则,请使用synchronized。
还有一点,我觉得synchronized可以指定获取哪一把对象锁,这个还是挺实用的