精 灵 王


  • 首页

  • 文章归档

  • 所有分类

  • 关于我

  • 搜索
设计模式之美 分布式 Redis 并发编程 个人成长 周志明的软件架构课 架构 单元测试 LeetCode 工具 位运算 读书笔记 操作系统 MySQL 异步编程 技术方案设计 集合 设计模式 三亚 游玩 转载 Linux 观察者模式 事件 Spring SpringCloud 实战 实战,SpringCloud 源码分析 线程池 同步 锁 线程 线程模型 动态代理 字节码 类加载 垃圾收集器 垃圾回收算法 对象创建 虚拟机内存 内存结构 Java

多线程-按序打印

发表于 2021-05-08 | 分类于 并发编程 | 0 | 阅读次数 219

描述

点击查看原题

我们提供了一个类:

public class Foo {
  public void first() { print("first"); }
  public void second() { print("second"); }
  public void third() { print("third"); }
}

三个不同的线程 A、B、C 将会共用一个 Foo 实例。

  • 一个将会调用 first() 方法
  • 一个将会调用 second() 方法
  • 还有一个将会调用 third() 方法

请设计修改程序,以确保 second() 方法在 first() 方法之后被执行,third() 方法在 second() 方法之后被执行。

示例

示例 1:

输入: [1,2,3]
输出: "firstsecondthird"
解释:
有三个线程会被异步启动。
输入 [1,2,3] 表示线程 A 将会调用 first() 方法,线程 B 将会调用 second() 方法,线程 C 将会调用 third() 方法。
正确的输出是 "firstsecondthird"。

示例 2:

输入: [1,3,2]
输出: "firstsecondthird"
解释:
输入 [1,3,2] 表示线程 A 将会调用 first() 方法,线程 B 将会调用 third() 方法,线程 C 将会调用 second() 方法。
正确的输出是 "firstsecondthird"。

提示

  • 尽管输入中的数字似乎暗示了顺序,但是我们并不保证线程在操作系统中的调度顺序。
  • 你看到的输入格式主要是为了确保测试的全面性。

题解

解法1:使用volatile修饰的变量

用volatile修饰的变量来标识每个方法的执行情况。输出second、third的方法需要等待前一个方法触发指定的条件方可执行。

代码:

class Foo_1 {

        private volatile int index = 0;

        public Foo_1() {
        }

        public void first(Runnable printFirst) throws InterruptedException {

            printFirst.run();
            index = 1;
        }

        public void second(Runnable printSecond) throws InterruptedException {
            while(index < 1){
							// 自旋,等待first方法执行
            }
            printSecond.run();
            index = 2;
        }

        public void third(Runnable printThird) throws InterruptedException {
            while(index < 2){
              // 自旋,等待second方法执行
            }
            printThird.run();
        }
    }

优点:

  • 代码改动少,实现简单

缺点:

  • second、third使用while自旋,会占用CPU,如果方法first不执行,则后面的方法会一直占用 CPU。

解法2:使用CountDownLatch

CountDownLatch可以用来计数,通过设置为1,为second、third设置两个计数,second方法可执行的计数需要由first方法扣减,third方法可执行计数需要由second方法扣减触发,

代码:

class Foo_2 {
    // 初始化计数
    CountDownLatch cdl_second = new CountDownLatch(1);
    CountDownLatch cdl_third = new CountDownLatch(1);

    public Foo_2() {

    }

    public void first(Runnable printFirst) throws InterruptedException {
        // printFirst.run() outputs "first". Do not change or remove this line.
        printFirst.run();
        // 减计数,second方法才可以执行
        cdl_second.countDown();
    }

    public void second(Runnable printSecond) throws InterruptedException {
        // 等待计数为0
        cdl_second.await();
        // printSecond.run() outputs "second". Do not change or remove this line.
        printSecond.run();
        // 减计数,third方法才可以执行
        cdl_third.countDown();
    }

    public void third(Runnable printThird) throws InterruptedException {
        // 等待计数为0
        cdl_third.await();
        // printThird.run() outputs "third". Do not change or remove this line.
        printThird.run();
    }
}

优点:

  • 代码改动少,实现简单,没有自旋不会占用多余的CPU

CountDownLatch 一般都会和CyclicBarrier进行比较,那这里可以用CyclicBarrier实现么?CyclicBarrier主要的方法就只有一个await()方法,多个线程时由最后一个线程来唤醒前面等待的线程,这里没办法确定最后一个线程是谁。

解法3:使用Semaphore

和CountDownLatch的方案类似,只不过这里是由前一个方法来释放资源,后面的方法才可以执行。

先通过构造方法占用资源,second、third方法请求acquire()就会等待。

然后first方法执行,释放一个资源。

之后second方法执行,释放一个资源,最后second方法执行。

代码:

class Foo_3 {

    // 1:实现互斥锁一样的效果
    Semaphore sp_second = new Semaphore(1,true);
    Semaphore sp_third = new Semaphore(1,true);

    public Foo_3() {
        try{
            // 先通过构造方法占用资源,second、third方法请求acquire()就会等待
            sp_second.acquire();
            sp_third.acquire();
        }catch(InterruptedException e){
            e.printStackTrace();
        }
    }

    public void first(Runnable printFirst) throws InterruptedException {
        printFirst.run();
        // 释放资源,second方法才可以获得
        sp_second.release();
    }

    public void second(Runnable printSecond) throws InterruptedException {
        sp_second.acquire();
        printSecond.run();
        // 释放资源,third方法才可以获得
        sp_third.release();
    }

    public void third(Runnable printThird) throws InterruptedException {
        sp_third.acquire();
        printThird.run();
    }
}

解法4:使用条件锁

使用ReentrantLock结合Condition,来控制每个方法的执行。

代码:

static class Foo_4 {
    // 标识每个方法的执行情况
    private volatile int flag = 1;
    ReentrantLock lock;
    Condition c1;
    Condition c2;
    Condition c3;

    public Foo_4() {
        // 初始化锁
        lock = new ReentrantLock(true);
        c1 = lock.newCondition();
        c2 = lock.newCondition();
        c3 = lock.newCondition();
    }

    public void first(Runnable printFirst) throws InterruptedException{
        try{
            lock.lock();
            while(flag != 1){
                c1.await();
            }
            printFirst.run();
            flag <<= 1;
            c2.signal();
        }catch(Exception e){
            e.printStackTrace();
        }finally{
            lock.unlock();
        }
    }

    public void second(Runnable printSecond) throws InterruptedException{
        try{
            lock.lock();
            while(flag != 2){
                c2.await();
            }
            printSecond.run();
            flag <<= 1;
            c3.signal();
        }finally{
            lock.unlock();
        }
    }

    public void third(Runnable printThird) throws InterruptedException{
        try{
            lock.lock();
            while(flag != 4){
                c3.await();
            }
            printThird.run();
            c1.signal();
        }finally{
            lock.unlock();
        }
    }
}

测试方法

public static void main(String[] args) throws InterruptedException{
        Foo_4 foo = new Foo_4();

        new Thread(()-> {
            try{
                foo.second(()-> System.out.println("second"));
            }catch(InterruptedException e){
                e.printStackTrace();
            }
        }).start();
        new Thread(()-> {
            try{
                foo.first(()-> System.out.println("first"));
            }catch(InterruptedException e){
                e.printStackTrace();
            }
        }).start();
        new Thread(()-> {
            try{
                foo.third(()-> System.out.println("third"));
            }catch(InterruptedException e){
                e.printStackTrace();
            }
        }).start();
    }
精 灵 王 wechat
👆🏼欢迎扫码关注微信公众号👆🏼
  • 本文作者: 精 灵 王
  • 本文链接: https://jinglingwang.cn/archives/print-in-order
  • 版权声明: 本博客所有文章除特别声明外,均采用CC BY-NC-SA 3.0 许可协议。转载请注明出处!
# 设计模式之美 # 分布式 # Redis # 并发编程 # 个人成长 # 周志明的软件架构课 # 架构 # 单元测试 # LeetCode # 工具 # 位运算 # 读书笔记 # 操作系统 # MySQL # 异步编程 # 技术方案设计 # 集合 # 设计模式 # 三亚 # 游玩 # 转载 # Linux # 观察者模式 # 事件 # Spring # SpringCloud # 实战 # 实战,SpringCloud # 源码分析 # 线程池 # 同步 # 锁 # 线程 # 线程模型 # 动态代理 # 字节码 # 类加载 # 垃圾收集器 # 垃圾回收算法 # 对象创建 # 虚拟机内存 # 内存结构 # Java
我的开发工具百宝箱
收藏!Java编程技巧之单元测试用例编写流程
  • 文章目录
  • 站点概览
精 灵 王

精 灵 王

青春岁月,以此为伴

85 日志
14 分类
43 标签
RSS
E-mail
Creative Commons
Links
  • 添加友链说明
© 2022 精 灵 王
渝ICP备2020013371号
0%