
编写一个程序,开启 3 个线程 A,B,C,这三个线程的输出分别为 A、B、C,每个线程将自己的 输出在屏幕上打印 10 遍,要求输出的结果必须按顺序显示。如:ABCABCABC....
核心在于多线程同步
完整代码: https://github.com/mightofcode/javacc
缺点是轮询白耗 CPU,性能很差
package com.mocyx.javacc.printer; import org.springframework.stereotype.Component; import java.util.ArrayList; import java.util.List; import java.util.concurrent.atomic.AtomicInteger; /** * 轮询 AtomicInteger 实现交替输出 ABC * @author Administrator */ @Component public class PollingPrinter implements Printer { private final AtomicInteger atomicInteger = new AtomicInteger(0); class Worker implements Runnable { private String pstr; private int index; private int gap; private int max; public Worker(String pstr, int index, int gap, int max) { this.pstr = pstr; this.index = index; this.gap = gap; this.max = max; } @Override public void run() { while (true) { int v = atomicInteger.get(); if (v == max) { return; } else { if (v % gap == index) { System.out.print(pstr); atomicInteger.set(v + 1); } } } } } @Override public void print() { List<Thread> threads = new ArrayList<>(); threads.add(new Thread(new Worker("A", 0, 3, 30))); threads.add(new Thread(new Worker("B", 1, 3, 30))); threads.add(new Thread(new Worker("C", 2, 3, 30))); for (Thread t : threads) { t.start(); } try { for (Thread t : threads) { t.join(); } } catch (Exception e) { e.printStackTrace(); } } } 使用 Lock & Condition 要注意: 1 检查条件谓词,避免信号丢失和过早唤醒 2 注意在 finally 中进行 unlock,否则出现异常会 hang 住
package com.mocyx.javacc.printer; import org.springframework.stereotype.Component; import java.util.ArrayList; import java.util.List; import java.util.concurrent.locks.Condition; import java.util.concurrent.locks.Lock; import java.util.concurrent.locks.ReentrantLock; /** * 使用 Lock and Condition 进行同步 * * @author Administrator */ @Component public class SignalPrinter implements Printer { private final Lock lock = new ReentrantLock(); private volatile int counter = 0; class Worker implements Runnable { Condition curCondition; Condition nextCondition; String pstr; int max; int index; int gap; public Worker(String pstr, int index, int gap, int max, Condition curCondition, Condition nextCondition) { this.pstr = pstr; this.max = max; this.curCOndition= curCondition; this.nextCOndition= nextCondition; this.index = index; this.gap = gap; } private boolean isMyTurn() { return counter % gap == index; } @Override public void run() { while (true) { lock.lock(); try { while (!isMyTurn()) { try { curCondition.await(); } catch (InterruptedException e) { e.printStackTrace(); } } if (counter < max) { System.out.print(pstr); } counter += 1; nextCondition.signalAll(); } finally { lock.unlock(); } if (counter >= max) { return; } } } } @Override public void print() { List<Thread> threads = new ArrayList<>(); List<Condition> cOnditions= new ArrayList<>(); conditions.add(lock.newCondition()); conditions.add(lock.newCondition()); conditions.add(lock.newCondition()); threads.add(new Thread(new Worker("A", 0, 3, 30, conditions.get(0), conditions.get(1)))); threads.add(new Thread(new Worker("B", 1, 3, 30, conditions.get(1), conditions.get(2)))); threads.add(new Thread(new Worker("C", 2, 3, 30, conditions.get(2), conditions.get(0)))); for (Thread t : threads) { t.start(); } try { for (Thread t : threads) { t.join(); } } catch (Exception e) { e.printStackTrace(); } } } 相比 Lock & Condition,使用 Semphore 代码比较简洁,不容易出错
package com.mocyx.javacc.printer; import org.springframework.stereotype.Component; import java.util.ArrayList; import java.util.List; import java.util.concurrent.Semaphore; /** * @author Administrator */ @Component public class SemaphorePrinter implements Printer { class Worker implements Runnable { private String pstr; private Semaphore curSemphore; private Semaphore nextSemphore; private int count = 0; Worker(String pstr, int count, Semaphore curSemphore, Semaphore nextSemphore) { this.pstr = pstr; this.count = count; this.curSemphore = curSemphore; this.nextSemphore = nextSemphore; } @Override public void run() { for (int i = 0; i < count; i++) { try { curSemphore.acquire(1); System.out.print(pstr); nextSemphore.release(1); } catch (InterruptedException e) { e.printStackTrace(); } } } } @Override public void print() { List<Thread> threads = new ArrayList<>(); List<Semaphore> semaphores = new ArrayList<>(); semaphores.add(new Semaphore(0)); semaphores.add(new Semaphore(0)); semaphores.add(new Semaphore(0)); threads.add(new Thread(new Worker("A", 10, semaphores.get(0), semaphores.get(1)))); threads.add(new Thread(new Worker("B", 10, semaphores.get(1), semaphores.get(2)))); threads.add(new Thread(new Worker("C", 10, semaphores.get(2), semaphores.get(0)))); for (Thread t : threads) { t.start(); } semaphores.get(0).release(1); try { for (Thread t : threads) { t.join(); } } catch (Exception e) { e.printStackTrace(); } } } 思路跟 go channel 类似,通过 BlockingQueue 传递信息
package com.mocyx.javacc.printer; import java.util.ArrayList; import java.util.List; import java.util.concurrent.ArrayBlockingQueue; import java.util.concurrent.BlockingQueue; /** * 使用阻塞队列进行同步 * * @author Administrator */ public class QueuePrinter implements Printer { static class Msg { public static final Msg PRINT_SUCCESS = new Msg(); public static final Msg PRINT = new Msg(); public static final Msg QUIT = new Msg(); } class Channel { BlockingQueue<Msg> inQueue = new ArrayBlockingQueue<Msg>(100); BlockingQueue<Msg> outQueue = new ArrayBlockingQueue<Msg>(100); } class Worker implements Runnable { Channel inChannel; String pstr; Worker(String pstr, Channel inChannel) { this.inChannel = inChannel; this.pstr = pstr; } @Override public void run() { while (true) { try { Msg msg = inChannel.inQueue.take(); if (msg == Msg.PRINT) { System.out.print(pstr); inChannel.outQueue.put(Msg.PRINT_SUCCESS); } else if (msg == Msg.QUIT) { return; } } catch (InterruptedException e) { e.printStackTrace(); } } } } @Override public void print() { List<Thread> threads = new ArrayList<>(); List<Channel> channels = new ArrayList<>(); channels.add(new Channel()); channels.add(new Channel()); channels.add(new Channel()); threads.add(new Thread(new Worker("A", channels.get(0)))); threads.add(new Thread(new Worker("B", channels.get(1)))); threads.add(new Thread(new Worker("C", channels.get(2)))); for (Thread t : threads) { t.start(); } for (int i = 0; i < 30; i++) { try { channels.get(i % channels.size()).inQueue.put(Msg.PRINT); channels.get(i % channels.size()).outQueue.take(); } catch (InterruptedException e) { e.printStackTrace(); } } for (Channel c : channels) { c.inQueue.add(Msg.QUIT); } try { for (Thread t : threads) { t.join(); } } catch (Exception e) { e.printStackTrace(); } } } 这个方法确实能工作,但是可能会被面试官打
package com.mocyx.javacc.printer; import java.util.ArrayList; import java.util.List; /** * sleep and sleep * * @author Administrator */ public class SleepPrinter implements Printer { static class Worker implements Runnable { private String printStr; private int sleepGap; private int delay; private int count; public Worker(String printStr, int delay, int sleepGap, int count) { this.printStr = printStr; this.sleepGap = sleepGap; this.delay = delay; this.count = count; } @Override public void run() { try { Thread.sleep(delay); } catch (InterruptedException e) { e.printStackTrace(); } for (int i = 0; i < count; i++) { try { Thread.sleep(sleepGap); System.out.print(printStr); } catch (InterruptedException e) { e.printStackTrace(); } } } } @Override public void print() { List<Thread> threads = new ArrayList<>(); threads.add(new Thread(new Worker("A", 00, 30, 10))); threads.add(new Thread(new Worker("B", 10, 30, 10))); threads.add(new Thread(new Worker("C", 20, 30, 10))); for (Thread t : threads) { t.start(); } try { for (Thread t : threads) { t.join(); } } catch (Exception e) { e.printStackTrace(); } } } 1 lihongjie0209 2020-01-01 16:24:33 +08:00 道理我都懂, 但是你在一个要求严格执行顺序的地方使用多线程的原因是什么? 有什么实际应用场景吗? |
2 HuHui 2020-01-01 17:06:14 +08:00 via Android @lihongjie0209 面试就挺实际 |
3 mightofcode OP @lihongjie0209 工作中没机会用,拿面试题练练手 |
4 inwar 2020-01-01 18:58:51 +08:00 via Android 应该把打印改成一个耗时任务,结果顺序输出,这样更实际一点 |
5 1194129822 2020-01-02 11:49:12 +08:00 现在所有 Java 类型的赋值操作是原子性的,所以 volatile 加 yield 不香吗?这样即使是轮询也查不到相当于阻塞了,CPU 消耗很少。 |
6 mightofcode OP @1194129822 为啥轮询查不到相当于阻塞了? yield 并不一定导致线程切换吧 |
7 luxinfl 2020-01-03 14:52:53 +08:00 认真的说,我一个都不会 |
8 luxinfl 2020-01-03 16:19:28 +08:00 while (true){ lock.lock(); if(in % 3 != index){ lock.unlock(); continue; } System.out.print(Thread.currentThread().getName()); in++; if(in >31){ lock.unlock(); return; } lock.unlock(); } 这样写会不会很 ugly |
9 mightofcode OP @luxinfl 还好,本质上跟方案 1 用 AtomicInteger 是一个策略:轮询 |
10 kkkkkrua 2020-01-09 17:07:02 +08:00 用 condition,优雅点 |