[Leetcode] 多線程

前言

Java 多線程解題常用方法 & 工具類

類型 方法 說明
synchronized Object.wait(), Object.notify(), Object.notifyAll() 每一個用 synchronized 修飾的部分都是一個臨界區,同一時間只允許一個線程訪問
Lock Lock.lock(), Lock.unlock() 具體實現類:ReentrantLock
Semaphore Semaphore.acquire(), Semaphore.release() 信號量是一種計數器,用來保護一個或多個共享資源的訪問
CountDownLatch CountDownLatch.await(), CountDownLatch.countDown() 在完成一組正在其他線程中執行的操作之前,允許線程一直等待
CyclicBarrier CyclicBarrier.await() 允許多個線程在某個集合點處進行互相等待
Phaser Phaser.arriveAndAwaitAdvance() 把併發任務分成多個階段運行,在開始下一階段之前,當前階段中的所有線程都必須執行完成

具體問題

按序打印

三個不同的線程將會共用一個 Foo 實例。

  • 線程 A 將會調用 one() 方法
  • 線程 B 將會調用 two() 方法
  • 線程 C 將會調用 three() 方法

請設計修改程序,以確保 two() 方法在 one() 方法之後被執行,three() 方法在 two() 方法之後被執行。

示例
輸入:[1,3,2]
輸出:"onetwothree"
解釋:輸入 [1,3,2] 表示線程 A 將會調用 one() 方法,線程 B 將會調用 three() 方法,線程 C 將會調用 two() 方法。
正確的輸出是 "onetwothree"。
解題思路
/* 解法一:synchronized */
class Foo {
	private Object lock;
	private boolean firstFlag, secondFlag;
	
    public Foo() {
        this.lock = new Object();
        this.firstFlag = false;
        this.secondFlag = false;
    }

    public void first(Runnable printFirst) throws InterruptedException {
        synchronized (lock) {
        	// printFirst.run() outputs "first". Do not change or remove this line.
        	printFirst.run();
        	firstFlag = true;
        	lock.notifyAll();
        }        
    }

    public void second(Runnable printSecond) throws InterruptedException {
        synchronized (lock) {
        	while (!firstFlag) {
        		lock.wait();
        	}
	        // printSecond.run() outputs "second". Do not change or remove this line.
	        printSecond.run();
	        secondFlag = true;
	        lock.notifyAll();
		}
    }

    public void third(Runnable printThird) throws InterruptedException {
        synchronized (lock) {
	        while (!secondFlag) {
	        	lock.wait();
	        }
	        // printThird.run() outputs "third". Do not change or remove this line.
	        printThird.run();	        
		}
    }
}

/* 解法二:Semaphore */
class Foo {
	private Semaphore semaphoreFirst, semaphoreSecond;
	
    public Foo() {
    	this.semaphoreFirst = new Semaphore(0); 
    	this.semaphoreSecond = new Semaphore(0);   
    }

    public void first(Runnable printFirst) throws InterruptedException {        
        // printFirst.run() outputs "first". Do not change or remove this line.
        printFirst.run();
        semaphoreFirst.release();
    }

    public void second(Runnable printSecond) throws InterruptedException {
        semaphoreFirst.acquire();
        // printSecond.run() outputs "second". Do not change or remove this line.
        printSecond.run();
        semaphoreSecond.release();
    }

    public void third(Runnable printThird) throws InterruptedException {
        semaphoreSecond.acquire();
        // printThird.run() outputs "third". Do not change or remove this line.
        printThird.run();
    }
}

/* 解法三:CountDownLatch */
class Foo {
	private CountDownLatch latchFirst, latchSecond;

    public Foo() {
    	this.latchFirst = new CountDownLatch(1);
    	this.latchSecond= new CountDownLatch(1);     
    }

    public void first(Runnable printFirst) throws InterruptedException {            
        // printFirst.run() outputs "first". Do not change or remove this line.
        printFirst.run();
        latchFirst.countDown();
    }

    public void second(Runnable printSecond) throws InterruptedException {
        latchFirst.await();
        // printSecond.run() outputs "second". Do not change or remove this line.
        printSecond.run();
        latchSecond.countDown();
    }

    public void third(Runnable printThird) throws InterruptedException {
        latchSecond.await();
        // printThird.run() outputs "third". Do not change or remove this line.
        printThird.run();
    }
}
交替打印FooBar

兩個不同的線程將會共用一個 FooBar 實例。其中一個線程將會調用 foo() 方法,另一個線程將會調用 bar() 方法。
請設計修改程序,以確保 “foobar” 被輸出 n 次。

示例
輸入:n = 2
輸出:"foobarfoobar"
解釋:"foobar" 將被輸出兩次。
解題思路
/* 解法一:synchronized */
class FooBar {
    private int n;
    private Object lock;
    private boolean flag;

    public FooBar(int n) {
        this.n = n;
        this.lock = new Object();
        this.flag = false;
    }

    public void foo(Runnable printFoo) throws InterruptedException {        
        for (int i = 0; i < n; i++) {
            synchronized (lock) {
                while (flag) {
                    lock.wait();
                }
                // printFoo.run() outputs "foo". Do not change or remove this line.
                printFoo.run();
                flag = true;
                lock.notifyAll();
            }
        }
    }

    public void bar(Runnable printBar) throws InterruptedException {        
        for (int i = 0; i < n; i++) {
            synchronized (lock) {
                while (!flag) {
                    lock.wait();
                }
                // printBar.run() outputs "bar". Do not change or remove this line.
                printBar.run();
                flag = false;
                lock.notifyAll();
            }
        }
    }
}

/* 解法二:Semaphore */
class FooBar {
    private int n;
    private Semaphore semaphoreFirst, semaphoreSecond;

    public FooBar(int n) {
        this.n = n;
        this.semaphoreFirst = new Semaphore(1);
        this.semaphoreSecond = new Semaphore(0);
    }

    public void foo(Runnable printFoo) throws InterruptedException {        
        for (int i = 0; i < n; i++) {
            semaphoreFirst.acquire();
        	// printFoo.run() outputs "foo". Do not change or remove this line.
        	printFoo.run();
            semaphoreSecond.release();
        }
    }

    public void bar(Runnable printBar) throws InterruptedException {        
        for (int i = 0; i < n; i++) {
            semaphoreSecond.acquire();
            // printBar.run() outputs "bar". Do not change or remove this line.
        	printBar.run();
            semaphoreFirst.release();
        }
    }
}
打印零與奇偶數

相同的一個 ZeroEvenOdd 類實例將會傳遞給三個不同的線程:

  • 線程 A 將調用 zero(),它只輸出 0 。
  • 線程 B 將調用 even(),它只輸出偶數。
  • 線程 C 將調用 odd(),它只輸出奇數。

每個線程都有一個 printNumber 方法來輸出一個整數。請修改給出的代碼以輸出整數序列 010203040506… ,其中序列的長度必須爲 2n。

示例
輸入:n = 2
輸出:"0102"
說明:三條線程異步執行,其中一個調用 zero(),另一個線程調用 even(),最後一個線程調用odd()。正確的輸出爲 "0102"。
解題思路
/* 解法一:synchronized */
class ZeroEvenOdd {
    private int n;
    private Object lock;
    private boolean zeroFlag, oddFlag, evenFlag;
    
    public ZeroEvenOdd(int n) {
        this.n = n;
        this.lock = new Object();
        this.zeroFlag = true;
        this.oddFlag = true;
        this.evenFlag = false;
    }

    // printNumber.accept(x) outputs "x", where x is an integer.
    public void zero(IntConsumer printNumber) throws InterruptedException {
        for (int i = 0; i < n; i++) {
            synchronized (lock) {
                while (!zeroFlag) {
                    lock.wait();
                }    
                printNumber.accept(0);
                zeroFlag = false;
                lock.notifyAll();
            }
        }    
    }

    public void odd(IntConsumer printNumber) throws InterruptedException {
        for (int i = 1; i <= n; i += 2) {
            synchronized (lock) {
                while (zeroFlag || !oddFlag) {
                    lock.wait();
                }
                printNumber.accept(i);
                zeroFlag = true;
                oddFlag = false;
                evenFlag = true;                
                lock.notifyAll();
            }
        }    
    }

    public void even(IntConsumer printNumber) throws InterruptedException {
        for (int i = 2; i <= n; i += 2) {
            synchronized (lock) {
                while (zeroFlag || !evenFlag) {
                    lock.wait();
                }
                printNumber.accept(i);
                zeroFlag = true;
                oddFlag = true;
                evenFlag = false;                
                lock.notifyAll();
            }
        }    
    }
}

/* 解法二:Semaphore */
class ZeroEvenOdd {
    private int n;
    private Semaphore semaphoreZero, semaphoreOdd, semaphoreEven;
    
    public ZeroEvenOdd(int n) {
        this.n = n;
        this.semaphoreZero = new Semaphore(1);
        this.semaphoreOdd = new Semaphore(0);
        this.semaphoreEven = new Semaphore(0);
    }

    // printNumber.accept(x) outputs "x", where x is an integer.
    public void zero(IntConsumer printNumber) throws InterruptedException {
        for (int i = 0; i < n; i++) {
            semaphoreZero.acquire();
            printNumber.accept(0);
            if (i % 2 == 0) {
                semaphoreOdd.release();
            } else {
                semaphoreEven.release();
            }
        }    
    }
    
    public void odd(IntConsumer printNumber) throws InterruptedException {
        for (int i = 1; i <= n; i += 2) {
            semaphoreOdd.acquire();
            printNumber.accept(i);
            semaphoreZero.release();
        }    
    }

    public void even(IntConsumer printNumber) throws InterruptedException {
        for (int i = 2; i <= n; i += 2) {
            semaphoreEven.acquire();
            printNumber.accept(i);
            semaphoreZero.release();
        }    
    }
}
H2O 生成

現在有兩種線程,氫 oxygen 和氧 hydrogen,你的目標是組織這兩種線程來產生水分子。存在一個屏障(barrier)使得每個線程必須等候直到一個完整水分子能夠被產生出來。氫和氧線程會被分別給予 releaseHydrogen 和 releaseOxygen 方法來允許它們突破屏障。這些線程應該三三成組突破屏障並能立即組合產生一個水分子。你必須保證產生一個水分子所需線程的結合必鬚髮生在下一個水分子產生之前。
換句話說:

  • 如果一個氧線程到達屏障時沒有氫線程到達,它必須等候直到兩個氫線程到達。
  • 如果一個氫線程到達屏障時沒有其它線程到達,它必須等候直到一個氧線程和另一個氫線程到達。

書寫滿足這些限制條件的氫、氧線程同步代碼。

示例
輸入:"OOHHHH"
輸出:"HHOHHO"
解釋:"HOHHHO", "OHHHHO", "HHOHOH", "HOHHOH", "OHHHOH", "HHOOHH", "HOHOHH" 和 "OHHOHH" 依然都是有效解。
解題思路
/* 解法一:synchronized */
class H2O {
	private Object lock;
	private int hCount;
	
    public H2O() {
        this.lock = new Object();
        this.hCount = 0;
    }

    public void hydrogen(Runnable releaseHydrogen) throws InterruptedException {
		synchronized (lock) {
			while (hCount == 2) {
				lock.wait();
			}
	        // releaseHydrogen.run() outputs "H". Do not change or remove this line.
	        releaseHydrogen.run();
			hCount++;
			lock.notifyAll();
		}
    }

    public void oxygen(Runnable releaseOxygen) throws InterruptedException {
        synchronized (lock) {
        	while (hCount != 2) {
        		lock.wait();
        	}
	        // releaseOxygen.run() outputs "H". Do not change or remove this line.
			releaseOxygen.run();
			hCount = 0;
			lock.notifyAll();
		}
    }
}

/* 解法二:Semaphore */
class H2O {
    private Semaphore semaphoreH, semaphoreO;

    public H2O() {
        this.semaphoreH = new Semaphore(2);
        this.semaphoreO = new Semaphore(0);
    }

    public void hydrogen(Runnable releaseHydrogen) throws InterruptedException {
		semaphoreH.acquire();
        // releaseHydrogen.run() outputs "H". Do not change or remove this line.
        releaseHydrogen.run();
        semaphoreO.release();
    }

    public void oxygen(Runnable releaseOxygen) throws InterruptedException {
        semaphoreO.acquire(2);
        // releaseOxygen.run() outputs "H". Do not change or remove this line.
		releaseOxygen.run();
        semaphoreH.release(2);
    }
}
發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章