問題
如何讓兩個執行緒交替列印1-100的數字?廢話不多說,直接上程式碼:
synchronized鎖+AtomicInteger
import java.util.concurrent.ExecutorService;
import java.util.concurrent.Executors;
import java.util.concurrent.atomic.AtomicInteger;
public class StrangePrinter {
private int max;
private AtomicInteger status = new AtomicInteger(1); // AtomicInteger保證可見性,也可以用volatile
public StrangePrinter(int max) {
this.max = max;
}
public static void main(String[] args) {
StrangePrinter strangePrinter = new StrangePrinter(100);
ExecutorService executorService = Executors.newFixedThreadPool(2);
executorService.submit(strangePrinter.new MyPrinter("Print1", 0));
executorService.submit(strangePrinter.new MyPrinter("Print2", 1));
executorService.shutdown();
}
class MyPrinter implements Runnable {
private String name;
private int type; // 列印的型別,0:代表列印奇數,1:代表列印偶數
public MyPrinter(String name, int type) {
this.name = name;
this.type = type;
}
@Override
public void run() {
if (type == 1) {
while (status.get() <= max) {
synchronized (StrangePrinter.class) { // 加鎖,保證下面的操作是一個原子操作
// 列印偶數
if (status.get() <= max && status.get() % 2 == 0) { // 列印偶數
System.out.println(name + " - " + status.getAndIncrement());
}
}
}
} else {
while (status.get() <= max) {
synchronized (StrangePrinter.class) { // 加鎖
// 列印奇數
if (status.get() <= max && status.get() % 2 != 0) { // 列印奇數
System.out.println(name + " - " + status.getAndIncrement());
}
}
}
}
}
}
}
複製程式碼
這裡需要注意兩點:
- 用AtomicInteger保證多執行緒資料可見性。
- 不要覺得synchronized加鎖是多餘的,如果沒有加鎖,執行緒1和執行緒2就可能出現不是交替列印的情況。如果沒有加鎖,設想執行緒1列印完了一個奇數後,執行緒2去列印下一個偶數,當執行完
status.getAndIncrement()
後,此時status又是奇數了,當此時cpu將執行緒2掛起,排程執行緒1,就會出現執行緒2還沒來得及列印偶數,執行緒1就已經列印了下一個奇數的情況。就不符合題目要求了。因此這裡加鎖是必須的,保證程式碼塊中的是一個原子操作。
使用Object的wait和notify實現
import java.util.concurrent.ExecutorService;
import java.util.concurrent.Executors;
import java.util.concurrent.atomic.AtomicInteger;
public class StrangePrinter2 {
Object odd = new Object(); // 奇數條件鎖
Object even = new Object(); // 偶數條件鎖
private int max;
private AtomicInteger status = new AtomicInteger(1); // AtomicInteger保證可見性,也可以用volatile
public StrangePrinter2(int max) {
this.max = max;
}
public static void main(String[] args) {
StrangePrinter2 strangePrinter = new StrangePrinter2(100);
ExecutorService executorService = Executors.newFixedThreadPool(2);
executorService.submit(strangePrinter.new MyPrinter("偶數Printer", 0));
executorService.submit(strangePrinter.new MyPrinter("奇數Printer", 1));
executorService.shutdown();
}
class MyPrinter implements Runnable {
private String name;
private int type; // 列印的型別,0:代表列印奇數,1:代表列印偶數
public MyPrinter(String name, int type) {
this.name = name;
this.type = type;
}
@Override
public void run() {
if (type == 1) {
while (status.get() <= max) { // 列印奇數
if (status.get() % 2 == 0) { // 如果當前為偶數,則等待
synchronized (odd) {
try {
odd.wait();
} catch (InterruptedException e) {
e.printStackTrace();
}
}
} else {
System.out.println(name + " - " + status.getAndIncrement()); // 列印奇數
synchronized (even) { // 通知偶數列印執行緒
even.notify();
}
}
}
} else {
while (status.get() <= max) { // 列印偶數
if (status.get() % 2 != 0) { // 如果當前為奇數,則等待
synchronized (even) {
try {
even.wait();
} catch (InterruptedException e) {
e.printStackTrace();
}
}
} else {
System.out.println(name + " - " + status.getAndIncrement()); // 列印偶數
synchronized (odd) { // 通知奇數列印執行緒
odd.notify();
}
}
}
}
}
}
}
複製程式碼
使用ReentrantLock+Condition實現
import java.util.concurrent.ExecutorService;
import java.util.concurrent.Executors;
import java.util.concurrent.atomic.AtomicInteger;
import java.util.concurrent.locks.Condition;
import java.util.concurrent.locks.ReentrantLock;
public class StrangePrinter3 {
private int max;
private AtomicInteger status = new AtomicInteger(1); // AtomicInteger保證可見性,也可以用volatile
private ReentrantLock lock = new ReentrantLock();
private Condition odd = lock.newCondition();
private Condition even = lock.newCondition();
public StrangePrinter3(int max) {
this.max = max;
}
public static void main(String[] args) {
StrangePrinter3 strangePrinter = new StrangePrinter3(100);
ExecutorService executorService = Executors.newFixedThreadPool(2);
executorService.submit(strangePrinter.new MyPrinter("偶數Printer", 0));
executorService.submit(strangePrinter.new MyPrinter("奇數Printer", 1));
executorService.shutdown();
}
class MyPrinter implements Runnable {
private String name;
private int type; // 列印的型別,0:代表列印奇數,1:代表列印偶數
public MyPrinter(String name, int type) {
this.name = name;
this.type = type;
}
@Override
public void run() {
if (type == 1) {
while (status.get() <= max) { // 列印奇數
lock.lock();
try {
if (status.get() % 2 == 0) {
odd.await();
}
if (status.get() <= max) {
System.out.println(name + " - " + status.getAndIncrement()); // 列印奇數
}
even.signal();
} catch (InterruptedException e) {
e.printStackTrace();
} finally {
lock.unlock();
}
}
} else {
while (status.get() <= max) { // 列印偶數
lock.lock();
try {
if (status.get() % 2 != 0) {
even.await();
}
if (status.get() <= max) {
System.out.println(name + " - " + status.getAndIncrement()); // 列印偶數
}
odd.signal();
} catch (InterruptedException e) {
e.printStackTrace();
} finally {
lock.unlock();
}
}
}
}
}
}
複製程式碼
這裡的實現思路其實和使用Object的wait和notify機制差不多。
通過flag標識實現
import java.util.concurrent.ExecutorService;
import java.util.concurrent.Executors;
import java.util.concurrent.atomic.AtomicInteger;
public class StrangePrinter4 {
private int max;
private AtomicInteger status = new AtomicInteger(1); // AtomicInteger保證可見性,也可以用volatile
private boolean oddFlag = true;
public StrangePrinter4(int max) {
this.max = max;
}
public static void main(String[] args) {
StrangePrinter4 strangePrinter = new StrangePrinter4(100);
ExecutorService executorService = Executors.newFixedThreadPool(2);
executorService.submit(strangePrinter.new MyPrinter("偶數Printer", 0));
executorService.submit(strangePrinter.new MyPrinter("奇數Printer", 1));
executorService.shutdown();
}
class MyPrinter implements Runnable {
private String name;
private int type; // 列印的型別,0:代表列印奇數,1:代表列印偶數
public MyPrinter(String name, int type) {
this.name = name;
this.type = type;
}
@Override
public void run() {
if (type == 1) {
while (status.get() <= max) { // 列印奇數
if (oddFlag) {
System.out.println(name + " - " + status.getAndIncrement()); // 列印奇數
oddFlag = !oddFlag;
}
}
} else {
while (status.get() <= max) { // 列印偶數
if (!oddFlag) {
System.out.println(name + " - " + status.getAndIncrement()); // 列印奇數
oddFlag = !oddFlag;
}
}
}
}
}
}
複製程式碼
這是最簡單最高效的實現方式,因為不需要加鎖。比前面兩種實現方式都要好一些。