5. java中的锁
1. Lock接口
Lock接口是在Java SE 5 出现的,Lock接口比Synchronized更为灵活,增加了中断、超时等功能。但是失去了synchronized隐式解锁的方便性,需要手动解锁,因此Lock更容易造成死锁条件。
Lock扩展性更强、synchronized更为便捷。
Lock依赖于底层的同步器实现,也就是前文说到的AQS。
2. 队列同步器
AbstractQueueSynchronized,队列同步器。用来构建锁的或者其他同步组件的基本框架,用过int表示同步状态,通过队列完成资源获取的排队工作。同步器主要是继承用的,为开发者扩展设计的,比如ReentrantLock、ReentrantWriteLock、CountDownLatch。
简化了锁的实现方式,屏蔽了同步状态管理、线程队列、等待和唤醒等底层操作。
队列同步器是基于模板方法模式的,使用者需要继承同步器来重写里面的方法
访问或者修改状态的三种方法:
- getState:获得当前同步状态
- setState:设置当前同步状态
- compareAndSetState:使用CAS设置当前状态,方法能够保证状态的原子性
同步队列
同步器依赖于内部的同步队列,如果当前线程获得同步状态失败,同步器会将当前线程以及等待状态等信息构造成一个节点,加入到同步队列中,并阻塞当前线程。当同步状态释放时,会把节点中的线程唤醒。
下图是独占锁执行过程
3. 重入锁
重入锁是指任意线程获得锁之后,能够再次获得此锁而不被阻塞
该特性的实现有两个问题:
线程获得锁而不被阻塞
锁的最终释放
锁需要识别当前线程是否为占据锁的线程,如果是则再次获取。
锁的释放是通过计数器来实现的,当计数器减到0时,表示锁已经成功释放。
4. 读写锁*
5. LockSupport工具
LockSupport类中定义了一组公共静态方法,提供了最基本的线程阻塞、线程唤醒。
import java.util.concurrent.locks.LockSupport;
public class LockSupportDemo {
public static void main(String[] args) {
for (int i=0 ; i<5 ; i++){
Thread thread = new Thread(new Runnable() {
@Override
public void run() {
int time = (int)(Math.random()*100) ;
LockSupport.parkNanos(time);
// LockSupport.park();
System.out.println(Thread.currentThread().getName()+" "+time );
}
}, "thread-" + i) ;
thread.start();
LockSupport.unpark(thread);
}
}
}
6. Condition接口
synchronized关键字和wait()、notify()、notifyAll()配合。
而Condition就是和Lock进行配合的。
Condition是由lock.newCondition()创建的。
- await == wait
- signal == notify
- signalAll == notifyAll
public Condition newCondition() {
return sync.newCondition();
}
final ConditionObject newCondition() {
return new ConditionObject();
}
ConditionObject是由AbstractQueueSynchronizer类的内部类,底层是使用的是LockSupport。
原因是 Condition的操作是要获取锁,所以作为AQS类的内部类是最合适的。
- 同步队列:获取同步状态失败的线程会进入到同步队列,同时会阻塞该线程,当同步状态释放后,会把首节点的线程唤醒,再次尝试获取同步状态。
- 等待队列:等待队列中的线程就是在Condition对象上等待的线程。
一个Condition包含一个等待队列。
- signal方法:唤醒等待队列中等待时间最长的结点(首节点),在唤醒节点之前,将节点移动到同步队列中。
- signalAll方法:相当于对等待队列中的所有结点都做一遍signal,效果就是将等待队列中的所有节点都转移动同步队列中,并唤醒这些线程。
- await:调用该方法的线程一定是获得此锁的线程,也就是同步队列的头节点,该方法会当前线程加入到等待队列,释放同步状态,并且唤醒后继结点。