Java中的锁
Lock接口
使用synchronized关键字将会隐式地获取锁,但是它将锁的获取和释放固化了,Lock却拥有锁获取与释放的可操作性,可中断的获取锁以及超时获取锁的便携性
队列同步器AQS
主要使用方式是继承:子类通过继承同步器并实现它的抽象方法来管理同步状态(getState(),setState()和compareAndSetState)
同步器是实现锁的关键,在锁的实现中聚合同步器,利用同步器实现锁的语义。锁是面对使用者的,隐藏了实现细节;同步器面对的是锁的实现者,它简化了锁的实现方式,屏蔽了同步状态管理
同步器的接口与示例
同步器的设计是基于模板方法模式的,使用者需要继承同步器并重写指定的方法,随后将同步器组合在自定义同步组件的实现中,并调用同步器提供的模板方法。
同步器提供的模板方法分为3类,独占式获取与释放同步状态,共享式获取与释放同步状态和查询同步队列中的等待线程情况
同步器的实现分析
-
同步队列
同步器依赖内部的同步队列(FIFO双向队列)来完成同步状态的管理,当前线程获取同步状态失败时,同步器会将当前线程以及等待状态等信息构造成为一个节点,并将其加入到同步队列,同时会阻塞当前线程,当同步状态释放时,会把首节点中的线程唤醒,使其再次尝试获取同步状态。
同步队列遵循FIFO,首节点时获取同步状态成功的节点,首节点在释放同步状态时,会唤醒后继节点
-
独占式同步状态获取与释放
通过调用同步器的acquire方法可以获取同步状态,该方法对中断不敏感
获取失败节点就进入同步队列,然后进入了一个自旋的过程,每个节点都在自省地观察,当条件满足,获取到了同步状态,就可以从这个自旋过程中退出
-
共享式同步状态获取与释放
共享式获取与独占式地最主要区别在于同一时刻能否由多个线程同时获取到同步状态
重入锁(ReentranLock)
该锁能够支持一个线程对资源的重复加锁,该锁还支持获取锁时的公平和非公平性选择
重进入是指任意线程在获取到锁之前能够再次获取该锁而不会被锁所阻塞
-
实现重进入
线程再次获取锁,锁需要去识别获取所得线程是否为当前占据锁的线程
锁的最终释放,锁对于获取进行计数自增,计数表示当前锁被重复获取的次数,计数自减当计数等于0时表示成功释放
-
公平与非公平获取锁的区别
如果一个锁是公平的,那么锁的获取顺序就应该符合请求的绝对时间顺序,也就是FIFO。对于非公平锁,只要CAS设置同步状态成功,则表示当前线程获取了锁
公平锁 保证了FIFO原则,代价是大量的线程切换
非公平锁可能会造成线程饥饿,但极少的线程切换,保证了更大的吞吐量
读写锁
只需要在读操作时获取读锁,写操作时获取写锁即可,当写锁被获取到时,后续的读写操作都会被阻塞
JUC提供的实现时ReentrantWriteLock,具有的特性是刚刚提到的公平性选择,重进入,以及锁降级(写锁可以降级为读锁)
读写锁的实现分析
-
读写状态的设计
读写锁同样以来自定义同步器来实现同步功能,而读写状态就是其同步器的同步状态,读写锁的自定义同步器需要在同步状态上维护多个读线程和一个写线程的状态,使得该状态的设计成为读写锁实现的关键
-
写锁的获取与释放
写锁 是一个支持重进入的排他锁,如果当前线程已经获取了写锁,则增加写状态。如果当前线程在获取写锁时,读锁已经被获取或者该线程不是已经获取写锁的线程,则当前线程进入等待状态
-
读锁的获取与释放
读锁是一个支持重进入的共享锁,它能够被多个线程同时获取,在没有其他写线程访问时,读锁总会被成功地获取
-
锁降级
锁降级是指把持住当前拥有的写锁,再获取到读锁,随后释放写锁的过程