2020M6W5 ARTS

1. Algorithm

上周开始学习并发编程,因此找了多线程的问题来练习,巩固知识。

1.1 1114. 按序打印

我们提供了一个类:

public class Foo {
  public void one() { print("one"); }
  public void two() { print("two"); }
  public void three() { print("three"); }
}
三个不同的线程将会共用一个 Foo 实例。

线程 A 将会调用 one() 方法
线程 B 将会调用 two() 方法
线程 C 将会调用 three() 方法
请设计修改程序,以确保 two() 方法在 one() 方法之后被执行,three() 方法在 two() 方法之后被执行。

 

示例 1:

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

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

注意:

尽管输入中的数字似乎暗示了顺序,但是我们并不保证线程在操作系统中的调度顺序。

你看到的输入格式主要是为了确保测试的全面性。

来源:力扣(LeetCode)
链接:https://leetcode-cn.com/problems/print-in-order
著作权归领扣网络所有。商业转载请联系官方授权,非商业转载请注明出处。

当然可以有几种方法来实现,本周正好学到LockCondition,所以我的方案是条件变量

class Foo {

    final Lock lock = new ReentrantLock();
  	// 标识first方法执行是否完成的条件
    final Condition first = lock.newCondition();
  	// 标识second方法执行是否完成的条件  
    final Condition second = lock.newCondition();
  	// 标识当前运行到哪一个方法
    private  AtomicInteger flag = new AtomicInteger(0);

    public Foo() {

    }

    public void first(Runnable printFirst) throws InterruptedException {
        // 加锁
        lock.lock();
        try {
            // printFirst.run() outputs "first". Do not change or remove this line.
            // first方法是需要第一个运行的,因此不需要条件判断,上来运行就可以了
            printFirst.run();
            // 运行完成之后,标识下一个方法应该是2
            flag.set(2);
            // 通知first方法已经执行完成,可以运行唤醒下一个线程了
            // 因为happens-before,所以执行first.singal()保证对flag的修改对下一个线程可见
            first.signal();
        } finally {
            lock.unlock();
        }

    }

    public void second(Runnable printSecond) throws InterruptedException {
        lock.lock();
        try {
            // 判断上一个方法是否执行完成,如果没有,阻塞线程
            while(flag.get() != 2) {
                first.await();
            }
            // first.signal(); 调用后,
            // printSecond.run() outputs "second". Do not change or remove this line.
            printSecond.run();
            // 同first()的处理
            flag.set(3);           
            second.signal();
        } finally {
            lock.unlock();
        }        
    }

    public void third(Runnable printThird) throws InterruptedException {
        lock.lock();
        try {
            while(flag.get() != 3) {
                second.await();
            }
            // printThird.run() outputs "third". Do not change or remove this line.
            printThird.run();
        } finally {
            lock.unlock();
        }               
    }
}

之前看java并发编程课程的时候,还以为自己已经懂了,真正做题的时候才发现脑袋里有点空。反映在解题中就是一开始没有设置条件private AtomicInteger flag = new AtomicInteger(0);导致死锁。

另外在写题解的时候我也是有点疑惑于flag.set(2);while(flag.get() != 2)条件之间是否由于Happens-before规则而保证在调用first.signal()之后才对second()方法可见。因此调整了flag.set(2)的顺序,结果是一致的,说明我的理解没有错,在lock.lock()之后的代码块内确实遵循Happens-before规则。

用信号量也可以实现

class Foo {

    final Semaphore s2 = new Semaphore(0);
    final Semaphore s3 = new Semaphore(0);
   
    public Foo() {

    }

    public void first(Runnable printFirst) throws InterruptedException {
        printFirst.run();
        s2.release();
    }

    public void second(Runnable printSecond) throws InterruptedException {
        s2.acquire();
        printSecond.run();
        s3.release();     
    }

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

同样也找到了知识盲点,Semaphore中的acquire()方法对应计数器减1,执行此方法后如果计数器小于0则会阻塞当前线程,还是看了评论区的答案后才恍然大悟。

2. Review

2.1 Continuous Delivery - It’s Not All about Tech!

背景介绍

作者的职业生涯从开发者到软件测试再到如今是NewVoiceMedia公司的工程经理(engineering manager)。

这篇文章讲述的是非技术因素在持续集成中的影响,包括:

  1. 仔细评估需求某些在上线前加急的需求是否必要,砍掉那些非真正紧急的需求将使得发布更加稳定;
  2. 事前仔细研究,事后复盘,最好使用可视化的手段来展现当前的瓶颈;
  3. 使用Toyota Improvement Kata

3. Tip

3.1 在构造方法中抛出异常

我们会遇到一些情况当创建对象时需要检查参数有效性,比如:

public class Person {
  private int age;
  public Person(int _age) {
    if (_age < 0) {
      throw new Exception();
    }
    this.age = _age;
  }
}

那么在构造方法中抛出异常是一个好办法么?

参考: https://stackoverflow.com/questions/6086334/is-it-good-practice-to-make-the-constructor-throw-an-exception

3.2 预测试提交、个人构建、试飞构建

这是CI/CD中的概念,预测试提交(pre-tested commit)、个人构建(personal build)、试飞构建(preflight build)其实讲的是同一件事,就是本地提交会自动测试是否构建通过,如果通过了持续集成服务器才会将变更提交到版本控制库中。

Jenkins中的插件介绍中有相关策略介绍(https://plugins.jenkins.io/pretested-integration/)

4. Share: 慢下来的能力

我是在2014年的时候知道Python这门编程语言的。当时人工智能还处在爆发的前夕,远远没有到现在万众瞩目,言必称AI、机器学习的程度。因此Python这门语言还是处在推荐的人很多,讲了许多优秀的地方,但是暂时没有看到一个杀手级应用的状况。

因为工作中很少有机会用到,我也就没有认真去学,后来AI大爆发,但凡订阅一点技术公众号,看个帖子都是Python培训班的广告。

现在我时常使用Python解决工作中的一些问题,通过点滴积累发现自己也能开始写点有用的东西了。如果这件事在2014年就开始做的话,那么到今天有了5、6年的积累我也能熬成一个Python中级程序员了,而不是一边写一边查资料的菜鸟。

但是现在不写现在不学的话也就永远不会。技术更新迭代太快,看准一个之后一定要去试试,不要急。

  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值