執行緒列印_曹工雜談:一道阿里面試題,兩個執行緒交替列印奇偶數
技術標籤:執行緒列印
作者:三國夢迴
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(); }}
思路很簡單,程式碼也很簡單,主要就是基於 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從入門到上班!!!
長按關注鋒哥微信公眾號,非常感謝;