一、前言
這些天忙著寫業務程式碼,曹工說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方式
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 鎖來實現阻塞和喚醒。
但是我個人感覺,頻繁地阻塞和喚醒,都需要執行緒從使用者態轉入核心態,有點太耗效能了,然後寫了以下的自旋非阻塞版本。
三、volatile 非阻塞方式
該方式的思路是,執行緒在volatile變數上無限迴圈,直到volatile變數變為false。變為false後,執行緒開始真正地執行業務邏輯,列印數字,最後,需要掛起自己,並修改volatile變數,來喚醒其他執行緒。
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 }
三、unsafe實現的版本
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語言快忘完了。。)
ps:注意上面變紅部分,因為是static field,所以要用這個方法,否則用 public native long objectFieldOffset(Field var1)。
四、總結
可重入鎖的實現方式類似,這裡留給讀者進行實踐。 大家有什麼好的思路,可以在下方進行評論,也歡迎加群探討。