• 【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

  • 相关阅读:
    hortonworks
    使用Ambari快速部署Hadoop大数据环境
    js模板引擎
    Scala中的语言特性是如何实现的2
    IOS多线程编程一:概述
    Struts框架
    总体设计
    算法介绍
    社区与关怀
    从C#的Singleton设计模式
  • 原文地址:https://www.cnblogs.com/h--d/p/14562146.html
Copyright © 2020-2023  润新知