AQS原理解析

谈谈你对AQS的理解#

AQS是JUC(java.util.concurrent)中很多同步组件的构建基础,简单来讲,它内部实现主要是状态变量state和一个FIFO队列来完成,同步队列的头结点是当前获取到同步状态的结点,获取同步状态state失败的线程,会被构造成一个结点(或共享式或独占式)加入到同步队列尾部(采用自旋CAS来保证此操作的线程安全),随后线程会阻塞;释放时唤醒头结点的后继结点,使其加入对同步状态的争夺中。

AQS的内部实现#

  • state 资源状态
  • exclusiveOwnerThread 持有资源的线程
  • CLH 同步等待队列。

AQS维护一个共享资源state,内置的同步队列由一个一个的Node结点组成,每个Node结点维护一个prev引用和next引用,分别指向自己的前驱和后继结点。

AQS维护两个指针,分别指向队列头部head和尾部tail

继承自AQS的常见类#

CountDownLatch、ReentrantLock、Semaphore等

AQS的两种实现方式#

独占式&共享式, 这样方便使用者实现不同类型的同步组件,独占式如ReentrantLock,共享式如Semaphore,CountDownLatch,组合式的如ReentrantReadWriteLock。

AQS定义的以下可重写的方法:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
独占式获取同步状态,试着获取,成功返回true,反之为false
protected boolean tryAcquire(int arg)

独占式释放同步状态,等待中的其他线程此时将有机会获取到同步状态
protected boolean tryRelease(int arg)

共享式获取同步状态,返回值大于等于0,代表获取成功;反之获取失败
protected int tryAcquireShared(int arg)

共享式释放同步状态,成功为true,失败为false
protected boolean tryReleaseShared(int arg)

是否在独占模式下被线程占用
protected boolean isHeldExclusively()

独占式

1.tryAcquire方法尝试获取锁,如果成功就返回,如果不成功,走到2.

2.把当前线程和等待状态信息构造成一个Node节点,并将结点放入同步队列的尾部

3.该Node结点在队列中尝试获取同步状态,若获取不到,则阻塞结点线程,直到被前驱结点唤醒或者被中断.

以ReentrantLock为例,state初始化为0,表示未锁定状态。A线程lock()时,会调用tryAcquire()独占该锁并将state+1。此后,其他线程再tryAcquire()时就会失败,直到A线程unlock()到state=0(即释放锁)为止,其它线程才有机会获取该锁。当然,释放锁之前,A线程自己是可以重复获取此锁的(state会累加),这就是可重入的概念。但要注意,获取多少次就要释放多么次,这样才能保证state是能回到零态的

自我实现独占锁示例,待重写方法提供更新state等操作.

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
import java.util.concurrent.locks.AbstractQueuedSynchronizer;

public class MyLock {


private final Sync sync = new Sync();

public void lock() {
sync.acquire(1);
}

public void unlock() {
sync.release(1);
}

public boolean isLocked() {
return sync.isHeldExclusively();
}


private static class Sync extends AbstractQueuedSynchronizer {
@Override
protected boolean tryAcquire(int arg) {

//首先判断状态是否等于=0,如果状态==0,就将status设置为1
if (compareAndSetState(0,1)) {
//将当前线程赋值给独占模式的onwer
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;
}
}
}

共享式

CountDownLatch:
任务分为N个子线程去执行,state也初始化为N(注意N要与线程个数一致)。这N个子线程是并行执行的,每个子线程执行完后countDown()一次,state会CAS减1。等到所有子线程都执行完后(即state=0),会unpark()主调用线程,然后主调用线程就会从await()函数返回,继续后余动作。

Semaphore:
AQS通过state值来控制对共享资源访问的线程数,有线程请求同步状态成功state值减1,若超过共享资源数量获取同步状态失败,则将线程封装共享模式的Node结点加入到同步队列等待。有线程执行完任务释放同步状态后,state值会增加1,同步队列中的线程才有机会获得执行权。公平锁与非公平锁不同在于公平锁申请获取同步状态前都会先判断同步队列中释放存在Node,若有则将当前线程封装成Node结点入队,从而保证按FIFO的方式获取同步状态,而非公平锁则可以直接通过竞争获取线程执行权。

一般来说,自定义同步器要么是独占方法,要么是共享方式,他们也只需实现tryAcquire-tryRelease、tryAcquireShared-tryReleaseShared中的一种即可。但AQS也支持自定义同步器同时实现独占和共享两种方式,如ReentrantReadWriteLock。

volatile可见性、内存屏障

总结

volatile关键字具有许多特性,其中最重要的特性就是保证了用volatile修饰的变量对所有线程的可见性。利用内存屏障使得读写、读读、写写 都不能同时发生, 且不能指令重排。

可见性

当一个线程修改了变量的值,新的值会立刻同步到主内存当中。而其他线程读取这个变量的时候,也会从主内存中拉取最新的变量值。

得益于java语言的先行发生原则(happens-before)
对于一个volatile变量的写操作先行发生于后面对这个变量的读操作。

内存屏障

内存屏障(Memory Barrier)是一种CPU指令。禁止重排序,控制执行顺序,刷新缓存与获取最新主存数据。

CAS原理、缺点、AtomicInteger使用解析

cas原理

CAS(Compare and Swap),即比较并替换, 是设计并发算法时用到的一种技术。简单来说,比较和替换是使用一个期望值和一个变量的当前值进行比较,如果当前变量的值与我们期望的值相等,就使用一个新值替换当前变量的值。

CAS有三个操作数:内存值V、旧的预期值A、要修改的值B,当且仅当预期值A和内存值V相同时,将内存值修改为B并返回true,否则什么都不做并返回false。

在CAS中,比较和替换是一组原子操作,不会被外部打断,且在性能上更占有优势。

并发编程笔记-核心问题与BUG源头

并发编程核心问题

其实并发编程可以总结为三个核心问题:分工、同步、互斥。

所谓分工指的是如何高效地拆解任务并分配给线程,而同步指的是线程之间如何协作,互斥则是保证同一时刻只允许一个线程访问共享资源。Java SDK 并发包很大部分内容都是按照这三个维度组织的,例如 Fork/Join 框架就是一种分工模式,CountDownLatch 就是一种典型的同步方式,而可重入锁则是一种互斥手段。

Your browser is out-of-date!

Update your browser to view this website correctly. Update my browser now

×