1.LeetCode 1114 按序打印
题目:设计修改程序,以确保 two() 方法在 one() 方法之后被执行,three() 方法在 two() 方法之后被执行。
方法1: 用两个boolean变量flag1和flag2控制,当第一个线程结束,flag1=true; 线程2只有当flag1=true时,才能执行; 线程3只有当flag2=true时,才能执行。 |
class Foo { public Foo() { } Thread t1,t2; boolean flag1 = false; boolean flag2 = false; public void first(Runnable printFirst) throws InterruptedException { // printFirst.run() outputs "first". Do not change or remove this line. // t1 = Thread.currentThread(); printFirst.run(); flag1 = true; } public void second(Runnable printSecond) throws InterruptedException { // printSecond.run() outputs "second". Do not change or remove this line. while(!flag1){} printSecond.run(); flag2 = true; } public void third(Runnable printThird) throws InterruptedException { // printThird.run() outputs "third". Do not change or remove this line. while(!flag2){} // t2.join(); printThird.run(); } } |
|
方法2:join 获取执行first的线程t1和执行second的线程t2,t1和t2初始值为null; 当获取到t1时,t2会等到t1执行完才继续执行; 同样,t3会等到t2执行完之后,才会执行。 |
class Foo { Thread t1,t2; public Foo() { } public void first(Runnable printFirst) throws InterruptedException { t1 = Thread.currentThread(); printFirst.run(); } public void second(Runnable printSecond) throws InterruptedException { while(t1 == null){}; t1.join(); t2 = Thread.currentThread(); printSecond.run(); } public void third(Runnable printThird) throws InterruptedException { while(t2 == null){}; t2.join(); printThird.run(); } } |
|
方法3:wait/notifyAll 使用整型变量mark控制线程2和线程3的执行顺序, wait/notifyAll方法需要配合synchronized代码块使用,线程1执行之后,会释放锁。 |
class Foo { private int mark = 0; private Object lock = new Object(); public Foo() { } public void first(Runnable printFirst) throws InterruptedException { synchronized(lock){ printFirst.run(); mark = 1; lock.notifyAll(); } } public void second(Runnable printSecond) throws InterruptedException { synchronized(lock){ while (mark != 1){ lock.wait(); } printSecond.run(); mark = 2; lock.notifyAll(); } } public void third(Runnable printThird) throws InterruptedException { synchronized(lock){ while (mark != 2){ lock.wait(); } printThird.run(); // lock.notifyAll(); } } } |
|
方法4:CountDownLatch 用两个CountDownLatch实现,当第一个线程执行完毕,latch1减1为0,线程2就会被激活,当线程2执行完成,latch2减1为0,线程3就会被激活。 |
import java.util.concurrent.CountDownLatch; class Foo { CountDownLatch latch1 = new CountDownLatch(1); CountDownLatch latch2 = new CountDownLatch(1); public Foo() { } public void first(Runnable printFirst) throws InterruptedException { printFirst.run(); latch1.countDown(); } public void second(Runnable printSecond) throws InterruptedException { latch1.await(); printSecond.run(); latch2.countDown(); } public void third(Runnable printThird) throws InterruptedException { latch2.await(); printThird.run(); } } |
2.Leetcode 1195 交替打印字符串
题目:编写一个可以从 1 到 n 输出代表这个数字的字符串的程序,但是:
- 如果这个数字可以被 3 整除,输出 "fizz"。
- 如果这个数字可以被 5 整除,输出 "buzz"。
- 如果这个数字可以同时被 3 和 5 整除,输出 "fizzbuzz"。
请你实现一个有四个线程的多线程版 FizzBuzz, 同一个 FizzBuzz 实例会被如下四个线程使用:
- 线程A将调用 fizz() 来判断是否能被 3 整除,如果可以,则输出 fizz。
- 线程B将调用 buzz() 来判断是否能被 5 整除,如果可以,则输出 buzz。
- 线程C将调用 fizzbuzz() 来判断是否同时能被 3 和 5 整除,如果可以,则输出 fizzbuzz。
- 线程D将调用 number() 来实现输出既不能被 3 整除也不能被 5 整除的数字。
方法1:wait/notifyAll 给出一个数n,要遍历从1到n所有的数,这里用mark来表示每次遍历到的数, 4个线程之间是并行关系, 线程A的功能是找到只能被3且不能被5整除的数,否则就等待; 线程B的功能是找到只能被5且不能被3整除的数,否则就等待; 线程C的功能是找到只能被15整除的数,否则就等待; 线程D的功能是找到不能被3或5整除的数,否则就等待; |
class FizzBuzz { private int n; private int mark = 1; private Object lock = new Object(); public FizzBuzz(int n) { this.n = n; } // printFizz.run() outputs "fizz". public void fizz(Runnable printFizz) throws InterruptedException { while(mark <= n){ synchronized(lock){ if (mark % 3 != 0 || mark % 5 == 0){ // 不能被3整除 或 能被5整除 lock.wait(); continue; }else{ printFizz.run(); mark += 1; lock.notifyAll(); } } } } // printBuzz.run() outputs "buzz". public void buzz(Runnable printBuzz) throws InterruptedException { while(mark <= n){ synchronized(lock){ if (mark % 5 != 0 || mark % 3 == 0){ // 不能被5整除 或 能被3整除 lock.wait(); continue; }else{ printBuzz.run(); mark += 1; lock.notifyAll(); } } } } // printFizzBuzz.run() outputs "fizzbuzz". public void fizzbuzz(Runnable printFizzBuzz) throws InterruptedException { while(mark <= n){ synchronized(lock){ if (mark % 15 != 0){ // 能被15整除 lock.wait(); continue; }else{ printFizzBuzz.run(); mark += 1; lock.notifyAll(); } } } } // printNumber.accept(x) outputs "x", where x is an integer. public void number(IntConsumer printNumber) throws InterruptedException { while(mark <= n){ synchronized(lock){ if (mark % 3 == 0 || mark % 5 == 0){ // 能被3整除 或 能被5整除 lock.wait(); continue; }else{ printNumber.accept(mark); mark += 1; lock.notifyAll(); } } } } } |
|
方法2:semaphore 利用4个信号量实现,其中用number线程充当入口(信号量初始为1); 在number线程中先判断数字满足哪一个条件,如果满足其一,就将对应的信号量+1, 使得对应的线程获取到信号量,执行输出语句,然后释放number的信号量。 |
class FizzBuzz { private int n; private int mark = 0; private final Semaphore fizzSema = new Semaphore(0); private final Semaphore buzzSema = new Semaphore(0); private final Semaphore fizzbuzzSema = new Semaphore(0); //最开始只有number线程能够执行,其他线程的执行都受number线程的控制 private final Semaphore numberSema = new Semaphore(1); public FizzBuzz(int n) { this.n = n; } // printFizz.run() outputs "fizz". public void fizz(Runnable printFizz) throws InterruptedException { while(true){ fizzSema.acquire(); if(mark > n){break;} printFizz.run(); numberSema.release(); } } // printBuzz.run() outputs "buzz". public void buzz(Runnable printBuzz) throws InterruptedException { while(true){ buzzSema.acquire(); if(mark > n){break;} printBuzz.run(); numberSema.release(); } } // printFizzBuzz.run() outputs "fizzbuzz". public void fizzbuzz(Runnable printFizzBuzz) throws InterruptedException { while(true){ fizzbuzzSema.acquire(); if(mark > n){break;} printFizzBuzz.run(); numberSema.release(); } } // printNumber.accept(x) outputs "x", where x is an integer. public void number(IntConsumer printNumber) throws InterruptedException { while(mark <= n){ numberSema.acquire(); mark += 1; if(mark > n){break;} if(mark % 3 == 0 && mark % 5 != 0){ fizzSema.release(); }else if(mark % 5 == 0 && mark % 3 != 0){ buzzSema.release(); }else if(mark % 15 == 0){ fizzbuzzSema.release(); }else{ printNumber.accept(mark); numberSema.release(); } } // mark > n后,释放其他信号量使他们可以各自终止线程 fizzSema.release(); buzzSema.release(); fizzbuzzSema.release(); } } |
3.Leetcode 1115 交替打印FooBar
题目:我们提供一个类:
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"); } } }
两个不同的线程将会共用一个 FooBar 实例。其中一个线程将会调用 foo() 方法,另一个线程将会调用 bar() 方法。
请设计修改程序,以确保 "foobar" 被输出 n 次。
方法1:boolean变量+yield 用boolean变量isPrint控制线程先后,这里不用isPrint,执行会超时。 |
class FooBar { private int n; private boolean isPrint; public FooBar(int n) { this.n = n; } public void foo(Runnable printFoo) throws InterruptedException { for (int i = 0; i < n; i++) { // printFoo.run() outputs "foo". Do not change or remove this line. while(isPrint){ Thread.yield(); } printFoo.run(); isPrint = true; } } public void bar(Runnable printBar) throws InterruptedException { for (int i = 0; i < n; i++) { while(!isPrint){ Thread.yield(); } // printBar.run() outputs "bar". Do not change or remove this line. printBar.run(); isPrint = false; } } } |
|
方法2:wait/notify 用布尔型变量isPrint来控制两个线程的调用顺序 |
class FooBar { private int n; private boolean isPrint = false; public FooBar(int n) { this.n = n; } public void foo(Runnable printFoo) throws InterruptedException { for (int i = 0; i < n; i++) { synchronized(this){ while(isPrint){ //超过两个线程要用while,两个线程可以用if代替while this.wait(); } printFoo.run(); isPrint = true; this.notify(); } } } public void bar(Runnable printBar) throws InterruptedException { for (int i = 0; i < n; i++) { synchronized(this){ while(!isPrint){ this.wait(); } printBar.run(); isPrint = false; this.notify(); } } } } |
|
方法3:CountDownLatch 用两个CountDownLatch相互限制 |
class FooBar { private int n; private CountDownLatch countFoo; private CountDownLatch countBar; public FooBar(int n) { this.n = n; countFoo = new CountDownLatch(0); countBar = new CountDownLatch(1); } public void foo(Runnable printFoo) throws InterruptedException { for (int i = 0; i < n; i++) { // printFoo.run() outputs "foo". Do not change or remove this line. countFoo.await(); printFoo.run(); countFoo = new CountDownLatch(1); countBar.countDown(); } } public void bar(Runnable printBar) throws InterruptedException { for (int i = 0; i < n; i++) { // printBar.run() outputs "bar". Do not change or remove this line. countBar.await(); printBar.run(); countBar = new CountDownLatch(1); countFoo.countDown(); } } }
|
|
方法4:semaphore 两个线程互相控制对方的信号量,当一个线程执行完,释放另一个线程的信号量,并且等待自己的信号量。 |
import java.util.concurrent.Semaphore; public class FooBar { private int n; //here is the full path, or maybe cann't compile in leetcode. Semaphore semaphoreFoo=new Semaphore(0); Semaphore semaphoreBar=new Semaphore(0); public FooBar(int n) { this.n = n; } public void foo(Runnable printFoo) throws InterruptedException { for (int i = 0; i < n; i++) { printFoo.run(); //由于下面阻塞了,所以这里变为0,下面的方法就能继续执行 semaphoreBar.release(); //这里让他等一会,等到bar()执行完 semaphoreFoo.acquire(); } } public void bar(Runnable printBar) throws InterruptedException { for (int i = 0; i < n; i++) { // 进来先变为1,就会等上面的release()使他变为0,才进行,所以肯定在foo之后。 semaphoreBar.acquire(); printBar.run(); //bar()执行完了,就让foo()继续。 semaphoreFoo.release(); } } } |
4.Leetcode 1116 打印零与奇偶数
题目:假设有这么一个类:
class ZeroEvenOdd { public ZeroEvenOdd(int n) { ... } // 构造函数 public void zero(printNumber) { ... } // 仅打印出 0 public void even(printNumber) { ... } // 仅打印出 偶数 public void odd(printNumber) { ... } // 仅打印出 奇数 }
相同的一个 ZeroEvenOdd 类实例将会传递给三个不同的线程:
- 线程 A 将调用 zero(),它只输出 0 。
- 线程 B 将调用 even(),它只输出偶数。
- 线程 C 将调用 odd(),它只输出奇数。
每个线程都有一个 printNumber 方法来输出一个整数。请修改给出的代码以输出整数序列 010203040506... ,其中序列的长度必须为 2n。
方法1:semaphore 设置3个semaphore 先执行zeroSema输出0,再判断奇偶数; 如果是奇数就执行evenSema,计算1,3,5,...,n; 如果是偶数就执行oddSema,计算2,4,6,...,n; 每次奇偶数进程执行后,释放zeroSema,重新判断奇偶数。 |
package 多线程.leetcode; import java.util.concurrent.Semaphore; import java.util.function.IntConsumer; /** * 功能描述:打印0与奇偶数 * * @author nxf * @since 2020-06-10 */ class ZeroEvenOdd2 { private int n; private Semaphore zeroSema = new Semaphore(1); private Semaphore evenSema = new Semaphore(0); private Semaphore oddSema = new Semaphore(0); public ZeroEvenOdd2(int n) { this.n = n; } // printNumber.accept(x) outputs "x", where x is an integer. public void zero(IntConsumer printNumber) throws InterruptedException { for(int i=1; i<=n; i++){ zeroSema.acquire(); printNumber.accept(0); if(i % 2 == 0){ evenSema.release(); } else{ oddSema.release(); } } } public void even(IntConsumer printNumber) throws InterruptedException { for(int i=2; i <= n; i+=2){ evenSema.acquire(); printNumber.accept(i); zeroSema.release(); } } public void odd(IntConsumer printNumber) throws InterruptedException { for(int i=1; i <= n; i+=2){ oddSema.acquire(); printNumber.accept(i); zeroSema.release(); } } public static void main(String[] args) { try { char integer = (char) System.in.read(); System.out.println("传入n: " + integer); int input = (char) integer - (char) '0'; ZeroEvenOdd zeroEvenOdd = new ZeroEvenOdd(input); IntConsumer intConsumer = value -> System.out.println(value); new Thread(new Runnable() { @Override public void run() { try { zeroEvenOdd.zero(intConsumer); } catch (InterruptedException e) { e.printStackTrace(); } } }).start(); new Thread(new Runnable() { @Override public void run() { try { zeroEvenOdd.odd(intConsumer); } catch (InterruptedException e) { e.printStackTrace(); } } }).start(); new Thread(new Runnable() { @Override public void run() { try { zeroEvenOdd.even(intConsumer); } catch (InterruptedException e) { e.printStackTrace(); } } }).start(); } catch (Exception e) { e.printStackTrace(); } } } |
|