一、前言
这些天忙着写业务代码,曹工说Tomcat系列暂时没时间写,先随便写点其他的。
里面提到了:两个线程,交替打印奇偶数这道笔试题。
update on 2020/6/7,下面的第二种方式,现在回头看,其实感觉写得不好,下面直接贴一种更直接的方式(目前的技术水平写的,应该比之前的写的好点)。
1 @Slf4j2 public classOddEvenDemo {3 private static volatile int number = 0;4
5 public static voidmain(String[] args) {6 final Object monitor = newObject();7
8 /**
9 * 奇数线程10 */
11 Runnable callable = newRunnable() {12
13 @Override14 public voidrun() {15 while (true) {16 boolean interrupted =Thread.currentThread().isInterrupted();17 if(interrupted) {18 break;19 }20 synchronized(monitor) {21 while (number % 2 == 0) {22 try{23 monitor.wait();24 } catch(InterruptedException e) {25 e.printStackTrace();26 }27 }28 log.info("奇数线程, number:{}", number);29 number++;30
31 monitor.notify();32 }33
34 try{35 Thread.sleep(5000);36 } catch(InterruptedException e) {37 e.printStackTrace();38 }39 }40 }41 };42 Thread thread1 = newThread(callable);43 thread1.setName("odd");44 thread1.start();45
46 /**
47 * 偶数线程48 */
49 Runnable evenCallable = newRunnable() {50
51 @Override52 public voidrun() {53 while (true) {54 boolean interrupted =Thread.currentThread().isInterrupted();55 if(interrupted) {56 break;57 }58 synchronized(monitor) {59 while (number % 2 != 0) {60 try{61 monitor.wait();62 } catch(InterruptedException e) {63 e.printStackTrace();64 }65 }66 log.info("偶数线程, number:{}", number);67 number++;68
69 monitor.notify();70 }71
72
73 try{74 Thread.sleep(5000);75 } catch(InterruptedException e) {76 e.printStackTrace();77 }78 }79 }80 };81 Thread thread = newThread(evenCallable);82 thread.setName("even");83 thread.start();84
85
86 }87 }
二、object的wait/notify方式
1 packageproducerconsumer;2
3 importjava.util.concurrent.atomic.AtomicInteger;4
5 public classOddEvenThread {6 private static volatile Integer counter = 0;7 private static Object monitor = newObject();8
9 public static voidmain(String[] args) {10 new Thread(newRunnable() {11 //奇数线程
12 @Override13 public voidrun() {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(newRunnable() {36 @Override37 public voidrun() {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 packageproducerconsumer;2
3 /**
4 * Created by Administrator on 2019/7/20.5 */
6 public classOddEvenThreadVolatileVersion {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) throwsInterruptedException {14 new Thread(newRunnable() {15
16 //奇数线程
17 @Override18 public voidrun() {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(newRunnable() {43 @Override44 public voidrun() {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 packageproducerconsumer;2
3 importsun.misc.Unsafe;4
5 importjava.lang.reflect.Field;6
7 /**
8 * Created by Administrator on 2019/7/20.9 */
10 public classOddEvenThreadCASVersion {11 private static volatile boolean loopForOdd = true;12
13 private static volatile boolean loopForEven = true;14
15 private static longloopForOddOffset;16
17 private static longloopForEvenOffset;18
19 private static volatile int counter = 1;20
21 private staticUnsafe 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.staticFieldOffset39 (OddEvenThreadCASVersion.class.getDeclaredField("loopForOdd"));40 } catch (Exception ex) { throw newError(ex); }41
42 try{43 loopForEvenOffset =unsafe.staticFieldOffset44 (OddEvenThreadCASVersion.class.getDeclaredField("loopForEven"));45 } catch (Exception ex) { throw newError(ex); }46 }47
48 public static void main(String[] args) throwsInterruptedException {49 new Thread(newRunnable() {50
51 //奇数线程
52 @Override53 public voidrun() {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(newRunnable() {82 @Override83 public voidrun() {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)。
四、总结
可重入锁的实现方式类似,这里留给读者进行实践。 大家有什么好的思路,可以在下方进行评论,也欢迎加群探讨。