目录
一. 初识Lock与AbstractQueuedSynchronizer(AQS)
1. Lock简介
2. Lock常用API
3. 初识AQS(队列同步器)
4. AQS的模板模式
5. AQS详解
二. ReentrantLock(独占式重入锁)
1. 重入的实现
2. 公平锁OR非公平锁
三. 可重入读写锁---ReentrantReadWriteLock详解
1. 写锁---WriteLock(独占锁)
2. ReadLock---读锁(共享式锁)
3. 缓存的实现应用读写锁
4. 锁降级
四. Condition接口的await、signal机制
1. 与内建锁wait、notify的区别
2. 等待队列
3. 应用
在Lock接口出现之前,java程序主要靠synchronized关键字实现锁的功能,而在JDK5之后,并发包中增加了lock接口,他提供了与synchronized一样的锁功能。
lock在juc包下。(java.util.concurrent)
与内建锁不同的是,内建锁是隐式的加减锁,而lock是显式的进行锁的获取和消除。
通常使用lock的情况如下:
Lock lock = new ReentrantLock();
lock.lock();
try{
......
}finally {
lock.unlock();
}
lock体系拥有可中断的获取锁、超时获取锁以及共享锁等内建锁不具备的特性。
常用API:
同步器是用来构建锁以及其他同步组件的基础框架,它的实现主要是依赖一个int状态变量以及通过一个FIFO队列共同构成同步队列。它的子类必须重写AQS的protected修饰的用来改变同步状态的方法,其它方法主要是实现了排队与阻塞机制。int状态的更新使用getState(),setState()以及compareAndSetState()方法。
同步器是实现锁的关键,在锁的实现中聚合同步器,利用同步器实现锁的语义。可以这样理解二者的关系:
lock:面向使用者,定义了使用者与锁交互的接口。
AQS:面向锁的实现者,屏蔽了同步状态的管理,线程排队,线程等待与唤醒等等底层操作。
AQS使用模板方法模式,将一些与状态相关的核心方法开放给子类重写,而后AQS会使用子类重写的关于状态的方法进行线程的排队,阻塞以及唤醒等操作。
例:自己实现简易的Lock锁
package www.like.java;
import com.sun.corba.se.impl.orbutil.concurrent.Sync;
import java.sql.Time;
import java.util.concurrent.TimeUnit;
import java.util.concurrent.locks.AbstractQueuedSynchronizer;
import java.util.concurrent.locks.Condition;
import java.util.concurrent.locks.Lock;
//自定义锁
class Mutex implements Lock{
private Sync sync = new Sync();
//自定义同步器
static class Sync extends AbstractQueuedSynchronizer {
@Override
protected boolean tryAcquire(int arg) {
//0表示无锁状态,更新为1表示当前线程拿到锁
if (arg != 1){
throw new RuntimeException("arg参数不为1");
}
//将0状态改为1状态
if (compareAndSetState(0,1)){
//此时线程成功获取同步状态,将当前线程ID扔进去
setExclusiveOwnerThread(Thread.currentThread());
return true;
}
return false;
}
@Override
protected boolean tryRelease(int arg) {
if(getState() == 0){
throw new IllegalMonitorStateException();
}
//当前线程置空
setExclusiveOwnerThread(null);
setState(0);
return true;
}
@Override
protected boolean isHeldExclusively() {
return getState() == 1;
}
}
//-------------------------------------------------------------------
@Override
public void lock() {
sync.acquire(1);
}
@Override
public void lockInterruptibly() throws InterruptedException {
sync.acquireInterruptibly(1);
}
@Override
public boolean tryLock() {
return sync.tryAcquire(1);
}
@Override
public boolean tryLock(long time, TimeUnit unit) throws InterruptedException {
return sync.tryAcquireNanos(1,time);
}
@Override
public void unlock() {
sync.release(1);
}
@Override
public Condition newCondition() {
return null;
}
//------------------------------------------------------------------------
}
public class Test {
public static void main(String[] args) {
Lock lock = new Mutex();
for (int i = 0; i < 10; i++){
Thread thread = new Thread(()->{
lock.lock();
try {
Thread.sleep(5000);
} catch (InterruptedException e) {
e.printStackTrace();
}finally {
lock.unlock();
}
});
thread.start();
}
}
}
结果表明,同一时间只有一个线程获取到锁,这也印证了我们的锁实现是正确的。
(如果有些方法不明白的,会在下面进行讲解)
在同步组件(锁)中,AQS是最核心的部分。同步组件依赖AQS提供的模板方法来实现同步语义。
AQS核心组成:同步队列、独占锁的获取与释放、共享锁的获取与释放、可中断锁、超时锁。
同步队列:
在AQS内部有一个静态内部类Node,这是同步队列中每个具体的节点。
节点中有如下属性:
节点状态值如下:
AQS同步队列采用带有头尾节点的双向链表。
独占式锁:
共享式锁:
重入:表示能够对共享资源重复加锁,即当前线程再次获取锁时不会被阻塞。
我们来看一下源码:
c代表的是获取当前同步状态。如果当前同步状态不为0,表示此时同步状态已被线程获取,就执行else语句,else语句是判断持有同步状态的线程是否为当前线程,如果是,同步状态再次加1并返回true,表示持有线程重入同步块。
释放过程:
当且仅当持有线程为当前线程并且同步状态减为0时,表示锁被正确释放,否则调用setState()将减1后的状态设置回去。
ReentrantLock支持两种锁,公平锁和非公平锁。那么,什么是公平性呢?如果锁的获取顺序符合时间上的顺序,即等待时间最长的线程最先获取锁。那么就是公平锁,反之,即为非公平锁。ReentrantLock默认使用非公平锁。
如果要想使用公平锁,需要调用ReentrantLock的有参构造,传入true,获取内置的公平锁。
对比:
读写锁:允许同一时刻被多个读线程访问,但是在写线程访问时,读线程和其他写线程都会被阻塞。
写线程能够获取到锁的条件:没有任何读写线程拿到锁。
注意:读线程与写线程互斥,写线程与读线程和写线程都互斥。
在同一时刻写锁不能被多个线程获取,很显然,它是独占锁,而实现锁的同步语义是通过重写AQS的tryAcquire()方法实现的,源码及分析如下:
protected final boolean tryAcquire(int acquires) {
/*
* Walkthrough:
* 1. If read count nonzero or write count nonzero
* and owner is a different thread, fail.
* 2. If count would saturate(饱和), fail. (This can only
* happen if count is already nonzero.)
* 3. Otherwise, this thread is eligible(合格的) for lock if
* it is either a reentrant acquire or
* queue policy allows it. If so, update state
* and set owner.
*/
Thread current = Thread.currentThread();
//判断当前同步状态
int c = getState();
//判断写锁的获取次数(exclusive---独占式)
int w = exclusiveCount(c);
if (c != 0) {
// (Note: if c != 0 and w == 0 then shared count != 0)
if (w == 0 || current != getExclusiveOwnerThread())
return false;
if (w + exclusiveCount(acquires) > MAX_COUNT)
throw new Error("Maximum lock count exceeded");
// Reentrant acquire
setState(c + acquires);
return true;
}
if (writerShouldBlock() ||
!compareAndSetState(c, c + acquires))
return false;
//获取写锁成功
setExclusiveOwnerThread(current);
return true;
}
***重要:同步状态的低16位表示写锁获取次数,高16位表示读锁获取次数。***
写锁的获取逻辑:当读锁已被读线程或写锁已被其他线程获取,则写锁获取失败,否则,当前同步状态没有被任何读写线程获取,当前线程获取写锁成功并且支持重入。
源码及分析如下:
protected final int tryAcquireShared(int unused) {
/*
* Walkthrough:
* 1. If write lock held by another thread, fail.
* 2. Otherwise, this thread is eligible for
* lock wrt state, so ask if it should block
* because of queue policy. If not, try
* to grant by CASing state and updating count.
* Note that step does not check for reentrant
* acquires, which is postponed to full version
* to avoid having to check hold count in
* the more typical non-reentrant case.
* 3. If step 2 fails either because thread
* apparently not eligible or CAS fails or count
* saturated, chain to version with full retry loop.
*/
Thread current = Thread.currentThread();
int c = getState();
//如果写锁已经被获取并且获取写锁的线程不是当前线程
//线程获取读锁失败并返回-1
if (exclusiveCount(c) != 0 &&
getExclusiveOwnerThread() != current)
return -1;
int r = sharedCount(c);
if (!readerShouldBlock() &&
r < MAX_COUNT &&
//当前线程获取读锁
compareAndSetState(c, c + SHARED_UNIT)) {
//当前获取锁的次数
if (r == 0) {
firstReader = current;
firstReaderHoldCount = 1;
} else if (firstReader == current) {
firstReaderHoldCount++;
} else {
HoldCounter rh = cachedHoldCounter;
if (rh == null || rh.tid != getThreadId(current))
cachedHoldCounter = rh = readHolds.get();
else if (rh.count == 0)
readHolds.set(rh);
rh.count++;
}
return 1;
}
//CAS失败或者已经获取读锁的线程再次重入
return fullTryAcquireShared(current);
}
import java.util.HashMap;
import java.util.Map;
import java.util.concurrent.locks.Lock;
import java.util.concurrent.locks.ReentrantReadWriteLock;
/**
* 读写锁实现缓存
*/
public class Test {
static Map map = new HashMap<>();
static ReentrantReadWriteLock rwl = new ReentrantReadWriteLock();
static Lock readLock = rwl.readLock();
static Lock writeLock = rwl.writeLock();
/**
* 线程安全的根据一个key获取value
* @param key
* @return
*/
public static final Object get(String key){
readLock.lock();
try{
return map.get(key);
}finally {
readLock.unlock();
}
}
/**
* 线程安全的根据key设置value,并返回旧的value
* @param key
* @param value
* @return
*/
public static Object put(String key,Object value){
writeLock.lock();
try {
return map.put(key,value);
}finally {
writeLock.unlock();
}
}
/**
* 线程安全的清空所有value
*/
public static final void clear(){
writeLock.lock();
try {
map.clear();
}finally {
writeLock.unlock();
}
}
写锁可以降级为读锁,读锁不能升级为写锁。
等待方法:
void await() throws InterruptedException---同Object.wait(),直到被中断或唤醒。
void awaitUninterruptibly()---不响应中断,直到被唤醒。
boolean await(long time, TimeUnit unit) throws InterruptedException---同Object.wait(long timeout),多了自定义时间单位,中断、超时、被唤醒。
boolean awaitUntil(Date deadline) throws InterruptedException---支持设置截止时间。
唤醒方法:
signal():唤醒一个等待在condition上的线程,将该线程由等待队列转移到同步队列中。
signalAll():将所有等待在condition上的线程全部转移到同步队列中。
等待队列与同步队列共享Node节点类(内部静态类),等待队列是一个单向的有头尾节点的队列。
Condition实现的多生产多消费模型:
package www.like.java;
import java.util.ArrayList;
import java.util.List;
import java.util.concurrent.locks.Condition;
import java.util.concurrent.locks.Lock;
import java.util.concurrent.locks.ReentrantLock;
class Goods{
private String name;
private int count;
private int maxCount;
private Lock lock = new ReentrantLock();
//消费者等待队列
private Condition consumerCondition = lock.newCondition();
//生产者等待队列
private Condition producerCondition = lock.newCondition();
public Goods(int maxCount) {
this.maxCount = maxCount;
}
/**
* 生产者方法
* @param name 设置商品名称
*/
public void setGoods(String name){
lock.lock();
try{
//商品数量达到最大值,生产者线程进入生产者等待队列
while(count == maxCount) {
try {
System.out.println(Thread.currentThread().getName()+"还有很多商品,歇会~");
producerCondition.await();
}catch (InterruptedException e) {
e.printStackTrace();
}
}
this.name = name;
count++;
System.out.println(Thread.currentThread().getName()+"生产"+toString());
//唤醒属于消费者队列的线程
consumerCondition.signalAll();
}finally {
lock.unlock();
}
}
/**
* 消费者方法
*/
public void getGoods(){
try{
lock.lock();
while(count == 0){
System.out.println(Thread.currentThread().getName()+"还没有商品");
try {
consumerCondition.await();
}catch (InterruptedException e) {
e.printStackTrace();
}
}
count--;
System.out.println(Thread.currentThread().getName()+"消费"+toString());
//唤醒所有生产者线程
producerCondition.signalAll();
}finally {
lock.unlock();
}
}
@Override
public String toString() {
return "Goods{" +
"name='" + name + '\'' +
", count=" + count +
'}';
}
}
class Producer implements Runnable{
private Goods goods;
public Producer(Goods goods) {
this.goods = goods;
}
@Override
public void run() {
while(true){
this.goods.setGoods("computer");
}
}
}
class Consumer implements Runnable{
private Goods goods;
public Consumer(Goods goods) {
this.goods = goods;
}
@Override
public void run() {
while(true){
this.goods.getGoods();}
}
}
public class Test {
public static void main(String[] args) {
Goods goods = new Goods(100);
Producer producer = new Producer(goods);
Consumer consumer = new Consumer(goods);
List list = new ArrayList<>();
//启动10个生产者线程
for(int i = 0; i < 10; i++){
Thread thread = new Thread(producer,"生产者"+i);
list.add(thread);
}
//启动15个消费者线程
for(int i = 0; i < 15; i++){
Thread thread = new Thread(consumer,"消费者"+i);
list.add(thread);
}
//一键启动
for(Thread th:list){
th.start();
}
}
}