0%

Java 并发 - AQS:自定义同步工具

1.前言

通过前面的AQS的基本原理了解:之后:Java 并发 - AQS:框架分析,我了解了大概的AQS的一整个流程,也明白了大部分的同步工具都是基于AQS来实现的,好像比较重要的就是重写tryAcquire 和 tryRelease 两个方法而已,那么我借鉴了其他同步工具的写法,试了试尝试自己实现一个基于AQS的同步工具,看看能不能正常跑起来。

以下是基于独占式的写法,并不是共享式的,所以实现的也是tryAcquiretryRelease。主要想做的事情就是同一个时刻只能让一个线程一直抱有资源做一件事情,直到这件事情做完了之后,才可以让其他线程去做一些事情,这不就是同步的概念嘛!

2.自定义工具

实现自定义同步器需要实现tryAcquiretryRelease,这里再重新提及一下state状态的意思,他代表当前锁的状态,0代表没有被占用,大于 0 代表有线程持有当前锁。

具体Demo如下:

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
public class LockBaseAQS {

// 实现内部类继承AQS
private static class Sync extends AbstractQueuedSynchronizer {
// 重写tryAcquire方法
// 返回1则代表获取锁成功,返回0则代表获取锁失败
@Override
protected boolean tryAcquire (int arg){
// 省略了this,AQS中state默认为0
return compareAndSetState(0, 1);
}

// 重写tryRelease方法
@Override
protected boolean tryRelease (int arg){
// 将状态设置为0
setState(0);
// 并返回1,代表该锁没有被任何线程持有
return true;
}

// 该线程是否正在独占资源
@Override
protected boolean isHeldExclusively() {
// 如果state为1,代表资源正在被线程占有,否则没有
return getState() == 1;
}
}

private Sync sync = new Sync();

// 实现给用户上锁的api:lock
public void lock() {
sync.acquire(1);
}

// 实现给用户解锁的api:unLock
public void unLock() {
sync.release(1);
}
}

这里我想还是把acquire和release的源码放出来,比较容易理解:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
public final void acquire(int arg) {
// 上面实现了这个tryAcquire!
if (!tryAcquire(arg) &&
acquireQueued(addWaiter(Node.EXCLUSIVE), arg))
selfInterrupt();
}

public final boolean release(int arg) {
// 我们上面就实现了这个tryRelease!
if (tryRelease(arg)) {
Node h = head;
if (h != null && h.waitStatus != 0)
unparkSuccessor(h);
return true;
}
return false;
}

3.测试

编写了一个测试Demo如下,我们创建两个线程,然后启动这两个线程,让两个线程各自从0打印10000000,看结果会不会统一。

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
package MyLock;

public class Test {

static int count = 0;
static LockBaseAQS myLock = new LockBaseAQS();

public static void main(String[] args) throws InterruptedException {
Runnable runnable = new Runnable() {
@Override
public void run () {
try {
// 进行上锁,同一时间,只允许一个线程创建累加计数
// 这块代码同一时间只能有一个线程进来(获取到锁的线程)
// 其他的线程在lock()方法上阻塞,等待获取到锁,再进来
myLock.lock();
for (int i = 0; i < 10000000; i++) {
count++;
}
} catch (Exception e) {
e.printStackTrace();
} finally {
// 解锁
myLock.unLock();
}

}
};
Thread thread1 = new Thread(runnable);
Thread thread2 = new Thread(runnable);
thread1.start();
thread2.start();
thread1.join();
thread2.join();
System.out.println(count);
}
}

这样一来每次都会打印出:

1
20000000

假如将同步方法给屏蔽了之后,会发现每次都打印出不一样的数字!