原題鏈接:https://leetcode-cn.com/problems/print-zero-even-odd/
問題:
死磕了2天了,不弄明白切雞兒。大概是在幾百次運行后會有明顯的同步問題導致結果錯誤,有時也會莫名其妙的阻塞住。不知道老哥們有何高見,換解法就不必了~
我的解法:
main方法測驗用了輸出重定向,然后獲取列印值進行判斷,和期望值不符則break。同時還依賴一個把類似Consumer函式封裝成Runnable并try catch例外的介面,嫌麻煩可以自己寫測驗。
public class Solution1116 {
static CountDownLatch cdl = new CountDownLatch(3);
public static void main(String[] args) {
ZeroEvenOdd zeo = new ZeroEvenOdd(9);
// 重定向輸出流相關操作
ByteArrayOutputStream baos = new ByteArrayOutputStream();
PrintStream ps = new PrintStream(baos);
PrintStream old = System.out;
System.setOut(ps);
// 初始化執行緒和3個Runnable
IntConsumer ic = System.out::print;
ExecutorService es = Executors.newFixedThreadPool(3);
Runnable r0 = ConsumerCatch.runnableWrap(zeo::zero, ic); // 需要ConsumerCatch介面的支持
Runnable r1 = ConsumerCatch.runnableWrap(zeo::odd, ic);
Runnable r2 = ConsumerCatch.runnableWrap(zeo::even, ic);
while (true) {
es.submit(r0);
es.submit(r1);
es.submit(r2);
try {
cdl.await(); // 阻塞主執行緒
} catch (InterruptedException e) {
e.printStackTrace();
}
// 列印并比對結果
ps.flush();
System.setOut(old);
String s = baos.toString();
System.out.println(s);
if (!s.equals("010203040506070809")) {
break;
}
// 全體重置
baos.reset();
cdl = new CountDownLatch(3);
zeo.reset();
System.setOut(ps);
}
}
private static class ZeroEvenOdd {
private int n;
private volatile int current = 1; // 棄用
private AtomicInteger ai = new AtomicInteger(1);
private volatile Thread t0 = null;
private volatile Thread t1 = null;
private volatile Thread t2 = null;
public ZeroEvenOdd(int n) {
this.n = n;
}
// 測驗用,用來使該物件恢復到初始狀態
public void reset() {
ai.set(1);
t0 = null;
t1 = null;
t2 = null;
}
// printNumber.accept(x) outputs "x", where x is an integer.
public void zero(IntConsumer printNumber) throws InterruptedException {
t0 = Thread.currentThread();
for (int i = 0; i < n; i++) {
printNumber.accept(0);
while (t1 == null || t2 == null) {} // 自旋保證初始化 + 記憶體屏障
if ((ai.get() & 1) == 1)
LockSupport.unpark(t1);
else
LockSupport.unpark(t2);
if (i != n - 1)
LockSupport.park();
}
Solution1116.cdl.countDown(); // 僅測驗,用來給主執行緒放行
}
public void even(IntConsumer printNumber) throws InterruptedException {
t2 = Thread.currentThread();
for (int i = 0; i < n / 2; i++) {
LockSupport.park();
printNumber.accept(ai.getAndIncrement());
LockSupport.unpark(t0);
}
Solution1116.cdl.countDown(); // 僅測驗,用來給主執行緒放行
}
public void odd(IntConsumer printNumber) throws InterruptedException {
t1 = Thread.currentThread();
for (int i = 0; i < n - n / 2; i++) {
LockSupport.park();
printNumber.accept(ai.getAndIncrement());
LockSupport.unpark(t0);
}
Solution1116.cdl.countDown(); // 僅測驗,用來給主執行緒放行
}
}
}
main方法所依賴的介面:
主要是為了少寫try catch,就整了一個
@FunctionalInterface
public interface ConsumerCatch<T>{
static <T> Runnable runnableWrap(ConsumerCatch<T> ct, T t) {
return () -> {
try {
ct.accept(t);
} catch (Exception e) {
e.printStackTrace();
}
};
}
static <T> Thread threadWrap(ConsumerCatch<T> ct, T t) {
return new Thread(() -> {
try {
ct.accept(t);
} catch (Exception e) {
e.printStackTrace();
}
});
}
void accept(T t) throws Exception;
}
uj5u.com熱心網友回復:
像這種運行N次才出現問題的多執行緒程式,你們會怎么debug呢。我表示很缺這個方法論,知道了這個問題我也能自己再死磕下去了。uj5u.com熱心網友回復:
好了,我知道問題在哪了。
if (i != n - 1)
LockSupport.park();
問題就處在zero方法的這個if判斷上。它導致每次運行park和unpark的次數不同,從而每次都會給一個執行緒多發放一次permit,也就是下一次park的豁免權。去掉就沒有問題了。
至于為什么使用fixed執行緒池,這個錯誤會最多扛到5000次運行才出現,而每次都新創建執行緒這個錯誤只要2次就出現,目前我還無法解釋。需要了解這個permit到底是跟誰走的,如果跟執行緒走,那么每次新建執行緒肯定不享有之前的豁免權,這就很反直覺。
轉載請註明出處,本文鏈接:https://www.uj5u.com/houduan/53710.html
標籤:Java SE
上一篇:SP檔案:[/showAllStudent.jsp] 的第 [23] 行發生了一個錯誤 StudentDB cannot be resolved to a ty
