Lock详解
简介
java.util.concurrent.locks包下常用的类与接口(lock是jdk 1.5后新增的)
Lock 和ReadWriteLock 是两大锁的根接口:
- Lock代表实现类是
ReentrantLock (可重入锁) - ReadWriteLock(读写锁)的代表实现类是
ReentrantReadWriteLock
同步实现原理
lock只能被一个线程获取,当一个线程执行lock.lock()后其他线程再尝试获取会进入lock对象中的等待队列,从而达到同步效果,ReenTrantLock 的实现是一种自旋锁,通过循环调用CAS操作来实现加锁。它的性能比较好也是因为避免了使线程进入内核态的阻塞状态。
- lock的存储结构:一个int类型状态值(用于锁的状态变更),一个双向链表(用于存储等待中的线程)
- lock获取锁的过程:本质上是通过CAS(两次)来修改锁状态值,如果当场没获取到,会将该线程放在线程等待链表中自旋+CAS尝试获取锁。应该不是所有线程都自旋吧???(只有head才会自旋尝试加锁,并且自旋执行也不是无限自旋,对于head节点来说只会尝试3次获取锁为什么,而对于非head节点来说只会尝试获取一次为什么,获取失败则lcoksupport.park ,而CAS仅仅是针对尝试获取锁)
- lock释放锁的过程:CAS修改状态值,调整等待链表。因为无竞争所以没有使用CAS。
Lock接口实现类的使用
Lock接口有6个方法:
void lock()
void unlock()
void lockInterruptibly()
Condition newCondition()
boolean tryLock()
boolean tryLock(long time, TimeUnit unit)
获取释放锁
Lock lock=new ReentrantLock();
lock.lock();
try{
}catch(Exception ex){
}finally{
lock.unlock();
}
Synchronized与Lock
构成
synchronized 是关键字属于JVM层面
monitorenter (底层通过monitor对象来完成,其实wait/notify 等方法也依赖于monitor对象只有在同步块或方法中调用wait/notify等方法)monitorexit
Lock 是具体类(java.util.concurrent.Locks.lock)是api层面的锁
使用方法
synchronized 不需要手动释放锁,当synchronized代码执行完后系统会自动让线程释放对锁的占用ReentrantLock 则需要手动释放锁若没有主动释放锁,就有可能导致出现死锁现象。需要Lock()和unlock()方法配合try/finally语句块来完成
等待是否可中断
synchronized 不可中断,除非抛出异常或者正常运行完成ReentrantLock 可中断,
- 设置超时方法tryLock(long timeout,TimeUnit unit)
- lockInterruptibly()放代码块中,调用interrupt()方法可中断
加锁是否公平
synchronized 非公平锁ReentrantLock 两者都可以,默认非公平锁,构造方法可以传入boolean值,true为公平锁,false为非公平锁
锁绑定多个条件Condition
synchronized 没有,要么随机唤醒一个线程要么唤醒全部线程ReentrantLock 用来实现分组唤醒需要的线程们,可以实现精确唤醒
synchronized的缺陷
效率低 :锁的释放情况少,只有代码执行完毕或者异常结束才会释放锁;试图获取锁的时候不能设定超时,不能中断一个正在使用锁的线程,相对而言,Lock可以中断和设置超时不够灵活 :加锁和释放的时机单一,每个锁仅有一个单一的条件(某个对象),相对而言,读写锁更加灵活无法知道是否成功获得锁 ,相对而言,Lock可以拿到状态,如果成功获取锁,…,如果获取失败,…
Lock解决相应问题
Lock类这里不做过多解释,主要看里面的4个方法:
lock() : 加锁unlock() : 解锁tryLock() : 尝试获取锁,返回一个boolean值tryLock(long,TimeUtil) : 尝试获取锁,可以设置超时
Synchronized只有锁只与一个条件(是否获取锁)相关联,不灵活,后来Condition与Lock的结合 解决了这个问题。
多线程竞争一个锁时,其余未得到锁的线程只能不停的尝试获得锁,而不能中断。高并发的情况下会导致性能下降。ReentrantLock的lockInterruptibly()方法可以优先考虑响应中断。 一个线程等待时间过长,它可以中断自己,然后ReentrantLock响应这个中断,不再让这个线程继续等待。有了这个机制,使用ReentrantLock时就不会像synchronized那样产生死锁了。
|