我们提供了一个类:

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”。

方法一:Semaphore

  • Semaphore是一个计数信号量。
  • 从概念上将,Semaphore包含一组许可证。
  • 如果有需要的话,每个acquire()方法都会阻塞,直到获取一个可用的许可证。
  • 每个release()方法都会释放持有许可证的线程,并且归还Semaphore一个可用的许可证。
  • 然而,实际上并没有真实的许可证对象供线程使用,Semaphore只是对可用的数量进行管理维护
  • 总结:如果线程要访问一个资源就必须先获得信号量。如果信号量内部计数器大于0,信号量减1,然后允许共享这个资源;否则,如果信号量的计数器等于0,信号量将会把线程置入休眠直至计数器大于0.当信号量使用完时,必须释放
class Foo {Semaphore s12 = new Semaphore(0);Semaphore s23 = new Semaphore(0);public Foo() {}public void first(Runnable printFirst) throws InterruptedException {printFirst.run();s12.release();//释放后s12的值会变成1}public void second(Runnable printSecond) throws InterruptedException {s12.acquire();//没有会阻塞  当为1的时候,说明线程2可以拿到s12了printSecond.run();s23.release();//释放后s23的值会变成1}public void third(Runnable printThird) throws InterruptedException {s23.acquire();//0的时候拿不到,1的时候可以拿到printThird.run();}
}

方法二:Synchronized

1、wait()、notify/notifyAll() 方法是Object的本地final方法,无法被重写。

2、wait()使当前线程阻塞,前提是 必须先获得锁,一般配合synchronized 关键字使用,即,一般在synchronized 同步代码块里使用 wait()、notify/notifyAll() 方法。

3、 由于 wait()、notify/notifyAll() 在synchronized 代码块执行,说明当前线程一定是获取了锁的。

当线程执行wait()方法时候,会释放当前的锁,然后让出CPU,进入等待状态。

只有当 notify/notifyAll() 被执行时候,才会唤醒一个或多个正处于等待状态的线程,然后继续往下执行,直到执行完synchronized 代码块的代码或是中途遇到wait() ,再次释放锁。

也就是说,notify/notifyAll() 的执行只是唤醒沉睡的线程,而不会立即释放锁,锁的释放要看代码块的具体执行情况。所以在编程中,尽量在使用了notify/notifyAll() 后立即退出临界区,以唤醒其他线程让其获得锁

4、wait() 需要被try catch包围,以便发生异常中断也可以使wait等待的线程唤醒。

5、notify 和wait 的顺序不能错,如果A线程先执行notify方法,B线程在执行wait方法,那么B线程是无法被唤醒的。

6、notify 和 notifyAll的区别

notify方法只唤醒一个等待(对象的)线程并使该线程开始执行。所以如果有多个线程等待一个对象,这个方法只会唤醒其中一个线程,选择哪个线程取决于操作系统对多线程管理的实现。notifyAll 会唤醒所有等待(对象的)线程,尽管哪一个线程将会第一个处理取决于操作系统的实现。如果当前情况下有多个线程需要被唤醒,推荐使用notifyAll 方法。比如在生产者-消费者里面的使用,每次都需要唤醒所有的消费者或是生产者,以判断程序是否可以继续往下执行。

7、在多线程中要测试某个条件的变化,使用if 还是while?

要注意,notify唤醒沉睡的线程后,线程会接着上次的执行继续往下执行。所以在进行条件判断时候,可以先把 wait 语句忽略不计来进行考虑;显然,要确保程序一定要执行,并且要保证程序直到满足一定的条件再执行,要使用while进行等待,直到满足条件才继续往下执行

class Foo {public int flag = 1;public Foo() {}public synchronized void first(Runnable printFirst) throws InterruptedException {// printFirst.run() outputs "first". Do not change or remove this line.printFirst.run();flag = 2;this.notifyAll();}public synchronized void second(Runnable printSecond) throws InterruptedException {while(flag != 2){try{this.wait();}catch(Exception e){e.printStackTrace();}}// printSecond.run() outputs "second". Do not change or remove this line.printSecond.run();flag = 3;this.notifyAll();}public synchronized void third(Runnable printThird) throws InterruptedException {while(flag != 3){try{this.wait();}catch(Exception e){e.printStackTrace();}}// printThird.run() outputs "third". Do not change or remove this line.printThird.run();}
}

方法三:BlockingQueue

  • LinkedBlockingDeque: 一个由链表结构组成的双向阻塞队列。队列头部和尾部都可以添加和移除元素,多线程并发时,可以将锁的竞争最多降到一半

  • add(obj):把obj加到BlockingQueue里,如果BlockQueue没有空间,则调用此方法的线程被阻断直到BlockingQueue里面有空间再继续.

  • take():取走BlockingQueue里排在首位的对象,若BlockingQueue为空,阻断进入等待状态直到BlockingQueue有新的数据被加入;

class Foo {//阻塞队列         //同步队列,没有容量,进去一个元素,必须等待取出来以后,才能再往里面放一个元素BlockingQueue<Integer> block12 = new LinkedBlockingQueue<Integer>();BlockingQueue<Integer> block23 = new LinkedBlockingQueue<Integer>();public Foo() {}public void first(Runnable printFirst) throws InterruptedException {printFirst.run();block12.add(0);}public void second(Runnable printSecond) throws InterruptedException {block12.take();//printSecond.run();block23.add(0);}public void third(Runnable printThird) throws InterruptedException {block23.take();printThird.run();}
}

方法四:ReentrantLock

Condition可以通俗的理解为条件队列。当一个线程在调用了await方法以后,直到线程等待的某个条件为真的时候才会被唤醒。这种方式为线程提供了更加简单的等待/通知模式。Condition必须要配合锁一起使用,因为对共享状态变量的访问发生在多线程环境下。一个Condition的实例必须与一个Lock绑定,因此Condition一般都是作为Lock的内部实现。

  • await() :造成当前线程在接到信号或被中断之前一直处于等待状态。
  • await(long time, TimeUnit unit) :造成当前线程在接到信号、被中断或到达指定等待时间之前一直处于等待状态。
  • signal() :唤醒一个等待线程。该线程从等待方法返回前必须获得与Condition相关的锁。
  • signalAll() :唤醒所有等待线程。能够从等待方法返回的线程必须获得与Condition相关的锁。
class Foo {int num;Lock lock;//精确的通知和唤醒线程Condition condition1, condition2, condition3;public Foo() {num = 1;lock = new ReentrantLock();condition1 = lock.newCondition();condition2 = lock.newCondition();condition3 = lock.newCondition();}public void first(Runnable printFirst) throws InterruptedException {lock.lock();try {while (num != 1) {//不是1的时候,阻塞condition1.await();}// printFirst.run() outputs "first". Do not change or remove this line.printFirst.run();num = 2;condition2.signal();} catch (Exception e) {e.printStackTrace();} finally {lock.unlock();}}public void second(Runnable printSecond) throws InterruptedException {lock.lock();try {while (num != 2) {//不是2的时候,阻塞condition2.await();}// printSecond.run() outputs "second". Do not change or remove this line.printSecond.run();num = 3;condition3.signal();} catch (Exception e) {e.printStackTrace();} finally {lock.unlock();}}public void third(Runnable printThird) throws InterruptedException {lock.lock();try {while (num != 3) {//不是3的时候,阻塞condition3.await();}// printThird.run() outputs "third". Do not change or remove this line.printThird.run();num = 1;condition1.signal();} catch (Exception e) {e.printStackTrace();} finally {lock.unlock();}}
}

LeetCode 1114. 按序打印相关推荐

  1. LeetCode 1114 按序打印

    https://leetcode-cn.com/problems/print-in-order/ 解决方案 class Foo {private AtomicInteger jobDone = new ...

  2. LeetCode 多线程 1114. 按序打印

    1114. 按序打印 Ideas 并发执行问题是多线程要解决的经典问题,此题是典型的执行屏障问题,因此我们需要构造几把锁来确保执行顺序. 题目要求按顺序依次执行三个方法,为了保证线程的执行顺序,可以在 ...

  3. 1114. 按序打印

    1114. 按序打印 我们提供了一个类: public class Foo { public void first() { print("first"); } public voi ...

  4. LeetCode:交替打印【1115】

    LeetCode:交替打印[1115] 题目描述 我们提供一个类: class FooBar { public void foo() {for (int i = 0; i < n; i++) { ...

  5. LeetCode 1114. Print in Order--Java解法--并发问题

    此文首发于我的个人博客:LeetCode 1114. Print in Order–Java解法–并发问题 - zhang0peter的个人博客 LeetCode题解专栏:LeetCode题解 Lee ...

  6. 2022-4-8 Leetcode 1114.按顺序打印

    原来直接用信号量就好了 #include <semaphore.h>class Foo {protected:sem_t firstJobDone;sem_t secondJobDone; ...

  7. LeetCode 多线程 1116. 打印零与奇偶数

    1116. 打印零与奇偶数 Ideas 有几个线程就用几个信号量,最先开始的信号量初始化为1,其它初始化为0,然后根据条件判断实现同步. 多线程的问题好多都是:锁自己,解锁别人. Code from ...

  8. leetcode算法题--打印从1到最大的n位数

    原题链接:https://leetcode-cn.com/problems/da-yin-cong-1dao-zui-da-de-nwei-shu-lcof/ vector<int> pr ...

  9. (多线程)leetcode1114. 按序打印 认识AtomicInteger

    我们提供了一个类: public class Foo {   public void one() { print("one"); }   public void two() { p ...

最新文章

  1. html流式布局插件,Jquery瀑布流网格布局插件
  2. json java typeof_Json对象与Json字符串的转化、JSON字符串与Java对象的转换
  3. unity2d随机生成物体_2020 年最好用的一键生成设计神器,全在这里了!
  4. 简述python的特性_Python的特性概要
  5. 项目管理基本目录结构
  6. caffee2安装 踩坑记录
  7. Linux学习(一)
  8. CS下载、安装以及简单使用
  9. C中printf()的常用输出
  10. 奇瑞QQ序列首款新能源汽车QQ冰淇淋上市;上海嘉定集中发展氢燃料电池和ICV | 能动...
  11. 肖申克的救赎-救赎自己的心灵
  12. Linux——Linux账号与群组
  13. matlab修改图像分辨率_matlab imresize 改变图像大小
  14. 中层管理者如何脱颍而出?
  15. 手势识别(一) - 项目概述与简单应用介绍
  16. JavaScript遍历二维数组
  17. 两台思科交换机vlan划分_单交换机VLAN划分(基于Cisco模拟器)
  18. 一段通过java 得到svn log 然后过滤一些文字, 最后打印出来的shell
  19. 用好大数据,科创能变现
  20. Hadoop的应用场景

热门文章

  1. 线性表顺序表模板 纯本人手工创造
  2. MVCWebForm对照学习:文件上传(以图片为例)
  3. Windows Phone 7 处理休眠和墓碑的恢复
  4. 思想篇(1)--企业需要什么样的人才?
  5. springboot2源码1-SpringApplication实例化
  6. 编历修改工作表中的控件属性(更新条形码)
  7. 文件上传fileupload文件接收
  8. 执行NET 命令无法使用超过20个字符的组名或用户名
  9. swift 关于闭包和函数
  10. 深度解析javascript中的浅复制和深复制