前面章节(【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