LeetCode - 176. 第二高的薪水

  • 题目
    • 来源:力扣(LeetCode)
    • 描述
      • 我们提供了一个类:

        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() 方法之后被执行。
      • 初始程序:

        class Foo {
        
            public Foo() {
        
            }
        
            public void first(Runnable printFirst) throws InterruptedException {
        
                // printFirst.run() outputs "first". Do not change or remove this line.
                printFirst.run();
            }
        
            public void second(Runnable printSecond) throws InterruptedException {
        
                // printSecond.run() outputs "second". Do not change or remove this line.
                printSecond.run();
            }
        
            public void third(Runnable printThird) throws InterruptedException {
        
                // printThird.run() outputs "third". Do not change or remove this line.
                printThird.run();
            }
        }
      • 示例 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"。
  • 题解
    • 使用CountDownLatch计数器

      class Foo {
          private CountDownLatch c2;
          private CountDownLatch c3;
          public Foo() {
              c2 = new CountDownLatch(1);
              c3 = new CountDownLatch(1);
          }
      
          public void first(Runnable printFirst) throws InterruptedException {
      
              // printFirst.run() outputs "first". Do not change or remove this line.
              printFirst.run();
              c2.countDown();
          }
      
          public void second(Runnable printSecond) throws InterruptedException {
              c2.await();
              // printSecond.run() outputs "second". Do not change or remove this line.
              printSecond.run();
              c3.countDown();
          }
      
          public void third(Runnable printThird) throws InterruptedException {
              c3.await();
              // printThird.run() outputs "third". Do not change or remove this line.
              printThird.run();
          }
      }
    • 使用Semaphore信号量

      class Foo {
          //声明两个 Semaphore变量
          private Semaphore spa,spb;
          public Foo03() {
              //初始化Semaphore为0的原因:如果这个Semaphore为零,如果另一线程调用(acquire)这个Semaphore就会产生阻塞,便可以控制second和third线程的执行
              spa = new Semaphore(0);
              spb = new Semaphore(0);
          }
          public void first(Runnable printFirst) throws InterruptedException {
                  // printFirst.run() outputs "first". Do not change or remove this line.
                  printFirst.run();
                  //只有等first线程释放Semaphore后使Semaphore值为1,另外一个线程才可以调用(acquire)
                  spa.release();
          }
          public void second(Runnable printSecond) throws InterruptedException {
                  //只有spa为1才能执行acquire,如果为0就会产生阻塞
                  spa.acquire();
                  // printSecond.run() outputs "second". Do not change or remove this line.
                  printSecond.run();
                  spb.release();
          }
          public void third(Runnable printThird) throws InterruptedException {
                  //只有spb为1才能通过,如果为0就会阻塞
                  spb.acquire();
                  // printThird.run() outputs "third". Do not change or remove this line.
                  printThird.run();
          }
      }
    • 构造执行屏障实现(Synchronized锁和控制变量)
      • 我们需要构造2道屏障,second线程等待first屏障,third线程等待second 屏障。
      • firstFinished和secondFinished也可以合并成一个int类型的控制变量,在三个方法中的run()后面分别赋值为1、2、0,然后后面两个方法中wait()的判断条件换成是否等于1、2。

        class Foo {
        
            private boolean firstFinished;
            private boolean secondFinished;
            private Object lock = new Object();
        
            public Foo() {
        
            }
        
            public void first(Runnable printFirst) throws InterruptedException {
        
                synchronized (lock) {
                    // printFirst.run() outputs "first". Do not change or remove this line.
                    printFirst.run();
                    firstFinished = true;
                    lock.notifyAll(); 
                }
            }
        
            public void second(Runnable printSecond) throws InterruptedException {
        
                synchronized (lock) {
                    while (!firstFinished) {
                        lock.wait();
                    }
        
                    // printSecond.run() outputs "second". Do not change or remove this line.
                    printSecond.run();
                    secondFinished = true;
                    lock.notifyAll();
                }
            }
        
            public void third(Runnable printThird) throws InterruptedException {
        
                synchronized (lock) {
                    while (!secondFinished) {
                            lock.wait();
                        }
        
                        // printThird.run() outputs "third". Do not change or remove this line.
                        printThird.run();
                    } 
            }
        }
  • 考点
    • CountDownLatch
      • countDownLatch这个类使一个线程等待其他线程各自执行完毕后再执行。是通过一个计数器来实现的,计数器的初始值是线程的数量。每当一个线程执行完毕后,计数器的值就-1,当计数器的值为0时,表示所有线程都执行完毕,然后在闭锁上等待的线程就可以恢复工作了。
      • 线程安全的计数器
      • 可以用来做线程安全的条件判断,值为0时才执行后面的代码
      • CountDownLatch()
        • 构造函数
        • 可传入int值,作为初始计数
      • CountDownLatch.countDown()
        • 当前计数器的值减1
      • CountDownLatch.await()
        • 不需要像wait()那样要放到while中不断检查。
        • 只有该CountDownLatch的值将为0时,才会继续执行后面的代码。
    • Semaphore
      • Semaphore与CountDownLatch相似,不同的地方在于Semaphore的值被获取到后是可以释放的,并不像CountDownLatch那样一直减到底。
      • 获得Semaphore的线程处理完它的逻辑之后,你就可以调用它的Release()函数将它的计数器重新加1,这样其它被阻塞的线程就可以得到调用了。
      • Semaphore.acquire()
        • 只有Semaphore值为1才能执行acquire,如果为0就会产生阻塞
      • Semaphore.release()
        • 释放Semaphore后使Semaphore值为1,另外一个线程才可以调用(acquire)
    • 构造执行屏障实现(Synchronized锁和控制变量)
      • Java中,我们使用线程等待的方式实现执行屏障,使用释放线程等待的方式实现屏障消除。
      • Synchronized
        • Synchronized (Object lock) { ... }
          • 构造函数,将传入的对象作为锁
          • 也就是不同方法中如果使用了同一对象作为锁,那么这些不同代码块中的代码在不同的线程中一次只能执行一个,只有获得了该对象上的锁的线程可以执行后面的代码
          • 注意lock可以声明成final的?不然后如果某处代码改变了lock对象,可能会引起线程错乱。
      • Object.notifyAll()
        • 通常放在Synchronized代码块的最后
        • 一般放在线程逻辑代码的锁范围的最后,在即将释放锁时通知一下其他线程去检查线程、锁状态和获取线程锁。
      • Object.wait()
        • 通常在Synchronized代码块的开始处用来判断和等待对象锁
        • 即等其他线程更新线程状态和释放锁
        • 永远都要把wait()放到循环语句里面。
          • 因为wait()的线程永远不能确定其他线程会在什么状态下notify(),所以必须在被唤醒、抢占到锁并且从wait()方法退出的时候再次进行指定条件的判断,以决定是满足条件往下执行呢还是不满足条件再次wait()呢。
          • 我们在调用wait()方法的时候,心里想的肯定是因为当前方法不满足我们指定的条件,因此执行这个方法的线程需要等待直到其他线程改变了这个条件并且做出了通知。
上一篇:OBS窗口捕获


下一篇:解读clickhouse存算分离在华为云实践