lock的两个子类——ReentrantLock和ReentrantReadWriteLock
ReentrantLock
相比synchronized功能更加强大
用法
调用ReentrantLock的lock方法加锁,unlock方法解锁(一般在finally中写unlock)
Object中的wait()、wait(long)等价于Condition中的await()、await(long time, TimeUnit unit)
Object中的notify()、notifyAll()等价于Condition中的singal()、signalAll()
使用Condition实现等待/通知
ReentrantLock可以实现如果关键字synchronized与wait、notify等的功能,但需要借助Condition对象,使用Condition可以有更好的灵活性——比如多路通知,即可以在一个Lock对象里面创建多个Condition(即对象监视器)实例,,编程对象可以注册在指定的Condition中,从而可以有选择性地进行线程通知,在调度线程上更有灵活性。
使用notify/notifyAll,被通知的线程是JVM选择的,而ReentrantLock结合Condition类可以实现“选择性通知”,事实上,synchronized相当于整个Lock对象中只有一个单一的Condition对象,所有的线程都注册在它一个对象身上。线程开始notifyAll的时候,需要通知所有的waiting线程,产生效率问题。
实现通知部分线程:
private Lock lock = new ReentrantLock();
private Condition cA = lock.newCondition();
private Condition cB = lock.newCondition();
...
在某wait函数中:
lock.lock();
conditionA.await();
在某notify函数中:
lock.lock();
conditionA.signalAll();
公平锁与非公平锁
公平锁:线程获取锁的顺序是按照线程加锁的顺序来分配的,类似FIFO,但只是大部分线程按照FIFO
非公平锁:未必按照FIFO,可能导致部分线程永远获取不到锁
Lock lock = new ReentrantLock(isFair)
通过设置如上的isFair判断是否为公平锁(默认情况下,ReentrantLock为非公平锁);当持有锁的时间或者请求锁的时间较长的时候,使用公平锁好一些,因为非公平锁的插队带来的效率提升此时可能不会有
非公平锁的性能优于公平锁,原因如下:
- 从线程进入了RUNNABLE状态到run状态是要比较久的时间的。而且,在一个锁释放之后,其他的线程会需要重新来获取锁。其中经历了持有锁的线程释放锁,其他线程从挂起恢复到RUNNABLE状态,其他线程请求锁,获得锁,线程执行,这一系列步骤。如果这个时候,存在一个线程直接请求锁,可能就避开挂起到恢复RUNNABLE状态的这段消耗,所以性能更优化。 相当于等待一个阻塞的进程和一个就绪的进程进入运行使用CPU的状态
- 公平锁需要多加入一个条件判断
getHoldCount()、getQueueLength()、getWaitQueueLength()的测试
-
getHoldCount()
查询当前线程保持此锁定的次数,也就是调用lock方法的次数
-
getQueueLength()
返回正等待获取此锁定的线程估计数,比如有5个线程,1个线程首先执行await()方法,调用此函数的返回值是4,说明有4个线程同时在等待lock的释放
-
getWaitQueueLength(Condition)
返回等待与此锁相关的给定条件Condition的线程估计数,比如5个线程,每个贤臣都执行了同一个Condition的await方法,则调用此函数的返回值为5
hasQueuedThread、hasQueuedThreads、hasWaiters的测试
-
hasQueuedThread(Thread)
查询指定的线程是否在等待获取此锁定
-
hasQueuedThreads()
查询是否有线程在等待获取此锁定
-
hasWaiters(Condition)
查询是否有线程在等待与此锁相关的condition条件
isFair()、isHeldByCurrentThread()、isLocked的测试
-
isFair()
判断是否为公平锁
-
isHeldByCurrentThread()
查询当前线程是否保持此锁定,一般用于unlock前的条件判断
-
isLocked()
判断此锁是否由任意线程持有
lockInterruptibly()、tryLock()、tryLock(long timeout, TimeUnit unit)的测试
-
lockInterruptibly()
如果当前线程未被中断,则获取锁定;否则抛出异常(可以利用此终止异常)
-
tryLock()
调用时锁定未被另一个线程持有的情况下,才获取此锁定
-
tryLock(long timeout, TimeUnit unit)
如果锁定在给定等待时间内没有被另一个线程保持,且当前线程未被中断,则获取该锁定。
ReentrantReadWriteLock类
类ReentrantLock具有完全互斥排他的效果,即同一时间只有一个线程在执行ReentrantLock.lock方法后面的任务,虽然安全,但效率低。
而ReentrantReadWriteLock类可以在某些不需要操作实例变量的方法中,使用读写锁来提高方法的代码运行速度。
读写锁有两个锁:读——共享锁,写——排它锁。
使用
ReentrantReadWriteLock lock = new ReentrantReadWriteLock();
lock.readLock.lock()
则可以有多个线程共享读
如果把readLock改成writeLock,则将会导致读写、写写互斥