python两个线程交替打印_曹工杂谈:一道阿里面试题,两个线程交替打印奇偶数...

一、前言

这些天忙着写业务代码,曹工说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)。

四、总结

可重入锁的实现方式类似,这里留给读者进行实践。 大家有什么好的思路,可以在下方进行评论,也欢迎加群探讨。

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值