多线程交替打印 [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();
            }
        }
    }
}

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();
            }
        }

    }
}

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();// 让执行态 -> 就绪态
        }
    }
}

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;
        }

    }
}

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();
        }
    }
}

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解锁。
        }
    }
}

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时超过容量就阻塞。
        }
    }
}

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)通过交替打印线程的先后控制,来学习线程并发中的知识点。

参考文献

[1] LeetCode 交替打印

[2] 多线程六脉神剑

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

  • 1
    点赞
  • 6
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值