java并发包中的AbstractQueuedSychronizer
简介
AQS: AbstractQueuedSychronizer(抽象的队列同步器)是java的J.U.C包中Lock、Semaphore、ReentrantLock等这些锁都是基于AQS框架实现的,
核心
AbstractQueuedSychronizer(抽象的队列同步器)
核心: volatile int state (资源)
FIFO(进程等待队列)
对资源(state)有两种共享方式:
exclusive独占锁: ReentrantLock
share共享锁: CountDownLatch
自定义同步器
不同的自定义同步器争用共享资源的方式也不同。自定义同步器在实现时只需要实现共享资源state的获取和释放方式即可,至于具体线程等待队列的维护(如获取资源失败入队、唤醒出队等),AQS已经在顶层实现好了。自定义同步器实现时主要实现以下几种方法:
isHeldExclusively(): 该线程是否正在独占资源
tryAcquire(int): 独占方式。尝试获取资源,成功返回true,失败返回false
tryRelease(int):独占方式,释放资源,成功返回true,失败返回false
tryAcquireShared(int):共享方式,尝试获取资源,负数表示失败,0表示成功,但是没有剩余资源,正数表示成功,且有剩余资源
tryRelaseShared(int):共享方式,尝试释放资源,如果释放后允许唤醒后续等待节点返回true,否则返回false
ReentrantLock
ReentrantLock reentrantLock = new ReentrantLock();
// 加锁
reentrantLock.lock();
// 释放锁
reentrantLock.unlock();
加锁
lock & unlock
public void lock() {
sync.lock();
}
final void lock() {
acquire(1);
}
public final void acquire(int arg) {
// tryAcquire获取到锁 直接返回
// tryAcquire没有获取到锁,acquireQueued当前线程加入等待队列
if (!tryAcquire(arg) &&
acquireQueued(addWaiter(Node.EXCLUSIVE), arg))
selfInterrupt();
}
tryAcquire
fairSync 公平锁
protected final boolean tryAcquire(int acquires) {
final Thread current = Thread.currentThread();
int c = getState();
if (c == 0) {
// 当前没有线程获取到锁
// CAS compare and set
if (!hasQueuedPredecessors() &&
compareAndSetState(0, acquires)) {
//设置当前线程获取到锁
setExclusiveOwnerThread(current);
return true;
}
}
// 可重入锁,当前线程再次获取到锁
else if (current == getExclusiveOwnerThread()) {
int nextc = c + acquires;
if (nextc < 0)
throw new Error("Maximum lock count exceeded");
//设置 state值,为当前重入次数
setState(nextc);
return true;
}
//无法获取到锁
return false;
}
acquireQueued
final boolean acquireQueued(final Node node, int arg) {
// 标记是否获取到锁资源
boolean failed = true;
try {
// 标记等待过程中是否被中断过
boolean interrupted = false;
// 自旋锁
for (;;) {
// 当前尝试获取锁的节点的前一个节点
final Node p = node.predecessor();
//如果当前进程是队列中的老二,那么便可尝试获取锁(等待第一个线程释放锁就可以获取到锁了)
if (p == head && tryAcquire(arg)) {
// 可以获取到锁,将head指向该节点
setHead(node);
p.next = null; // help GC
// 表示成功获取到锁了
failed = false;
// 返回等待过程中是否被中断过
return interrupted;
}
// 是否可以挂起,挂起后等待唤醒
if (shouldParkAfterFailedAcquire(p, node) &&
parkAndCheckInterrupt())
// 有被中断过
interrupted = true;
}
} finally {
//自旋锁没有获取到锁,可能是超时或中断,那么可以取消节点在队列中的等待
if (failed)
cancelAcquire(node);
}
}
isHeldExclusively
//该线程是否正在独占资源
protected final boolean isHeldExclusively() {
return getExclusiveOwnerThread() == Thread.currentThread();
}
class AbstractOwnableSynchronizer {
private transient Thread exclusiveOwnerThread;
//获取当前获取到锁的线程
protected final Thread getExclusiveOwnerThread() {
return exclusiveOwnerThread;
}
}
释放锁
public void unlock() {
sync.release(1);
}
public final boolean release(int arg) {
if (tryRelease(arg)) {
// 表示锁已经完全释放
Node h = head;
if (h != null && h.waitStatus != 0)
unparkSuccessor(h);
return true;
}
return false;
}
protected final boolean tryRelease(int releases) {
int c = getState() - releases;
// 当前线程是不是获取到锁的进程
if (Thread.currentThread() != getExclusiveOwnerThread())
throw new IllegalMonitorStateException();
boolean free = false;
// state等于0表示占领锁的线程已经全部(重入锁)被释放了
if (c == 0) {
free = true;
// exclusiveOwnerThread 设置为Null
setExclusiveOwnerThread(null);
}
setState(c);
return free;
}
锁
synchornized
synchornized关键字是同步锁,可以加在对象、类、方法、代码块,静态方法;synchornized是可重入的锁。
对象锁: 对象、方法、代码块
类锁:类、静态方法,
类
public Test {
public void demo() {
// 同一个jvm只有一个线程某刻可以获取到锁
synchornized(Test.class) {
// do something
}
}
}
静态方法
public Test {
// 同一个jvm只有一个线程某刻可以获取到锁
public static synchornized void demo() {
//do something
}
}
对象
public Test {
public void demo() {
// 同一个Test实例只有一个线程某刻可以获取到锁
synchornized(this) {
// do something
}
}
}
public void demo1)_ {
Test test1 = new Test()
Test test2 = new Test()
// test1与test2可以同时获得锁,因为他们获得是两把锁
test1.demo();
test2.demo()
}
方法
public Test {
// 同一个Test实例只有一个线程某刻可以获取到锁
public synchornized void demo() {
//do something
}
}
代码块
public Test {
// 同一个Test实例只有一个线程某刻可以获取到锁
public void demo(Test test) {
synchornized(test) {
//do something
}
}
}
原理
当代码块使用synchornized编译后会在代码中加入monitor监视器。
编译后的代码
monitorenter
// 代码块编译后的指令
monitorexit
当方法声明使用synchronized时,方法编译后会设置ACC_SYNCHRONIZED来标识
当方法调用时,调用指针将会检查方法的ACC_SYNCHRONIZED标识是否被设置了,如
果被设置了,执行线程将先持有monitor,然后在执行方法,最后方法完成时释放monitor。
synchronized是可重入的锁,因此当持有的锁的线程再一次获取到锁,monitor的计数器仍然会加1。
每一次释放monitor计数器会减一。
这里看起synchronized重入的实现和AQS的state思想是一致的。
死锁
两辆车在桥的两端,如果一方不让出路来,那么两辆车都过不了。这就是死锁。
生动的一个解释:
消费者:先给商品再付款
商家: 先付款再给商品
双方都不愿意让步,就会产生死锁。
活锁
两辆车在桥的两端,两方都在给对方让路,那么两辆车都过不去,这就是活锁
公平与非公平锁
公平锁
多个线程按照申请资源的顺序来获取锁,先到先得
// 公平锁
ReentrantLock lock = new ReentrantLock(true);
非公平锁
多个线程争抢资源,谁先抢到谁获得资源,非公平锁并发度高。
synchronized和ReentrantLock是非公平锁
// 非公平锁
ReentrantLock lock = new ReentrantLock();
重入与不可重入
可重入锁
同一个线程可以多次获取同一把锁。
// code show
public void demo() {
for (int i = 0; i < 10; i++) {
synchronized(this) {
// do something
}
}
}
不可重入锁
同一个线程如果要再次获得锁,必须等待之前获取到的锁释放锁。不可重入锁很容易引起死锁。(如上可重入锁的代码如果加锁代码块使用不可重入锁,就是引起死锁)。
自旋锁
一个线程尝试获取锁,获取不到,不会立即阻塞,而是采用循环的方式尝试去获取,acquireQueued方法中的for (;;)
就是在自旋。
自旋锁可以减少线程切换的上下文开销,但是如果自旋时间过长会非常耗费CPU的性能。
乐观与悲观锁
悲观锁
在操作资源前先加锁,例如数据库中的行锁,一个线程读取到准备操作资源,其他线程就只能阻塞等待了。
悲观锁认为别人在操作资源的时候会对资源进行修改,所以在它持有资源的时候,会阻塞其他线程。
乐观锁
乐观锁通过版本号来实现对资源的修改,当版本号不对则重新读取再次修改。不会阻塞其他线程。
如线程A,B同时读取到资源id = 2, data = 1, 他们都做加一操作。
则:
A: update set data = 2, version = version + 1 where id = 2 and version = 1 成功
B: update set data = 2, version = version + 1 where id = 2 and version = 1 失败
CAS: compare and set 也是乐观锁, 因为CAS没有版本号所以容易发生ABA问题。
共享锁与排他锁
共享锁
多个线程可以获取到同一把锁,同一个把锁会被多个线程获取。也就是读锁
如CountDownLatch
排他琐
一把锁只能被一个线程获取。某一个时刻只有一个线程可以获取到锁。也称独占锁。
J.U.C提供的一些锁
CyclicBarrier 栅栏
CountDownLatch 闭锁
Semaphore 信号量
ReentrantLock: 可重入锁
ReentrantReadWriteLock : 读写锁