【Java多线程】自定义同步组件

  前面章节(【Java多线程】队列同步器AQS(十一))中,对同步器AbstractQueuedSynchronized进行了实现层面的分析,本章通过编写一个自定义同步组件来加深对同步器的理解

同步组件要求

  设计一个同步工具:该工具在同一时刻,只允许至多两个线程同时访问,这里显然是共享式访问将被组赛,我们将这个同步工具命名为TwinsLock。

  首先,确定访问模式。TwinsLock能够在同一个时刻支持多个线程的访问,这显然是共享式访问,因此,需要使用同步器提供的 acquireShared(int args) 方法等和Shared相关的方法,这要求TwinsLock必须重写 tryAcquireShared(int args) 方法和 tryReleaseShared int args),这样才能保证同步器的共享式同步状态的获取与释放方法得以执行。

  其次,定义资源数。TwinsLock子啊同一时刻允许至多两个线程同时访问,表面同步资源数为2,这样可以设置初始状态 status 为2,当一个线程进行获取,status减1,该线程释放,则 status加1,状态的合法范围为0、1 和 2,其中0表示当前已经有两个线程获取了同步资源,此时再有其他线程对同步状态进行获取,该线程只能被阻塞。在同步状态变更时,需要使用 compareAndSetState(int expect, int update) 方法作为原子性保障

  最后,组合自定义同步器。前面的章节提到,自定义同步组件通过组合自定义同步器来完成功能,一般情况下自定义同步器会被定义为自定义同步组件的内部类。

TwinsLock代码

  TwinsLock代码如下:

 1 public class TwinsLock implements Lock {
 2     private final Sync sync = new Sync(2);
 3 
 4     private static final class Sync extends AbstractQueuedSynchronizer {
 5         // 构造方法
 6         Sync(int count) {
 7             if(count <= 0){
 8                 throw new IllegalArgumentException("count must large than zero.");
 9             }
10             setState(count);
11         }
12 
13         // 共享式获取同步状态
14         @Override
15         protected int tryAcquireShared(int reduceCount) {
16             for(;;){
17                 int current = getState();
18                 int newCount = current - reduceCount;
19                 if(newCount < 0 || compareAndSetState(current, newCount)){
20                     return newCount;
21                 }
22             }
23         }
24 
25         // 共享式释放同步状态
26         @Override
27         protected boolean tryReleaseShared(int returnCount) {
28             for(;;){
29                 int current = getState();
30                 int newCount = current + returnCount;
31                 if(compareAndSetState(current, newCount)){
32                     return true;
33                 }
34             }
35         }
36 
37         // 返回一个Condition,每个condition都包含了一个condition队列
38         Condition newCondition() {
39             return new ConditionObject();
40         }
41 
42     }
43 
44     @Override
45     public void lock(){
46         sync.acquireShared(1);
47     }
48 
49     @Override
50     public void unlock(){
51         sync.releaseShared(1);
52     }
53 
54     @Override
55     public void lockInterruptibly() throws InterruptedException {
56         sync.acquireInterruptibly(1);
57     }
58 
59     @Override
60     public boolean tryLock() {
61         return sync.tryAcquireShared(1) > 0;
62     }
63 
64     @Override
65     public boolean tryLock(long time, TimeUnit unit) throws InterruptedException {
66         return false;
67     }
68 
69     // 返回一个Condition,每个condition都包含了一个condition队列
70     @Override
71     public Condition newCondition() {
72         return sync.newCondition();
73     }
74 
75 }

  在上述示例中,TwinsLock实现了Lock接口,提供了面向使用者的接口,使用者调用lock()方法获取锁,随后调用unlock()方法释放锁,而同一时刻只能有两个线程同时获取到锁。

  TwinsLock同时包含了一个自定义同步器Sync,而该同步器面向线程访问和同步状态控制。以共享式获取同步状态为例:同步器会先计算出获取后的同步状态,然后通过CAS确保状态的正确设置,当 tryAcquireShared(int reduceCount) 方法返回值大于等于0时,当前线程才获取同步状态,对于上层的TwinsLock而言,则表示当前线程获得了锁。

自定义同步组件测试

  在测试用例中,定义了工作者线程Worker,该线程在执行过程中获取锁,当获取锁之后使当前线程睡眠1秒(并不释放锁),随后打印当前线程名称,最后再次睡眠1秒并释放锁。

 1 public class TwinsLockTest {
 2 
 3     @Test
 4     public void test() {
 5         final Lock lock = new TwinsLock();
 6         class Worker extends Thread {
 7             @Override
 8             public void run() {
 9                 while (true) {
10                     lock.lock();
11                     try {
12                         SleepUtils.second(1);
13                         System.out.println(Thread.currentThread().getName());
14                         SleepUtils.second(1);
15                     } finally {
16                         lock.unlock();
17                     }
18                 }
19             }
20         }
21 
22         //启动10个线程
23         for (int i = 0; i < 10; i++) {
24             Worker w = new Worker();
25             w.setDaemon(true);
26             w.start();
27         }
28 
29         //每隔1秒换行
30         for (int i = 0; i < 100; i++) {
31             SleepUtils.second(1);
32             System.out.println();
33         }
34     }
35 
36 
37     public static class SleepUtils{
38         public static final void second(long sec) {
39             try {
40                 TimeUnit.SECONDS.sleep(sec);
41             } catch (Exception e) {
42                 // TODO: handle exception
43             }
44         }
45     }
46 }

  运行该测试用例,可以看到线程名称成对输出,也就是在同一时刻只有两个线程能够获取到锁,这表明TwinsLock可以按照预期正确工作。

  

参考文章:

  1、《Java并发编程的艺术》

  2、https://blog.csdn.net/cold___play/article/details/104055201

原文地址:https://www.cnblogs.com/h--d/p/14562146.html