多线程 -- H2O 生成、交替打印字符串
一、题目:https://leetcode-cn.com/problems/building-h2o
现在有两种线程,氧 oxygen 和氢 hydrogen,你的目标是组织这两种线程来产生水分子。
存在一个屏障(barrier)使得每个线程必须等候直到一个完整水分子能够被产生出来。
氢和氧线程会被分别给予 releaseHydrogen 和 releaseOxygen 方法来允许它们突破屏障。
这些线程应该三三成组突破屏障并能立即组合产生一个水分子。
你必须保证产生一个水分子所需线程的结合必须发生在下一个水分子产生之前。
换句话说:
如果一个氧线程到达屏障时没有氢线程到达,它必须等候直到两个氢线程到达。
如果一个氢线程到达屏障时没有其它线程到达,它必须等候直到一个氧线程和另一个氢线程到达。
书写满足这些限制条件的氢、氧线程同步代码。
示例 1:
输入: "HOH"
输出: "HHO"
解释: "HOH" 和 "OHH" 依然都是有效解。
题解:O线程需要等待2个H线程才能执行,可用Semaphore信号量,O线程等待2个许可,释放2个许可,H线程等待1个许可
import java.util.concurrent.Semaphore; /** H2O 生成 现在有两种线程,氧 oxygen 和氢 hydrogen,你的目标是组织这两种线程来产生水分子。 存在一个屏障(barrier)使得每个线程必须等候直到一个完整水分子能够被产生出来。 氢和氧线程会被分别给予 releaseHydrogen 和 releaseOxygen 方法来允许它们突破屏障。 这些线程应该三三成组突破屏障并能立即组合产生一个水分子。 你必须保证产生一个水分子所需线程的结合必须发生在下一个水分子产生之前。 换句话说: 如果一个氧线程到达屏障时没有氢线程到达,它必须等候直到两个氢线程到达。 如果一个氢线程到达屏障时没有其它线程到达,它必须等候直到一个氧线程和另一个氢线程到达。 书写满足这些限制条件的氢、氧线程同步代码。 示例 1: 输入: "HOH" 输出: "HHO" 解释: "HOH" 和 "OHH" 依然都是有效解。 https://leetcode-cn.com/problems/building-h2o * @author jy.cui * @version 1.0 * @date 2020/10/21 19:52 */ class H2O { private Semaphore h = new Semaphore(2); //初始化2个许可 private Semaphore o = new Semaphore(0); //初始化0个许可,等待H的输出,可为0-2,此时输出为HHO public H2O() { } public void hydrogen(Runnable releaseHydrogen) throws InterruptedException { h.acquire(); //需要1个许可 releaseHydrogen.run(); o.release(); } public void oxygen(Runnable releaseOxygen) throws InterruptedException { o.acquire(2); //需要两个许可 releaseOxygen.run(); h.release(2); } }
测试类:
private static ThreadPoolExecutor pool = new ThreadPoolExecutor(5, 10, 1L, TimeUnit.SECONDS, new ArrayBlockingQueue(1000)); public static void main(String[] args) throws InterruptedException { H2O h2O = new H2O(); for (int i = 0; i < 5; i++){ pool.submit(()->{ try { h2O.oxygen(new O()); } catch (InterruptedException e) { e.printStackTrace(); } }); pool.submit(()->{ try { h2O.hydrogen(new H()); } catch (InterruptedException e) { e.printStackTrace(); } }); pool.submit(()->{ try { h2O.hydrogen(new H()); } catch (InterruptedException e) { e.printStackTrace(); } }); } pool.shutdown(); } static class H implements Runnable { @Override public void run() { System.out.print("H"); } } static class O implements Runnable { @Override public void run() { System.out.print("O"); } }
二、交替打印字符串 https://leetcode-cn.com/problems/fizz-buzz-multithreaded
编写一个可以从 1 到 n 输出代表这个数字的字符串的程序,但是:
如果这个数字可以被 3 整除,输出 "fizz"。
如果这个数字可以被 5 整除,输出 "buzz"。
如果这个数字可以同时被 3 和 5 整除,输出 "fizzbuzz"。
例如,当 n = 15,输出: 1, 2, fizz, 4, buzz, fizz, 7, 8, fizz, buzz, 11, fizz, 13, 14, fizzbuzz。
请你实现一个有四个线程的多线程版 FizzBuzz, 同一个 FizzBuzz 实例会被如下四个线程使用:
线程A将调用 fizz() 来判断是否能被 3 整除,如果可以,则输出 fizz。
线程B将调用 buzz() 来判断是否能被 5 整除,如果可以,则输出 buzz。
线程C将调用 fizzbuzz() 来判断是否同时能被 3 和 5 整除,如果可以,则输出 fizzbuzz。
线程D将调用 number() 来实现输出既不能被 3 整除也不能被 5 整除的数字。
题解:四个线程,num判断条件唤醒其他相关线程解决。
解法一:信号量,等待其他线程的许可
import java.util.concurrent.Semaphore; import java.util.function.IntConsumer; /** * 编写一个可以从 1 到 n 输出代表这个数字的字符串的程序,但是: * <p> * 如果这个数字可以被 3 整除,输出 "fizz"。 * 如果这个数字可以被 5 整除,输出 "buzz"。 * 如果这个数字可以同时被 3 和 5 整除,输出 "fizzbuzz"。 * 例如,当 n = 15,输出: 1, 2, fizz, 4, buzz, fizz, 7, 8, fizz, buzz, 11, fizz, 13, 14, fizzbuzz。 * <p> * 请你实现一个有四个线程的多线程版 FizzBuzz, 同一个 FizzBuzz 实例会被如下四个线程使用: * <p> * 线程A将调用 fizz() 来判断是否能被 3 整除,如果可以,则输出 fizz。 * 线程B将调用 buzz() 来判断是否能被 5 整除,如果可以,则输出 buzz。 * 线程C将调用 fizzbuzz() 来判断是否同时能被 3 和 5 整除,如果可以,则输出 fizzbuzz。 * 线程D将调用 number() 来实现输出既不能被 3 整除也不能被 5 整除的数字。 * <p> * https://leetcode-cn.com/problems/fizz-buzz-multithreaded * * @author jy.cui * @version 1.0 * @date 2020/10/22 14:04 */ public class FizzBuzz { private Semaphore fizz = new Semaphore(0); private Semaphore buzz = new Semaphore(0); private Semaphore fizzbuzz = new Semaphore(0); private Semaphore num = new Semaphore(1); private int n; public FizzBuzz(int n) { this.n = n; } // printFizz.run() outputs "fizz". public void fizz(Runnable printFizz) throws InterruptedException { for (int i = 0; i < (n / 3 - n / 15); i++) { fizz.acquire(); printFizz.run(); num.release(); } } // printBuzz.run() outputs "buzz". public void buzz(Runnable printBuzz) throws InterruptedException { for (int i = 0; i < (n / 5 - n / 15); i++) { buzz.acquire(); printBuzz.run(); num.release(); } } // printFizzBuzz.run() outputs "fizzbuzz". public void fizzbuzz(Runnable printFizzBuzz) throws InterruptedException { for (int i = 0; i < n / 15; i++) { fizzbuzz.acquire(); printFizzBuzz.run(); num.release(); } } // printNumber.accept(x) outputs "x", where x is an integer. public void number(IntConsumer printNumber) throws InterruptedException { for (int i = 1; i <= n; i++) { num.acquire(); if (i % 15 == 0) { fizzbuzz.release(); } else if (i % 3 == 0) { fizz.release(); } else if (i % 5 == 0) { buzz.release(); } else { printNumber.accept(i); num.release(); } } } }
解法二:lock的条件量,根据情况唤醒其他线程,注意需要多个标记与等待线程互斥,之前用一个标记不能判断fizz等线程先signal再wait情况
public class FizzBuzz { private ReentrantLock lock = new ReentrantLock(); private Condition fizz = lock.newCondition(); private Condition buzz = lock.newCondition(); private Condition fizzbuzz = lock.newCondition(); private Condition num = lock.newCondition(); //之前只有一个标记,这样的话有可能fizz signal后wait造成死锁 private volatile boolean flagN = false; // n释放标记 private volatile boolean flagF = false; // f释放标记 private volatile boolean flagB = false; // b释放标记 private volatile boolean flagFB = false; // fb释放标记 private int n; public FizzBuzz(int n) { this.n = n; } // printFizz.run() outputs "fizz". public void fizz(Runnable printFizz) throws InterruptedException { lock.lock(); try { for (int i = 0; i < (n / 3 - n / 15); i++) { if(!flagF){ fizz.await(); } printFizz.run(); flagF = false; num.signal(); } }finally { lock.unlock(); } } // printBuzz.run() outputs "buzz". public void buzz(Runnable printBuzz) throws InterruptedException { lock.lock(); try { for (int i = 0; i < (n / 5 - n / 15); i++) { if(!flagB){ buzz.await(); } printBuzz.run(); flagB = false; num.signal(); } }finally { lock.unlock(); } } // printFizzBuzz.run() outputs "fizzbuzz". public void fizzbuzz(Runnable printFizzBuzz) throws InterruptedException { lock.lock(); try { for (int i = 0; i < n / 15; i++) { if(!flagFB){ fizzbuzz.await(); } printFizzBuzz.run(); flagFB = false; num.signal(); } }finally { lock.unlock(); } } // printNumber.accept(x) outputs "x", where x is an integer. public void number(IntConsumer printNumber) throws InterruptedException { lock.lock(); try{ for (int i = 1; i <= n; i++) { if(flagN || flagF || flagB || flagFB){ num.await(); } if (i % 15 == 0) { flagFB = true; fizzbuzz.signal(); } else if (i % 3 == 0) { flagF = true; fizz.signal(); } else if (i % 5 == 0) { flagB = true; buzz.signal(); } else { printNumber.accept(i); flagN = false; num.signal(); } } }finally { lock.unlock(); } } }
解法三:
synchronized锁,每个线程判断是否满足当前输出条件,不满足等待,满足则输出结果,唤醒所有线程,所有线程唤醒后再次验证。此种方法效率最高
public class FizzBuzz { int i = 1; private int n; public FizzBuzz(int n) { this.n = n; } // printFizz.run() outputs "fizz". public void fizz(Runnable printFizz) throws InterruptedException { while (i <= n){ synchronized (this){ if(!verifyFizz(i)){ // 不满足fizz输出条件等待 wait(); } if(!verifyFizz(i)){ // notifyAll被唤醒后再验证1次 continue; } if(i > n){ break; } printFizz.run(); i++; notifyAll(); } } } // printBuzz.run() outputs "buzz". public void buzz(Runnable printBuzz) throws InterruptedException { while (i <= n){ synchronized (this){ if(!verifyBuzz(i)){ wait(); } if(!verifyBuzz(i)){ continue; } if(i > n){ break; } printBuzz.run(); i++; notifyAll(); } } } // printFizzBuzz.run() outputs "fizzbuzz". public void fizzbuzz(Runnable printFizzBuzz) throws InterruptedException { while (i <= n){ synchronized (this){ if(!verifyFizzBuzz(i)){ wait(); } if(!verifyFizzBuzz(i)){ continue; } if(i > n){ break; } printFizzBuzz.run(); i++; notifyAll(); } } } // printNumber.accept(x) outputs "x", where x is an integer. public void number(IntConsumer printNumber) throws InterruptedException { while (i <= n){ synchronized (this){ //不满足输出条件等待,唤醒后验证 if(verifyBuzz(i) || verifyFizz(i) || verifyFizzBuzz(i)){ wait(); } if(verifyBuzz(i) || verifyFizz(i) || verifyFizzBuzz(i)){ continue; } if(i > n){ break; } printNumber.accept(i); i++; notifyAll(); } } } private boolean verifyFizz(int i) { return i % 3 == 0 && i % 15 != 0; } private boolean verifyBuzz(int i) { return i % 5 == 0 && i % 15 != 0; } private boolean verifyFizzBuzz(int i) { return i % 15 == 0; } }
测试类:
private static ThreadPoolExecutor pool = new ThreadPoolExecutor(5, 10, 1L, TimeUnit.SECONDS, new ArrayBlockingQueue(1000)); public static void main(String[] args) throws InterruptedException { FizzBuzz fizzBuzz = new FizzBuzz(15); pool.submit(()->{ try { fizzBuzz.number(new O()); } catch (InterruptedException e) { e.printStackTrace(); } }); pool.submit(()->{ try { fizzBuzz.fizz(new H("fizz")); } catch (InterruptedException e) { e.printStackTrace(); } }); pool.submit(()->{ try { fizzBuzz.buzz(new H("buzz")); } catch (InterruptedException e) { e.printStackTrace(); } }); pool.submit(()->{ try { fizzBuzz.fizzbuzz(new H("fizebuzz")); } catch (InterruptedException e) { e.printStackTrace(); } }); pool.shutdown(); } static class H implements Runnable { String v; public H(String v) { this.v = v; } @Override public void run() { System.out.println(v); } } static class O implements IntConsumer { @Override public void accept(int value) { System.out.println(value); } }