1. 程式人生 > 其它 >執行緒列印_曹工雜談:一道阿里面試題,兩個執行緒交替列印奇偶數

執行緒列印_曹工雜談:一道阿里面試題,兩個執行緒交替列印奇偶數

技術標籤:執行緒列印

535bb84bf4d7d00d481b81b50730dc61.png

作者:三國夢迴

https://www.cnblogs.com/grey-wolf/p/11217164.html

一、前言

這些天忙著寫業務程式碼,曹工說Tomcat系列暫時沒時間寫,先隨便寫點其他的。

逛部落格園的時候,發現一篇園友的阿里面試文章,https://www.cnblogs.com/crossoverJie/p/9404789.html。

裡面提到了:兩個執行緒,交替列印奇偶數這道筆試題。

看了園友實現的程式碼(https://github.com/crossoverJie/JCSprout/blob/master/src/main/java/com/crossoverjie/actual/TwoThread.java),感覺有點複雜,於是自己琢磨著寫了一下,以下三個版本,一個基於object的wait、notify,一個基於volatile變數的方式,最後一種和第二種相似,只是用了unsafe實現。

二、object的wait/notify方式

package producerconsumer;import java.util.concurrent.atomic.AtomicInteger;public class OddEvenThread {    private static volatile Integer counter = 0;    private static Object monitor = new Object();    public static void main(String[] args) {        new Thread(new Runnable() {            // 奇數執行緒            @Override            public void run() {                while (true){                    synchronized (monitor){                        if (counter % 2 != 0){                            continue;                        }                        int i = ++counter;                        if (i > 100){                            return;                        }                        System.out.println("奇數執行緒:"  + i);                        try {                            monitor.notify();                            monitor.wait();                        } catch (InterruptedException e) {                            e.printStackTrace();                        }                    }                }            }        }).start();        new Thread(new Runnable() {            @Override            public void run() {                while (true){                    synchronized (monitor){                        if (counter % 2 == 0){                            continue;                        }                        int i = ++counter;                        if (i > 100){                            return;                        }                        System.out.println("偶數執行緒:"  + i);                        try {                            monitor.notify();                            monitor.wait();                        } catch (InterruptedException e) {                            e.printStackTrace();                        }                    }                }            }        }).start();    }}

bd073a2aae50060badbae66703b2c796.png

思路很簡單,程式碼也很簡單,主要就是基於 synchronized 鎖來實現阻塞和喚醒。

但是我個人感覺,頻繁地阻塞和喚醒,都需要執行緒從使用者態轉入核心態,有點太耗效能了,然後寫了以下的自旋非阻塞版本。

三、volatile 非阻塞方式

該方式的思路是,執行緒在volatile變數上無限迴圈,直到volatile變數變為false。變為false後,執行緒開始真正地執行業務邏輯,列印數字,最後,需要掛起自己,並修改volatile變數,來喚醒其他執行緒。

package producerconsumer;/** * Created by Administrator on 2019/7/20. */public class OddEvenThreadVolatileVersion {    private static volatile  boolean loopForOdd = true;    private static volatile  boolean loopForEven = true;    private static volatile int counter = 1;    public static void main(String[] args) throws InterruptedException {        new Thread(new Runnable() {            // 奇數執行緒            @Override            public void run() {                while (true) {                    while (loopForOdd){                    }                    int counter = OddEvenThreadVolatileVersion.counter;                    if (counter > 100) {                        break;                    }                    System.out.println("奇數執行緒:" + counter);                    OddEvenThreadVolatileVersion.counter++;                    // 修改volatile,通知偶數執行緒停止迴圈,同時,準備讓自己陷入迴圈                    loopForEven = false;                    loopForOdd = true;                }            }        }).start();        new Thread(new Runnable() {            @Override            public void run() {                while (true) {                    while (loopForEven) {                    }                    int counter = OddEvenThreadVolatileVersion.counter;                    if (counter > 100) {                        break;                    }                    System.out.println("偶數執行緒:" + counter);                    OddEvenThreadVolatileVersion.counter++;                    // 修改volatile,通知奇數執行緒停止迴圈,同時,準備讓自己陷入迴圈                    loopForOdd = false;                    loopForEven = true;                }            }        }).start();        // 先啟動奇數執行緒        loopForOdd = false;    }}

三、unsafe實現的版本

package producerconsumer;import sun.misc.Unsafe;import java.lang.reflect.Field;/** * Created by Administrator on 2019/7/20. */public class OddEvenThreadCASVersion {    private static volatile  boolean loopForOdd = true;    private static volatile  boolean loopForEven = true;    private static  long loopForOddOffset;    private static  long loopForEvenOffset;    private static volatile int counter = 1;    private static Unsafe unsafe;    static {        Field theUnsafeInstance = null;        try {            theUnsafeInstance = Unsafe.class.getDeclaredField("theUnsafe");        } catch (NoSuchFieldException e) {            e.printStackTrace();        }        theUnsafeInstance.setAccessible(true);        try {            unsafe = (Unsafe) theUnsafeInstance.get(Unsafe.class);        } catch (IllegalAccessException e) {            e.printStackTrace();        }        try {            loopForOddOffset = unsafe.staticFieldOffset                    (OddEvenThreadCASVersion.class.getDeclaredField("loopForOdd"));        } catch (Exception ex) { throw new Error(ex); }        try {            loopForEvenOffset = unsafe.staticFieldOffset                    (OddEvenThreadCASVersion.class.getDeclaredField("loopForEven"));        } catch (Exception ex) { throw new Error(ex); }    }    public static void main(String[] args) throws InterruptedException {        new Thread(new Runnable() {            // 奇數執行緒            @Override            public void run() {                while (true) {                    while (true){                        boolean b = unsafe.getBoolean(OddEvenThreadCASVersion.class, loopForOddOffset);                        if (b){                            // 迴圈                        }else {                            break;                        }                    }                    int counter = OddEvenThreadCASVersion.counter;                    if (counter > 100) {                        break;                    }                    System.out.println("奇數執行緒:" + counter);                    OddEvenThreadCASVersion.counter++;                    // 修改volatile,通知偶數執行緒停止迴圈,同時,準備讓自己陷入迴圈                    unsafe.putBoolean(OddEvenThreadCASVersion.class, loopForOddOffset,true);                    unsafe.putBoolean(OddEvenThreadCASVersion.class, loopForEvenOffset,false);                }            }        }).start();        new Thread(new Runnable() {            @Override            public void run() {                while (true) {                    while (true){                        boolean b = unsafe.getBoolean(OddEvenThreadCASVersion.class, loopForEvenOffset);                        if (b){                            // 迴圈                        }else {                            break;                        }                    }                    int counter = OddEvenThreadCASVersion.counter;                    if (counter > 100) {                        break;                    }                    System.out.println("偶數執行緒:" + counter);                    OddEvenThreadCASVersion.counter++;                    // 修改volatile,通知奇數執行緒停止迴圈,同時,準備讓自己陷入迴圈                    unsafe.putBoolean(OddEvenThreadCASVersion.class, loopForOddOffset,false);                    unsafe.putBoolean(OddEvenThreadCASVersion.class, loopForEvenOffset,true);                }            }        }).start();        // 先啟動奇數執行緒        loopForOdd = false;    }}

程式碼整體和第二種類似,只是為了學習下 unsafe 的使用。unsafe的操作方式,如果學過c語言的話,應該會覺得比較熟悉,裡面的offset,其實就類似與指標的位置。

我們看看,要獲取一個值,用unsafe的寫法是,unsafe.getBoolean(OddEvenThreadCASVersion.class, loopForEvenOffset),模擬成c語言就是,獲取到 OddEvenThreadCASVersion 的指標,再偏移 loopForEvenOffset,再取接下來的4個位元組,換算成 boolean即可。

void * ptr = &OddEvenThreadCASVersion.classint tmp = *(int*)(ptr + loopForEvenOffset)boolean ret = (boolean)tmp; 

(只是個示意,不用糾結哈,c語言快忘完了。。)

ps:注意上面變紅部分,因為是static field,所以要用這個方法,否則用 public native long objectFieldOffset(Field var1)。

四、總結

可重入鎖的實現方式類似,這裡留給讀者進行實踐。

精彩推薦

  • Java學習路線圖!!!

  • Java1234 VIP特價!!!

  • 鋒哥帶你java從入門到上班!!!

長按關注鋒哥微信公眾號,非常感謝;

b90595008383cbf90d1a3a3a0f08b7cf.png