• 多线程交替打印 [8种方式控制先后]


    前言

    线程并发问题需要合理的控制机制,如并发交替打印,可等待与唤醒,可自旋,可阻塞自己相互解锁。

    一、交替打印

    在这里插入图片描述

    二、八种线程先后控制

    1、ReentrankLock + 等待与唤醒

    // 交替打印。
    public class FooBar {
        /*
        reentrantLock + condition + 通知signal和等待await。
        ReentrantLock简介:利用CAS(原子操作/CPU指令) + AQS队列实现,支持公平和非公平锁。
        ReentrantLock和synchronized的区别,
        1-相同点:独占锁(对于临界区);可重入锁(递归场景);
        2-区别:synchronized解锁隐私,JVM层面,简单方便,应用简单的并发场景;reentrantLock手动加锁解锁,复杂灵活,适用于复杂并发场景,且API层面,可操控性强。
    
         */
    
        // 可重入锁
        ReentrantLock rk = new ReentrantLock();
        // 因该锁而阻塞的条件对象。
        private final Condition c = rk.newCondition();
        // 设置可见的标志位。
        private volatile boolean flag = true;
        private int n;
    
        public FooBar(int n) {
            this.n = n;
        }
    
        public void foo(Runnable printFoo) throws InterruptedException {
    
            for (int i = 0; i < n; i++) {
                rk.lock();
                try {
                    // printFoo.run() outputs "foo". Do not change or remove this line.
                    while (!flag) c.await();
                    printFoo.run();
                    flag = !flag;
                    c.signal();
                } finally {
                    rk.unlock();
                }
            }
        }
    
        public void bar(Runnable printBar) throws InterruptedException {
    
            for (int i = 0; i < n; i++) {
                rk.lock();
                try {
                    // printBar.run() outputs "bar". Do not change or remove this line.
                    while (flag) c.await();
                    printBar.run();
                    flag = !flag;
                    c.signal();
                } finally {
                    rk.unlock();
                }
            }
        }
    }
    
    
    • 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

    2、synchronized + 等待与唤醒

    class FooBar3 {
        /*
        idea3:synchronized + 等待与唤醒
         */
    
        // 设置可见的标志位。
        private volatile boolean flag = true;
        // 对象锁
        private final Object lock = new Object();
        private int n;
    
        public FooBar3(int n) {
            this.n = n;
        }
    
        public void foo(Runnable printFoo) throws InterruptedException {
    
            for (int i = 0; i < n; i++) {
                synchronized (lock) {
                    // printFoo.run() outputs "foo". Do not change or remove this line.
                    while (!flag) lock.wait();
                    printFoo.run();
                    flag = false;
                    lock.notifyAll();
                }
            }
        }
    
        public void bar(Runnable printBar) throws InterruptedException {
            for (int i = 0; i < n; i++) {
                synchronized (lock) {
                    // printBar.run() outputs "bar". Do not change or remove this line.
                    while (flag) lock.wait();
                    printBar.run();
                    flag = true;
                    // 唤醒
                    lock.notifyAll();
                }
            }
    
        }
    }
    
    • 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

    3、自旋+让出cpu

    class FooBar2 {
        /*
        idea2:自旋(timeout) + 让出CPU(yield)
         */
    
        // 设置可见的标志位。
        private volatile boolean flag = true;
        private int n;
    
        public FooBar2(int n) {
            this.n = n;
        }
    
        public void foo(Runnable printFoo) throws InterruptedException {
    
            for (int i = 0; i < n; ) {
                // printFoo.run() outputs "foo". Do not change or remove this line.
                if (flag) {
                    printFoo.run();
                    flag = false;
                    ++i;
                } else Thread.currentThread().yield();
            }
        }
    
        public void bar(Runnable printBar) throws InterruptedException {
            for (int i = 0; i < n; ) {
                // printBar.run() outputs "bar". Do not change or remove this line.
                if (!flag) {
                    printBar.run();
                    flag = true;
                    ++i;
                } else Thread.currentThread().yield();// 让执行态 -> 就绪态
            }
        }
    }
    
    • 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

    4、cyclicBarrier

    class FooBar4 {
        /*
        idea4:cyclicBarrier同步点等待 + 自旋
        cyclicBarrier和countdownLatch的区别:
        1-countdownLatch参与的线程职责不单一,有的在倒计时,有的在等待倒计时结束;cyclicBarrier参与的线程职责单一,都是到达一个同步点。
        2-cyclicBarrier顾名思义,是循环使用,当线程全部达到同步点时,开启下一轮同步点等待。
         */
    
        // 设置可见的标志位。
        private volatile boolean flag = true;
        // 循环栅栏。
        CyclicBarrier cb = new CyclicBarrier(2);
        private int n;
    
        public FooBar4(int n) {
            this.n = n;
        }
    
        public void foo(Runnable printFoo) throws InterruptedException {
    
            for (int i = 0; i < n; i++) {
                // printFoo.run() outputs "foo". Do not change or remove this line.
                // 空等
                while (!flag) ;
    
                printFoo.run();
                flag = false;
                // 到达同步点
                try {
                    cb.await();
                } catch (BrokenBarrierException e) {
                    e.printStackTrace();
                }
            }
        }
    
        public void bar(Runnable printBar) throws InterruptedException {
            for (int i = 0; i < n; i++) {
                // 先达到同步点。
                try {
                    cb.await();
                } catch (BrokenBarrierException e) {
                    e.printStackTrace();
                }
                // printBar.run() outputs "bar". Do not change or remove this line.
                while (flag) ;
                printBar.run();
                flag = true;
            }
    
        }
    }
    
    
    • 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

    5、semaphore

    class FooBar5 {
        /*
        idea5:信号量
    
         */
        // 信号量
        Semaphore fs = new Semaphore(1);
        Semaphore bs = new Semaphore(0);
        private int n;
    
        public FooBar5(int n) {
            this.n = n;
        }
    
        public void foo(Runnable printFoo) throws InterruptedException {
    
            for (int i = 0; i < n; i++) {
                // printFoo.run() outputs "foo". Do not change or remove this line.
                fs.acquire();
    
                printFoo.run();
    
                bs.release();
            }
        }
    
        public void bar(Runnable printBar) throws InterruptedException {
            for (int i = 0; i < n; i++) {
                // printBar.run() outputs "bar". Do not change or remove this line.
                bs.acquire();
    
                printBar.run();
    
                fs.release();
            }
        }
    }
    
    • 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

    6、synchronousQueue阻塞队列

    class FooBar6 {
        /*
        idea6:synchronousQueue阻塞队列,靠着线程循环阻塞与不阻塞其他线程来推进交替打印。
    
         */
        // 阻塞队列,不存储元素,一个put必须先等一个take,Executors.newCachedThreadPool()就使用了SynchronousQueue
        SynchronousQueue<Integer> start = new SynchronousQueue<>();
        SynchronousQueue<Integer> end = new SynchronousQueue<>();
    
        private int n;
    
        public FooBar6(int n) {
            this.n = n;
        }
    
        public void foo(Runnable printFoo) throws InterruptedException {
    
            for (int i = 0; i < n; i++) {
                // printFoo.run() outputs "foo". Do not change or remove this line.
                printFoo.run();
    
                end.take(); // 自己执行完,给bar解锁。
                start.put(1); // 然后把自己阻塞,让已经被解锁的bar来给我解锁。
            }
        }
    
        public void bar(Runnable printBar) throws InterruptedException {
            for (int i = 0; i < n; i++) {
                // printBar.run() outputs "bar". Do not change or remove this line.
                end.put(1);// 阻塞自己,直到foo给我解锁。
    
                printBar.run();
    
                start.take();// 自己执行完,给foo解锁。
            }
        }
    }
    
    
    • 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

    7、BlockingQueue

    class FooBar7 {
        /*
        idea7:阻塞队列
    
         */
        // 双阻塞队列,自我上锁 + 上锁前的互相帮助。
        // 一个由链表结构组成的双向阻塞队列。队列头部和尾部都可以添加和移除元素,多线程并发时,可以将锁的竞争最多降到一半
        private BlockingQueue<Integer> fbq = new LinkedBlockingDeque<>(1);
        private BlockingQueue<Integer> bbq = new LinkedBlockingDeque<>(1);
    
        private int n;
    
        public FooBar7(int n) {
            this.n = n;
        }
    
        public void foo(Runnable printFoo) throws InterruptedException {
    
            for (int i = 0; i < n; i++) {
                // printFoo.run() outputs "foo". Do not change or remove this line.
                fbq.put(1); // 先put,再循环put时超过容量,就阻塞了。
    
                printFoo.run();
    
                bbq.put(1); // 让 未来给我解锁的人能够take()不阻塞。
            }
        }
    
        public void bar(Runnable printBar) throws InterruptedException {
            for (int i = 0; i < n; i++) {
                // printBar.run() outputs "bar". Do not change or remove this line.
                bbq.take(); // 有人解我锁,让我能够take(),顺便循环时把自己阻塞。
    
                printBar.run();
    
                fbq.take(); // 别人帮了我,我也要帮别人take(),免得put时超过容量就阻塞。
            }
        }
    }
    
    
    • 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

    8、LockSupport

    class FooBar8 {
        /*
        idea8:LockSupport,两个静态方法,阻塞当前线程和唤醒指定线程,park()/unpark()
        LockSupport类使用了一种名为Permit(许可)的概念来做到阻塞和唤醒线程的功能,
        可以把许可看成是一种(0,1)信号量(Semaphore),但与 Semaphore 不同的是,许可的累加上限是1。
        初始时,permit为0,当调用unpark()方法时,线程的permit加1,当调用park()方法时,如果permit为0,则调用线程进入阻塞状态。
    
    
         */
        Map<String, Thread> fx = new ConcurrentHashMap<>();
        volatile boolean flag = true;
    
        private int n;
    
        public FooBar8(int n) {
            this.n = n;
        }
    
        public void foo(Runnable printFoo) throws InterruptedException {
            fx.put("foo", Thread.currentThread());
            for (int i = 0; i < n; i++) {
                // printFoo.run() outputs "foo". Do not change or remove this line.
                while (!flag) LockSupport.park();
    
                printFoo.run();
                flag = !flag;
    
                LockSupport.unpark(fx.get("bar"));
            }
        }
    
        public void bar(Runnable printBar) throws InterruptedException {
            fx.put("bar", Thread.currentThread());
            for (int i = 0; i < n; i++) {
                // printBar.run() outputs "bar". Do not change or remove this line.
                while (flag) LockSupport.park();
    
                printBar.run();
                flag = !flag;
    
                LockSupport.unpark(fx.get("foo"));
            }
        }
    }
    
    • 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

    总结

    1)通过交替打印线程的先后控制,来学习线程并发中的知识点。

    参考文献

    [1] LeetCode 交替打印

    [2] 多线程六脉神剑

    [3] 畅游多线程之交替打印

  • 相关阅读:
    基于Java毕业设计学习自律养成小程序后台源码+系统+mysql+lw文档+部署软件
    分享图片或链接到抖音
    Vue2 Element DatePicker组件设置默认日期、控制日期范围
    计算机网络-数据交换技术
    使用kaliber与imu_utils进行IMU、相机+IMU联合标定
    Codeforces Round #815 (Div. 2)
    R 语言Analyzing wine data
    关系型数据库(一)---关系数据模型与关系
    Codeforces Round #811 (Div. 3)
    LeetCode-剑指58-I.翻转单词顺序
  • 原文地址:https://blog.csdn.net/qq_43164662/article/details/126755504