1. 一道好玩有趣的面試題
Java求職中往往會碰到這樣的面試題。請實現多線程交替打印0和1的程序。
看到這道題目,憑着程序員的第六感,我們很容易聯想到消費者和生產者模式。他們之間有很大的相似之處啊。生產者線程負責往倉庫生產物資,當倉庫的物資物滿爲患的時候,阻塞線程停止生產,等待消費者線程消費物資後喚醒生產者線程。消費者線程負責從倉庫拿出物資,當倉庫沒有物資的時候阻塞線程停止消費,等待生產者線程生產物資後喚醒消費者線程。這道題目不也是一樣嗎,有這樣兩類線程,一類線程打印0(下文稱T0),一類線程打印1(下文稱T1)。當滿足打印0的條件下,T0打印0,同時阻塞T0並喚醒T1線程,在不滿足打印0的條件下,T0就一直等待被T1喚醒。同理T1線程也是一樣,在滿足打印1的條件下,T1打印1,同時阻塞T1並喚醒T0線程,在不滿足打印1的條件下,T1就一直等待被T0喚醒。要實現這樣的功能我們很容易想到 wait()和notify()/notifyAll()方法。
- wait、notify以及notifyAll都是Object對象的方法,他們必須在被 synchronized 同步的方法或代碼塊中調用,否則會報錯
- 調用wait方法會使該線程進入等待狀態,並且會釋放被同步對象的鎖
- notify操作可以喚醒一個因執行wait而處於阻塞狀態的線程,使其進入就緒狀態,被喚醒的線程會去嘗試着獲取對象鎖,然後執行wait之後的代碼。如果發出notify操作時,沒有線程處於阻塞狀態,那麼該命令會忽略。注意執行notify並不會馬上釋放對象鎖,會等到執行完該同步方法或同步代碼塊後才釋放
- notifyAll方法可以喚醒等待隊列中等待同一共享資源的“全部”線程從等待狀態退出,進入可運行狀態。此時,優先級最高的那個線程優先執行,但也有可能是隨機執行,這取決於JVM虛擬機的實現
2. 用synchronized配合wait() notify()/notifyAll()實現
下面我就用wait() notify()來實現交替打印功能
package com.peter.tips.lock;
import java.util.concurrent.TimeUnit;
import java.util.concurrent.locks.Condition;
import java.util.concurrent.locks.ReentrantLock;
/**
*
* Created by jiangbin on 2018/6/3.
*/
public class AlternatesUseWait {
private static final Object lock = new Object();
private static int value;
//判斷當前應該打印的值
private static int getCurrentValue() {
return value % 2;
}
public static void main(String[] args) {
new Thread(new PrintZeroTask()).start();
new Thread(new PrintOneTask()).start();
}
public static class PrintZeroTask implements Runnable {
@Override
public void run() {
try {
synchronized (lock) {
while (true) {//這個while循環是讓線程一直打印
while (getCurrentValue() == 1)
{//大家一定要注意,這個while循環必不可少,少了這個while循環,這道面試題基本就是不及格
try {
lock.wait();
} catch (InterruptedException e) {
e.printStackTrace();
}
}
try {
TimeUnit.MILLISECONDS.sleep(500);
} catch (InterruptedException e) {
e.printStackTrace();
}
System.out.println("0 " + Thread.currentThread().getName());
value = (value + 1) % 2;//value 交替
lock.notify();//喚醒打印1的線程
}
}
} finally {
}
}
}
public static class PrintOneTask implements Runnable {
@Override
public void run() {
try {
synchronized (lock) {
while (true) {
while (getCurrentValue() == 0) {
try {
lock.wait();
} catch (InterruptedException e) {
e.printStackTrace();
}
}
try {
TimeUnit.MILLISECONDS.sleep(500);
} catch (InterruptedException e) {
e.printStackTrace();
}
System.out.println("1 " + Thread.currentThread().getName());
value = (value + 1) % 2;
lock.notify();
}
}
} finally {
}
}
}
}
至於打印的結果麻煩大家親自運行下
2. 用Lock配合await() signal()/signalAll()實現
上一節我們講到了java concurrent包的ReentrantLock。既然用synchronized 配合 wait/notify能實現。那麼ReentrantLock 是不是也有類似wait/notify這樣的實現呢。答案是有的。Lock接口有個Condition newCondition()方法,Condition是一個接口,它有await()和signal()/signalAll()。跟Object的wait() notify()/notifyAll()剛好一一對應。
package com.peter.tips.lock;
import java.util.concurrent.*;
import java.util.concurrent.locks.Condition;
import java.util.concurrent.locks.ReentrantLock;
/**
* 多線程交替打印0和1
* Created by jiangbin on 2018/6/3.
*/
public class Alternates {
private static ReentrantLock lock = new ReentrantLock();
private static Condition printOneCondition = lock.newCondition();
private static Condition printTwoCondition = lock.newCondition();
private static int value;
private static int getCurrentValue() {
return value % 2;
}
public static void main(String[] args) {
new Thread(new PrintZeroTask()).start();
new Thread(new PrintOneTask()).start();
}
public static class PrintZeroTask implements Runnable {
@Override
public void run() {
try {
System.out.println(Thread.currentThread().getName());
lock.lock();
while (true) {
while (getCurrentValue() == 1) {
try {
printOneCondition.await();
} catch (InterruptedException e) {
e.printStackTrace();
}
}
try {
TimeUnit.MILLISECONDS.sleep(500);
} catch (InterruptedException e) {
e.printStackTrace();
}
System.out.println("0 "+Thread.currentThread().getName());
value = (value + 1) % 2;
printTwoCondition.signal();
}
} finally {
lock.unlock();
}
}
}
public static class PrintOneTask implements Runnable {
@Override
public void run() {
try {
System.out.println(Thread.currentThread().getName());
lock.lock();
while (true) {
while (getCurrentValue() == 0) {
try {
printTwoCondition.await();
} catch (InterruptedException e) {
e.printStackTrace();
}
}
try {
TimeUnit.MILLISECONDS.sleep(500);
} catch (InterruptedException e) {
e.printStackTrace();
}
System.out.println("1 "+Thread.currentThread().getName());
value = (value + 1) % 2;
printOneCondition.signal();
}
} finally {
lock.unlock();
}
}
}
}