這些天忙着寫業務代碼,曹工說Tomcat系列暫時沒時間寫,先隨便寫點其餘的。html
逛博客園的時候,發現一篇園友的阿里面試文章,http://www.javashuo.com/article/p-aaxpqizc-kz.html。java
裏面提到了:兩個線程,交替打印奇偶數這道筆試題。git
看了園友實現的代碼(https://github.com/crossoverJie/JCSprout/blob/master/src/main/java/com/crossoverjie/actual/TwoThread.java),感受有點複雜,因而本身琢磨着寫了一下,如下三個版本,一個基於object的wait、notify,一個基於volatile變量的方式,最後一種和第二種類似,只是用了unsafe實現。github
1 package producerconsumer; 2
3 import java.util.concurrent.atomic.AtomicInteger; 4
5 public class OddEvenThread { 6 private static volatile Integer counter = 0; 7 private static Object monitor = new Object(); 8
9 public static void main(String[] args) { 10 new Thread(new Runnable() { 11 // 奇數線程
12 @Override 13 public void run() { 14 while (true){ 15 synchronized (monitor){ 16 if (counter % 2 != 0){ 17 continue; 18 } 19 int i = ++counter; 20 if (i > 100){ 21 return; 22 } 23 System.out.println("奇數線程:" + i); 24 try { 25 monitor.notify(); 26 monitor.wait(); 27 } catch (InterruptedException e) { 28 e.printStackTrace(); 29 } 30 } 31 } 32 } 33 }).start(); 34
35 new Thread(new Runnable() { 36 @Override 37 public void run() { 38 while (true){ 39 synchronized (monitor){ 40 if (counter % 2 == 0){ 41 continue; 42 } 43 int i = ++counter; 44 if (i > 100){ 45 return; 46 } 47 System.out.println("偶數線程:" + i); 48 try { 49 monitor.notify(); 50 monitor.wait(); 51 } catch (InterruptedException e) { 52 e.printStackTrace(); 53 } 54 } 55 } 56 } 57 }).start(); 58
59
60 } 61 }
思路很簡單,代碼也很簡單,主要就是基於 synchronized 鎖來實現阻塞和喚醒。面試
可是我我的感受,頻繁地阻塞和喚醒,都須要線程從用戶態轉入核心態,有點太耗性能了,而後寫了如下的自旋非阻塞版本。ide
該方式的思路是,線程在volatile變量上無限循環,直到volatile變量變爲false。變爲false後,線程開始真正地執行業務邏輯,打印數字,最後,須要掛起本身,並修改volatile變量,來喚醒其餘線程。oop
1 package producerconsumer; 2
3 /**
4 * Created by Administrator on 2019/7/20. 5 */
6 public class OddEvenThreadVolatileVersion { 7 private static volatile boolean loopForOdd = true; 8
9 private static volatile boolean loopForEven = true; 10
11 private static volatile int counter = 1; 12
13 public static void main(String[] args) throws InterruptedException { 14 new Thread(new Runnable() { 15
16 // 奇數線程
17 @Override 18 public void run() { 19 while (true) { 20 while (loopForOdd){ 21
22 } 23
24 int counter = OddEvenThreadVolatileVersion.counter; 25 if (counter > 100) { 26 break; 27 } 28 System.out.println("奇數線程:" + counter); 29
30 OddEvenThreadVolatileVersion.counter++; 31
32 // 修改volatile,通知偶數線程中止循環,同時,準備讓本身陷入循環
33 loopForEven = false; 34
35 loopForOdd = true; 36
37 } 38
39 } 40 }).start(); 41
42 new Thread(new Runnable() { 43 @Override 44 public void run() { 45 while (true) { 46 while (loopForEven) { 47
48 } 49
50 int counter = OddEvenThreadVolatileVersion.counter; 51 if (counter > 100) { 52 break; 53 } 54 System.out.println("偶數線程:" + counter); 55
56 OddEvenThreadVolatileVersion.counter++; 57
58 // 修改volatile,通知奇數線程中止循環,同時,準備讓本身陷入循環
59 loopForOdd = false; 60
61 loopForEven = true; 62 } 63 } 64 }).start(); 65
66 // 先啓動奇數線程
67 loopForOdd = false; 68
69 } 70 }
1 package producerconsumer; 2
3 import sun.misc.Unsafe; 4
5 import java.lang.reflect.Field; 6
7 /**
8 * Created by Administrator on 2019/7/20. 9 */
10 public class OddEvenThreadCASVersion { 11 private static volatile boolean loopForOdd = true; 12
13 private static volatile boolean loopForEven = true; 14
15 private static long loopForOddOffset; 16
17 private static long loopForEvenOffset; 18
19 private static volatile int counter = 1; 20
21 private static Unsafe unsafe; 22
23 static { 24 Field theUnsafeInstance = null; 25 try { 26 theUnsafeInstance = Unsafe.class.getDeclaredField("theUnsafe"); 27 } catch (NoSuchFieldException e) { 28 e.printStackTrace(); 29 } 30 theUnsafeInstance.setAccessible(true); 31 try { 32 unsafe = (Unsafe) theUnsafeInstance.get(Unsafe.class); 33 } catch (IllegalAccessException e) { 34 e.printStackTrace(); 35 } 36
37 try { 38 loopForOddOffset = unsafe.staticFieldOffset 39 (OddEvenThreadCASVersion.class.getDeclaredField("loopForOdd")); 40 } catch (Exception ex) { throw new Error(ex); } 41
42 try { 43 loopForEvenOffset = unsafe.staticFieldOffset 44 (OddEvenThreadCASVersion.class.getDeclaredField("loopForEven")); 45 } catch (Exception ex) { throw new Error(ex); } 46 } 47
48 public static void main(String[] args) throws InterruptedException { 49 new Thread(new Runnable() { 50
51 // 奇數線程
52 @Override 53 public void run() { 54 while (true) { 55 while (true){ 56 boolean b = unsafe.getBoolean(OddEvenThreadCASVersion.class, loopForOddOffset); 57 if (b){ 58 // 循環
59 }else { 60 break; 61 } 62 } 63
64 int counter = OddEvenThreadCASVersion.counter; 65 if (counter > 100) { 66 break; 67 } 68 System.out.println("奇數線程:" + counter); 69
70 OddEvenThreadCASVersion.counter++; 71
72 // 修改volatile,通知偶數線程中止循環,同時,準備讓本身陷入循環
73 unsafe.putBoolean(OddEvenThreadCASVersion.class, loopForOddOffset,true); 74 unsafe.putBoolean(OddEvenThreadCASVersion.class, loopForEvenOffset,false); 75
76 } 77
78 } 79 }).start(); 80
81 new Thread(new Runnable() { 82 @Override 83 public void run() { 84 while (true) { 85 while (true){ 86 boolean b = unsafe.getBoolean(OddEvenThreadCASVersion.class, loopForEvenOffset); 87 if (b){ 88 // 循環
89 }else { 90 break; 91 } 92 } 93
94 int counter = OddEvenThreadCASVersion.counter; 95 if (counter > 100) { 96 break; 97 } 98 System.out.println("偶數線程:" + counter); 99
100 OddEvenThreadCASVersion.counter++; 101
102 // 修改volatile,通知奇數線程中止循環,同時,準備讓本身陷入循環
103 unsafe.putBoolean(OddEvenThreadCASVersion.class, loopForOddOffset,false); 104 unsafe.putBoolean(OddEvenThreadCASVersion.class, loopForEvenOffset,true); 105 } 106 } 107 }).start(); 108
109 // 先啓動奇數線程
110 loopForOdd = false; 111
112 } 113 }
代碼總體和第二種相似,只是爲了學習下 unsafe 的使用。unsafe的操做方式,若是學過c語言的話,應該會以爲比較熟悉,裏面的offset,其實就相似與指針的位置。性能
咱們看看,要獲取一個值,用unsafe的寫法是,unsafe.getBoolean(OddEvenThreadCASVersion.class, loopForEvenOffset),模擬成c語言就是,獲取到 OddEvenThreadCASVersion 的指針,再偏移 loopForEvenOffset,再取接下來的4個字節,換算成 boolean便可。學習
void * ptr = &OddEvenThreadCASVersion.class
int tmp = *(int*)(ptr + loopForEvenOffset)
boolean ret = (boolean)tmp;
(只是個示意,不用糾結哈,c語言快忘完了。。)atom
ps:注意上面變紅部分,由於是static field,因此要用這個方法,不然用 public native long objectFieldOffset(Field var1)。
可重入鎖的實現方式相似,這裏留給讀者進行實踐。 你們有什麼好的思路,能夠在下方進行評論,也歡迎加羣探討。