lock体系
首先知道为什么Java有了synchronized实现管程了,为什么还要再造出 lock 呢?synchronized在处理死锁问题时方法非常局限,只能避免资源上锁线性化,使用 lock 有很多方法来避免死锁。
lock是juc(Java并发包)下的类
lock解决死锁的方法
lock 提供了3个API来解决死锁问题
//支持中断的API,能够响应中断,当给阻塞的线程发送中断信号时,能够唤醒它,释放曾经持有的锁。破坏不可抢占条件
void lockInterruptibly() throw InterruptedException;
//支持非阻塞获取锁,如果获取锁失败,线程不会进入阻塞状态,而是直接返回,这个线程也有机会释放它曾经持有的锁
boolean tryLock();
//支持超时,如果线程在一段时间内没有获取到锁,也不会进入阻塞状态,而是返回一个错误,这个线程也有机会释放它曾经持有的锁
boolean tryLock(long time,TimeUnit unit) throw Interruptedtion;
通常使用显示使用lock的形式
Lock lock =new ReentrantLock();
lock.lock;
try{
...
}finally{
lock.unlock();
}
synchronized同步代码块执行完成或者遇到异常是会自动释放锁的,而lock必须调用unlock()方法释放锁,因此在finally块中释放锁。
AQS(AbstractQueuedSynchronizer 抽象的队列式同步器)
概念:同步器是用来构建锁和其他同步组件的基础框架,它的实现主要依赖一个int成员变量来表示同步状态以及一个FIFO队列构成等待队列。它的子类必须重写AQS的几个protect修饰的用来改变同步状态的方法,其他方法主要是实现了排队和阻塞机制。状态更新使用getState、setState以及compareAndSetState这三种方法。
(1)抽象类
(2)队列作为数据结构:保存线程引用,线程同步状态
(3)同步器:管理线程的同步状态,来决定哪些线程会阻塞,有多少线程可以同步执行
节点的数据结构,即AQS的静态内部类Node,保存线程的引用和节点的等待状态等
同步队列是一个双向队列,AQS通过持有头尾指针管理同步队列
lock锁种类
实现原理:基于AQS+CAS+自旋,提供了多种锁。
(1)公平锁和非公平锁
public ReentrantLock(){
sync=new NonfairSync();//非公平锁,不保证申请锁的时间顺序
}
public ReentrantLock(boolean fair){
sync=fair?new FairSync():new NonFairSync();//公平锁,满足多个线程申请锁的时间顺序,来获取到锁
}
优缺点对比:
非公平锁:执行效率更高,但可能有些线程始终不能申请到锁,无法执行任务。
公平锁:执行效率更低,可以保证所有的线程都执行
(2)可重入锁
lock和synchronized类似,都可以在一个线程,多次对同一把锁进行操作
(3)读写锁(ReentrantReadWriteLock)
作用:提供了两把锁(读锁(基于AQS共享锁),写锁(AQS独占锁)),满足一些读多写少的场景,读读并发、读写(volatile读保读证线程安全,加锁保证写线程安全)/写写互斥。
Lock和synchronized对比
- 概念上:都是能保证线程安全的手段,lock提供锁对象,synchronized基于对象头来实现加锁
- 语法上:lock显示的加锁,释放锁;synchronized是内建锁(JVM内部构建的锁),隐式的加锁和释放锁。lock在使用上能更灵活
- 提供的功能:lock提供了多种获取锁的方式(获取锁、非阻塞式获取锁、可中断获取锁、超时获取锁);synchronized只有一种方式:竞争锁(竞争失败就会线程阻塞)
- 性能:同一个时间点,竞争锁的线程数量越多,synchronized性能下降就会越快(竞争失败的线程不停的在阻塞态与被唤醒态之间切换,用户态与内核态之间切换),使用lock性能更好。