• AQS实战-自定义同步组件TwinsLock


    背景

    设计一个同步工具:该工具在同一时刻,只允许至多两个线程同时访问,超过两个线程的访问将被阻塞,我们将这个同步工具命名为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表示当前已经有两个线程获取了同步资源,此时再有其他线程对同步状态进行获取,该线程只能被阻塞。在同步状态变更时,需要使用compareAndSet(int expect,int update)方法做原子性保障

    组合自定义同步器

    自定义同步组件通过组合自定义同步器来完成同步功能,一般情况下自定义同步器会被定义为自定义同步组件的内部类

    代码实现如下所示:

    public class TwinsLock implements Lock {
    
        private final Sync sync = new Sync(2);
    
        private static final class Sync extends AbstractQueuedSynchronizer {
            Sync(int count) {
                if (count <= 0) {
                    throw new IllegalArgumentException("count must large than zero");
                }
                setState(count);
            }
    
            @Override
            public int tryAcquireShared(int reduceCount) {
                for (; ; ) {
                    int current = getState();
                    int newCount = current - reduceCount;
                    if (newCount < 0 || compareAndSetState(current, newCount)) {
                        return newCount;
                    }
                }
            }
    
            @Override
            public boolean tryReleaseShared(int returnCount) {
                for (; ; ) {
                    int current = getState();
                    int newCount = current + returnCount;
                    if (compareAndSetState(current, newCount)) {
                        return true;
                    }
                }
            }
        }
    
        @Override
        public void lock() {
            sync.acquireShared(1);
        }
    
        @Override
        public void unlock() {
            sync.releaseShared(1);
        }
    
        @Override
        public void lockInterruptibly() throws InterruptedException {
    
        }
    
        @Override
        public boolean tryLock() {
            return false;
        }
    
        @Override
        public boolean tryLock(long time, TimeUnit unit) throws InterruptedException {
            return false;
        }
    
    
        @Override
        public Condition newCondition() {
            return null;
        }
    }
    
    • 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
    • 52
    • 53
    • 54
    • 55
    • 56
    • 57
    • 58
    • 59
    • 60
    • 61
    • 62
    • 63
    • 64
    • 65
    • 66

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

    TwinsLock同时包含了一个自定义同步器Sync,而该同步器面向线程访问和同步状态控制。

    以共享式获取同步状态为例:同步器会先计算出获取后的同步状态,然后通过CAS确保状态的正确设置,当tryAcquireShared(int reduceCount)方法返回值大于等于0时,当前线程才能获取同步状态,对于上层的TwinsLock而言,则表示当前线程获得了锁

    同步器作为一个桥梁,连接线程访问以及同步状态控制等底层技术。

    测试

    测试验证TwinsLock是否能按照预期工作。在测试用例中定义了工作线程Worker,该线程在执行过程中获取锁,当获取锁之后使当前线程睡眠1秒(并不释放锁),随后打印当前线程名称,最后再次睡眠1秒并释放锁,测试用例代码如下:

    public class TwinsLockTest {
        @Test
        public void test(){
            final Lock lock = new TwinsLock();
            class Worker extends Thread{
                @Override
                public void run(){
                    while(true){
                        lock.lock();
                        System.out.println("current thread name"+Thread.currentThread().getName()+"拿到了锁");
                        try {
                            Thread.sleep(1000);
                            System.out.println("current thread name:"+Thread.currentThread().getName());
                            Thread.sleep(1000);
                        } catch (InterruptedException e) {
                            e.printStackTrace();
                        } finally {
                            lock.unlock();
                            System.out.println("current thread name"+Thread.currentThread().getName()+"释放了锁");
                        }
                    }
                }
            }
    
            //启动10个线程
            for(int i=0;i<10;i++){
                Worker worker = new Worker();
                worker.setDaemon(true);
                worker.start();
            }
    
            try {
                Thread.sleep(10000);
            } catch (InterruptedException e) {
                e.printStackTrace();
            }
            
        }
    }
    
    • 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
    current thread nameThread-0拿到了锁
    current thread nameThread-1拿到了锁
    current thread name:Thread-1
    current thread name:Thread-0
    current thread nameThread-0释放了锁
    current thread nameThread-1释放了锁
    current thread nameThread-2拿到了锁
    current thread nameThread-3拿到了锁
    current thread name:Thread-2
    current thread name:Thread-3
    current thread nameThread-2释放了锁
    current thread nameThread-3释放了锁
    current thread nameThread-5拿到了锁
    current thread nameThread-4拿到了锁
    current thread name:Thread-5
    current thread name:Thread-4
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11
    • 12
    • 13
    • 14
    • 15
    • 16

    运行测试用例,发现线程成对输出,并且在同一时刻只有两个线程能够获取到锁,测试成功。

  • 相关阅读:
    循环结构--for循环
    OpenCV图像特征提取学习四,SIFT特征检测算法
    java健康饮食信息管理系统计算机毕业设计MyBatis+系统+LW文档+源码+调试部署
    AndroidStudio中虚拟机(AVD)无法启动,出现unable to locate adb错误
    Win10添加、删除鼠标右击的选项(快捷方法)
    arm day2(9.15)数据操作指令,跳转指令,特殊功能寄存器指令,+XMind
    AIGC玩转卡通化技术实践
    是时候和 Confluence 说再见了
    MediaPlayer_Analyze-2-JNI
    浅析影响银行小微信贷业务精细化发展的六大要素
  • 原文地址:https://blog.csdn.net/wozaibohaibian/article/details/126209038