LeetCode - 176. 第二高的薪水

Posted 碎羽love星谊

tags:

篇首语:本文由小常识网(cha138.com)小编为大家整理,主要介绍了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,CyclicBarrier和信号量Semaphore都是java并发包concurrent下提供的并发工具类,是比synchrorized关键字更高效的同步结构。
    • CountDownLatch(根本是等待其他线程完成,概念上和题目更直接相关)
      • 线程安全的计数器
      • 可以用来做线程安全的条件判断,值为0时才执行后面的代码
      • countDownLatch这个类使一个线程等待其他线程各自执行完毕后再执行。是通过一个计数器来实现的,计数器的初始值是线程的数量。每当一个线程执行完毕后,计数器的值就-1,当计数器的值为0时,表示所有线程都执行完毕,然后在闭锁上等待的线程就可以恢复工作了。
      • 其应用场景是,如果B组线程要等A组线程全部执行完后再执行,那A组中每个线程都会调用其countDown方法计数,即A组中每跑一个线程计数加一。而B组中每个线程都会调用其await()方法。只有当计数达到预设值后,因调用await()方法而处于等待状态的线程全部执行。CountDownLatch可以用于高并发测试,即计数积累了一定数量的线程后再一起执行。
      • CountDownLatch()
        • 构造函数
        • 可传入int值,作为初始计数
      • CountDownLatch.countDown()
        • 当前计数器的值减1
      • CountDownLatch.await()
        • 不需要像wait()那样要放到while中不断检查。
        • 只有该CountDownLatch的值将为0时,才会继续执行后面的代码。
    • Semaphore(根本是限制并发量,这里只是借用并发量为1时的行为来模拟达到题目要求)
      • Semaphore与CountDownLatch相似,不同的地方在于Semaphore的值被获取到后是可以释放的,并不像CountDownLatch那样一直减到底。
      • 获得Semaphore的线程处理完它的逻辑之后,你就可以调用它的Release()函数将它的计数器重新加1,这样其它被阻塞的线程就可以得到调用了。
      • 核心作用是控制并发执行的线程数量。定义信号量的时候会初始化一个预设值n,即最多有n个线程同时执行,如果将n设为1,就达到了互斥锁的效果。
      • 信号量是通过acquire()和release()来控制线程的。最多允许n个线程同时执行即有n个通行证,线程会先acquire()即去获得许可证,只有成功获得许可证才能继续执行,否则处于等待状态,最后通过release()把许可证释放掉以供其他线程获取,通过这种方式去控制线程最大并发量。
      • acquire()和release()都可以传参,acquire(n)/release(n)是获取/释放n个许可证,如果不传参即默认获取/释放1个许可证。有时候最大并发量需要计算一下,举个例子,如果预设值10个许可证,线程acquire(3)就是一次获取3个许可证才能执行,那么最大并发量就是10/3=3个。信号量还有一个availablepermiles()方法顾名思义就是返回可用许可证的数量。
      • 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()方法的时候,心里想的肯定是因为当前方法不满足我们指定的条件,因此执行这个方法的线程需要等待直到其他线程改变了这个条件并且做出了通知。

以上是关于LeetCode - 176. 第二高的薪水的主要内容,如果未能解决你的问题,请参考以下文章

LeetCode--176--第二高的薪水

leetcode(176)第二高的薪水

LeetCode:176.第二高的薪水

LeetCode 176. 第二高的薪水(MySQL版)

LeetCode SQL 1669. 176. 第二高的薪水

LeetCode - 176. 第二高的薪水