如何讓兩個執行緒交替列印數字

zhong0316發表於2019-02-24

問題

如何讓兩個執行緒交替列印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());
                        }
                    }
                }
            }
        }
    }
}

複製程式碼

這裡需要注意兩點:

  1. 用AtomicInteger保證多執行緒資料可見性。
  2. 不要覺得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;
                    }
                }
            }
        }
    }
}

複製程式碼

這是最簡單最高效的實現方式,因為不需要加鎖。比前面兩種實現方式都要好一些。

相關文章