LeetCode-按序打印

  |   0 评论   |   0 浏览

LeetCode:按序打印

题目

我们提供了一个类:

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

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

线程 A 将会调用 first() 方法
线程 B 将会调用 second() 方法
线程 C 将会调用 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"。

提示:

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

解法

class Foo {
    private int flag = 0;
//定义Object对象为锁
    private Object lock = new Object();
    public Foo() {
      
    }

    public void first(Runnable printFirst) throws InterruptedException {
 //如果flag不为0则让first线程等待,while循环控制first线程如果不满住条件就一直在while代码块中,防止出现中途跳入,执行下面的代码,其余线程while循环同理
        synchronized(lock){
            while(flag != 0){
                lock.wait();
            }
        // printFirst.run() outputs "first". Do not change or remove this line.
        printFirst.run();
        flag = 1;
//唤醒其余所有的线程
        lock.notifyAll();
        }
    }

    public void second(Runnable printSecond) throws InterruptedException {
        synchronized(lock){
            while(flag!=1){
                lock.wait();
            }
                 
        // printSecond.run() outputs "second". Do not change or remove this line.
        printSecond.run();
        flag=2;
        lock.notifyAll();
        }
    }

    public void third(Runnable printThird) throws InterruptedException {
        synchronized(lock){
            while(flag!=2){
                lock.wait();
            }
        // printThird.run() outputs "third". Do not change or remove this line.
        printThird.run();
        flag=0;
        lock.notifyAll();
        }
    }
}

为何使用Object 作为对象锁

synchronized(非this对象x)同步代码块
     1)在多个线程持有“对象监视器”为同一对象的前提下,同一时间只有一个线程可以执行synchronized(非this对象x)同步代码块。
     2)当持有“对象监视器”为同一个对象的前提下,同一时间只有一个线程可以执行synchronized(非this对象x)同步代码块中的代码。
    锁非this对象具有一定的优点:如果在一个类中有很多个synchronized方法,这时虽然能实现同步,但会受到阻塞,所以影响运行效率;但如果使用同步代码块锁非this对象,则synchronized(非this)代码块中的程序与同步方法是异步的,不与其他锁this同步方法争抢this锁,则可大大提高运行效率。

线程间的通信

用java实现 线程间 通信 涉及到 两个方法, wait() 和 notify(); 看下图源码,这个方法是 java万类之王——Object 类的 非静态成员方法。

这样,我们就可以推测 java的 任何对象实例 都能被用作 线程间通信 的 信号量。 因为 任何对象实例都是从 Object继承出来。

  所谓线程间通信, 

  其一:既然是 通信, 那是谁跟谁发起通信呢? 自然有一个 主动通知方 和 被动接受通知方,比如:我通知你明天会下雨,我就是主动通知方,你是被动接受通知方。  wait() 和 notify() 正是这样,  notify 是主动通知方, wait 是被动接受方。 

  其二:既然是 多线程间,那必然是一个线程 主动通知 另一个线程,所以 wait 和 notify 必须执行在不同的线程,事实它们也无法执行在相同的线程,原因下方慢慢讲。

  当一个线程A执行对象锁的wait()方法时,这个线程A会在wait方法处被阻塞住, 直到有其它线程B调用这个 对象锁的notify()方法,这个线程A才能被唤醒。

wait()、notify()

前提一:由同一个lock对象调用wait、notify方法,和 synchronized 锁。

前提二: wait、nofity 调用时必须加 synchronized(lock) 同步

  1. 当线程A执行wait方法时,该线程会被挂起(即阻塞),同时会释放对象锁(这就能解释上面的例子不会发生死锁);
  2. 当线程B执行notify方法时,会唤醒一个被挂起的线程A;

解法解析

实际上,就是利用Object 作为多个对象的同一非this对象锁,这样一次只有一个线程可以执行。

在执行时,根据flag判断是否要wait阻塞。如果阻塞,其他线程会启动。直到有一个判断对,改变flag,并唤起被挂起的线程。

循环以上,所有任务都按顺序执行了。


作者:大鹏
转载请标注来源:大鹏的技术博客