编写一个程序,开启 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 1
道理我都懂, 但是你在一个要求严格执行顺序的地方使用多线程的原因是什么? 有什么实际应用场景吗?
|
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,优雅点
|