一、Print in Order

Suppose we have a class:

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

The same instance of Foo will be passed to three different threads. Thread A will call first(), thread B will call second(), and thread C will call third(). Design a mechanism and modify the program to ensure that second() is executed after first(), and third() is executed after second().

Example 1:
Input: [1,2,3]
Output: "firstsecondthird"
Explanation: There are three threads being fired asynchronously. The input [1,2,3] means thread A calls first(), thread B calls second(), and thread C calls third(). "firstsecondthird" is the correct output.

方法1:信号量,semaphore和mutex都是内核对象,都可用于进程间的同步,并且都特别占用系统资源,区别是,mutex只能由一个线程(进行)访问被保护的资源。semaphore 是一种带计数的mutex的锁定,可定义同时访问被保护的资源的线程数



import java.util.concurrent.Semaphore;
class Foo {
private static Semaphore firstSemaphore = new Semaphore(1);
private static Semaphore secordSemaphore = new Semaphore(0);
private static Semaphore thirdSemaphore = new Semaphore(0);
public Foo() { }
public void first(Runnable printFirst) throws InterruptedException {
firstSemaphore.acquire();
// printFirst.run() outputs "first". Do not change or remove this line.
printFirst.run();
secordSemaphore.release();
}
public void second(Runnable printSecond) throws InterruptedException {
secordSemaphore.acquire();
// printSecond.run() outputs "second". Do not change or remove this line.
printSecond.run();
thirdSemaphore.release();
}
public void third(Runnable printThird) throws InterruptedException {
thirdSemaphore.acquire();
// printThird.run() outputs "third". Do not change or remove this line.
printThird.run();
firstSemaphore.release();
}
}

方法2、原子类AtomicInteger,AtomicInteger是一个提供原子操作的Integer类,通过线程安全的方式操作加减

import java.util.concurrent.atomic.AtomicInteger;
class Foo { private final AtomicInteger count = new AtomicInteger(0);
public Foo() { }
public void first(Runnable printFirst) throws InterruptedException {
//自旋,避免进入内核态,不过浪费CPU资源
while (count.get() != 0) {}
printFirst.run();
count.incrementAndGet();
}
public void second(Runnable printSecond) throws InterruptedException {
while (count.get() != 1) {}
printSecond.run();
count.incrementAndGet();
}
public void third(Runnable printThird) throws InterruptedException {
while (count.get() != 2) {}
printThird.run();
count.set(0);
}
}

方法3:倒计时器CountDownLatch,参考方法2。在多线程协作完成业务功能时,CountDownLatch能让我们很轻松实现下面这个需求,当需要等待其他多个线程完成任务之后,主线程才能继续往下执行业务功能

二、Print FooBar Alternately

Suppose you are given the following code:

class FooBar {
public void foo() {
for (int i = 0; i < n; i++) {
print("foo");
}
}
public void bar() {
for (int i = 0; i < n; i++) {
print("bar");
}
}
}

The same instance of FooBar will be passed to two different threads. Thread A will call foo()while thread B will call bar(). Modify the given program to output "foobar" n times.

Example 2:
Input: n = 2
Output: "foobarfoobar"
Explanation: "foobar" is being output 2 times.

这题其实是题目一的升级版,也可以使用信号量来轻松完成,这里给出其他解法

线程问题无非是要保证一致性、有序性和避免死锁,可见性可以使用volatile来保证,有序性可以使用锁来保证,死锁问题我们可以打破死锁的条件,也就是需要批量申请好资源或者按顺序获取到所有资源后才算获取到锁,比如



class FooBar {
private int n; private List<String> als; public FooBar(int n) {
this.n = n;
als = new ArrayList<>(2);
} synchronized void apply(String pre,String next) {
while (als.contains(pre)||als.contains(next)) {
try {
wait();
} catch (InterruptedException e) {
e.printStackTrace();
}
}
als.add(pre);
als.add(next);
} synchronized void free(String pre,String next) {
als.remove(pre);
als.remove(next);
notifyAll();
} public void foo(Runnable printFoo) throws InterruptedException { for (int i = 0; i < n; i++) {
apply("bar","foo");
// printFoo.run() outputs "foo". Do not change or remove this line.
printFoo.run();
free("foo","bar"); }
} public void bar(Runnable printBar) throws InterruptedException { for (int i = 0; i < n; i++) {
apply("foo","bar");
// printBar.run() outputs "bar". Do not change or remove this line.
printBar.run();
free("bar","foo");
}
}
}

但是上面的示例无法满足本题要求,当n=1时输出结果可能是foobar也可以是barfoo,同理n=k时也会有顺序性问题,看似通过add和remove字符串顺序来解决,但是没有达到效果,具体分析过程留给读者完成

我们换种方法来完成,使用标准的生产-消费模型

class FooBar {
private int n;
private Object lock;
private Boolean printFooStatus;
public FooBar(int n) {
this.n = n;
lock = new Object();
printFooStatus = true;
} public void foo(Runnable printFoo) throws InterruptedException {
for (int i = 0; i < n; i++) {
synchronized (lock) {
//必须使用while
while (!printFooStatus){
lock.wait();
}
// printFoo.run() outputs "foo". Do not change or remove this line.
printFoo.run();
printFooStatus = false;
//必须放在synchronized里面
lock.notifyAll();
}
}
}
public void bar(Runnable printBar) throws InterruptedException {
for (int i = 0; i < n; i++) {
synchronized (lock) {
//必须使用while
while (printFooStatus) {
lock.wait();
}
// printBar.run() outputs "bar". Do not change or remove this line.
printBar.run();
printFooStatus = true;
//必须放在synchronized里面
lock.notifyAll();
}
}
}
}

这里需要注意的几个点:

1、初学者理解wait()的时候都认为是将当前线程阻塞,所以Thread.currentThread().wait();视乎很有道理。但是不知道大家有没有发现,在JDK类库中wait()和notify()方法并不是Thread类的,而是Object()中的。在其他线程调用此对象的 notify() 方法或 notifyAll() 方法前,当前线程等待
2、始终使用while循环来调用wait方法,永远不要在循环外调用wait方法,这样做的原因是尽管并不满足条件,但是由于其他线程调用notifyAll方法会导致被阻塞线程意外唤醒,此时执行条件不满足,它会导致约束失效
3、唤醒线程,应该使用notify还是notifyAll?notify会随机通知等待队列中的一个线程,而notifyAll会通知等待队列中所有线程,可知notify是有风险的 ,可能导致某些线程永远不会被通知到
4、当前线程必须拥有此对象监视器,然后才可以放弃对此监视器的所有权并等待 ,直到其他线程通过调用notify方法或notifyAll方法通知在此对象的监视器上等待的线程醒来,然后该线程将等到重新获得对监视器的所有权后才能继续执行。否则会报IllegalMonitorStateException 错误

不过Object的wait\notify\notifyAll原理是基于内核中的对象监视器Monitor完成的,有可能导致大量的上下文切换。为了更好的性能,往往使用基于AQS的显示锁ReetrantLock中的成员变量ConditionObject代替。AQS中存在一个同步队列,当一个线程没有获取到锁时就会进入到同步队列中进行阻塞,如果被唤醒后获取到销,则移出同步队列。另外AQS中还存在一个条件队列,通过addWaiter方法,可以将wait()方法调用的线程放入到条件队列中,线程进入等待状态,当调用signal或者signalAll方法时,线程就会被唤醒,之后进入到同步队列中。其中条件队列是通过链表实现的,所以可以支持多个等待队列。也就是说使用基于AQS接口的await\signal\signalAll原理是基于JAVA代码层实现的,性能有更大的优势

所以本题的另外一种写法是



import java.util.concurrent.locks.Condition;
import java.util.concurrent.locks.ReentrantLock;
class FooBar {
private int n;
private Boolean printFooStatus;
private ReentrantLock reentrantLock;
private Condition condition;
public FooBar(int n) {
this.n = n;
printFooStatus = true;
//不管使用公平锁还是非公平锁,在本题中都没有区别
reentrantLock= new ReentrantLock(false);
condition = reentrantLock.newCondition();
}
public void foo(Runnable printFoo) throws InterruptedException {
for (int i = 0; i < n; i++) {
reentrantLock.lock();
try {
while (!printFooStatus){
condition.await();
}
// printFoo.run() outputs "foo". Do not change or remove this line.
printFoo.run();
} catch (Exception e) {
e.printStackTrace();
} finally {
printFooStatus = false;
//同理:必须放在锁内
condition.signalAll();
reentrantLock.unlock(); }
}
}
public void bar(Runnable printBar) throws InterruptedException {
for (int i = 0; i < n; i++) {
reentrantLock.lock();
try {
while (printFooStatus){
condition.await();
}
// printBar.run() outputs "bar". Do not change or remove this line.
printBar.run();
} catch (Exception e) {
e.printStackTrace();
} finally {
printFooStatus = true;
//同理:必须放在锁内
condition.signalAll();
reentrantLock.unlock(); }
}
}
}

三、Print Zero Even Odd

Suppose you are given the following code:

class ZeroEvenOdd {
public ZeroEvenOdd(int n) { ... } // constructor
public void zero(printNumber) { ... } // only output 0's
public void even(printNumber) { ... } // only output even numbers
public void odd(printNumber) { ... } // only output odd numbers
}

The same instance of ZeroEvenOdd will be passed to three different threads:

  1. Thread A will call zero() which should only output 0's.
  2. Thread B will call even() which should only ouput even numbers.
  3. Thread C will call odd() which should only output odd numbers.
    Each of the thread is given a printNumbermethod to output an integer. Modify the given program to output the series 010203040506… where the length of the series must be 2n.

Example 1:
Input: n = 2
Output: "0102"
Explanation: There are three threads being fired asynchronously. One of them calls zero(), the other calls even(), and the last one calls odd(). "0102" is the correct output.

Example 2:
Input: n = 5
Output: "0102030405"

提示:使用信号量

四、Building H2O

There are two kinds of threads, oxygen and hydrogen. Your goal is to group these threads to form water molecules. There is a barrier where each thread has to wait until a complete molecule can be formed. Hydrogen and oxygen threads will be given a releaseHydrogen and releaseOxygen method respectfully, which will allow them to pass the barrier. These threads should pass the barrier in groups of three, and they must be able to immediately bond with each other to form a water molecule. You must guarantee that all the threads from one molecule bond before any other threads from the next molecule do.

In other words:

  • If an oxygen thread arrives at the barrier when no hydrogen threads are present, it has to wait for two hydrogen threads.
  • If a hydrogen thread arrives at the barrier when no other threads are present, it has to wait for an oxygen thread and another hydrogen thread.
    We don’t have to worry about matching the threads up explicitly; that is, the threads do not necessarily know which other threads they are paired up with. The key is just that threads pass the barrier in complete sets; thus, if we examine the sequence of threads that bond and divide them into groups of three, each group should contain one oxygen and two hydrogen threads.

    Write synchronization code for oxygen and hydrogen molecules that enforces these constraints.

Example 1:
Input: "HOH"
Output: "HHO"
Explanation: "HOH" and "OHH" are also valid answers.



class H2O {

    private Object lock = new Object();
private int counter =0;
public H2O() { }
public void hydrogen(Runnable releaseHydrogen) throws InterruptedException { synchronized (lock) {
while(counter==2){
lock.wait();
}
releaseHydrogen.run();
counter++;
lock.notifyAll();
}
} public void oxygen(Runnable releaseOxygen) throws InterruptedException {
synchronized (lock) {
while(counter!=2){
lock.wait();
}
releaseOxygen.run();
counter=0;
lock.notifyAll();
} }
}

文章来源:www.liangsonghua.me

作者介绍:京东资深工程师-梁松华,在稳定性保障、敏捷开发、JAVA高级、微服务架构方面有深入的理解

leetcode并发题目解题报告JAVA版的更多相关文章

  1. LeetCode: Combination Sum 解题报告

    Combination Sum Combination Sum Total Accepted: 25850 Total Submissions: 96391 My Submissions Questi ...

  2. 【LeetCode】383. Ransom Note 解题报告(Java & Python)

    作者: 负雪明烛 id: fuxuemingzhu 个人博客: http://fuxuemingzhu.cn/ 目录 题目描述 题目大意 解题方法 Java解法 Python解法 日期 [LeetCo ...

  3. 【LeetCode】575. Distribute Candies 解题报告(Java & Python)

    作者: 负雪明烛 id: fuxuemingzhu 个人博客: http://fuxuemingzhu.cn/ 目录 题目描述 题目大意 解题方法 Java解法 Python解法 日期 题目地址:ht ...

  4. 【LeetCode】Permutations 解题报告

    全排列问题.经常使用的排列生成算法有序数法.字典序法.换位法(Johnson(Johnson-Trotter).轮转法以及Shift cursor cursor* (Gao & Wang)法. ...

  5. LeetCode - Course Schedule 解题报告

    以前从来没有写过解题报告,只是看到大肥羊河delta写过不少.最近想把写博客的节奏给带起来,所以就挑一个比较容易的题目练练手. 原题链接 https://leetcode.com/problems/c ...

  6. LeetCode: Sort Colors 解题报告

    Sort ColorsGiven an array with n objects colored red, white or blue, sort them so that objects of th ...

  7. 【LeetCode】237. Delete Node in a Linked List 解题报告 (Java&Python&C++)

    作者: 负雪明烛 id: fuxuemingzhu 个人博客:http://fuxuemingzhu.cn/ 目录 题目描述 题目大意 解题方法 设置当前节点的值为下一个 日期 [LeetCode] ...

  8. 【LeetCode】349. Intersection of Two Arrays 解题报告(Java & Python)

    作者: 负雪明烛 id: fuxuemingzhu 个人博客: http://fuxuemingzhu.cn/ 目录 题目描述 题目大意 解题方法 方法一:Java解法,HashSet 方法二:Pyt ...

  9. 【LeetCode】136. Single Number 解题报告(Java & Python)

    作者: 负雪明烛 id: fuxuemingzhu 个人博客: http://fuxuemingzhu.cn/ 目录 题目描述 题目大意 解题方法 异或 字典 日期 [LeetCode] 题目地址:h ...

随机推荐

  1. Fire! UVA - 11624 (两步bfs)

    题目链接 题意 人要从迷宫走出去,火会向四个方向同时扩散 分析 两步bfs,先出火到达各地时的时间(设初始时间为0,人每走一步为1s,在着一步内火可以向四周可触及的方向同时扩散),然后在bfs人,人能 ...

  2. 牛逼哄哄的Qt库

    目录 一.有价值 - 好的网站 - 好的文章 二.Qt开源库-工具 - QtXlsx--excel读写库 三.Qt开源库-控件 - libqxt编译 - Qwt - QCustomPlot - 其他 ...

  3. 使用java的MultipartFile实现layui官网文件上传实现全部示例,java文件上传

    layui(谐音:类UI) 是一款采用自身模块规范编写的前端 UI 框架,遵循原生 HTML/CSS/JS 的书写与组织形式,门槛极低,拿来即用. layui文件上传示例地址:https://www. ...

  4. 删除git中缓存的用户名和密码

    我们使用Git命令去clone Gitlab仓库的代码时,第一次弹框提示输入账号密码的时候输错了,然后后面就一直拒绝,不再重复提示输入账号密码,怎么破? git报错信息 运行一下命令缓存输入的用户名和 ...

  5. windows7(win7)64/32位激活工具

    win7激活工具中文绿色免费版是改自binbin的作品,我们修改的windows7激活工具grldr模拟激活是别人的东西,能激活win7旗舰.原作者是binbin,其他的激活工具都是基于grldr模拟 ...

  6. python接口自动化(二十九)--html测试报告通过邮件发出去——上(详解)

    简介 前边几篇,已经教小伙伴们掌握了如何生成HTML的测试报告,那么生成测试报告,我们也不能放在那里不管了,这样即使你报告在漂亮,领导也看不到.因此如果想向领导汇报工作,不仅需要提供更直观的测试报告. ...

  7. Lock和synchronized比较详解(转)

    从Java5之后,在java.util.concurrent.locks包下提供了另外一种方式来实现同步访问,那就是Lock. 也许有朋友会问,既然都可以通过synchronized来实现同步访问了, ...

  8. Intel FPGA 专用时钟引脚是否可以用作普通输入,输出或双向IO使用?

    原创 by DeeZeng FPGA 的 CLK pin 是否可以用作普通输入 ,输出或双向IO 使用?    这些专用Clock input pin 是否可以当作 inout用,需要看FPGA是否支 ...

  9. Devops-运维效率之数据迁移自动化

    overmind系统上线三个月,累计执行任务800+,自动审核执行SQL超过5000条,效率提升相当明显,离"一杯咖啡,轻松运维"的目标又进了一步. 写在前边 overmind系统 ...

  10. mongo去重统计

    表名:parkUserCost id: patkId: userId: phone: costVal: 适合特定条件下,对某些字段进行去重筛选.(比如限定抢购) 第一种,使用\(first操作符.\) ...