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

1、前言

这些天忙着写业务代码,曹工说Tomcat系列暂时没时间写,先随便写点其余的。html

逛博客园的时候,发现一篇园友的阿里面试文章,http://www.javashuo.com/article/p-aaxpqizc-kz.htmljava

里面提到了:两个线程,交替打印奇偶数这道笔试题。git

看了园友实现的代码(https://github.com/crossoverJie/JCSprout/blob/master/src/main/java/com/crossoverjie/actual/TwoThread.java),感受有点复杂,因而本身琢磨着写了一下,如下三个版本,一个基于object的wait、notify,一个基于volatile变量的方式,最后一种和第二种类似,只是用了unsafe实现。github

 

2、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 锁来实现阻塞和唤醒。面试

可是我我的感受,频繁地阻塞和唤醒,都须要线程从用户态转入核心态,有点太耗性能了,而后写了如下的自旋非阻塞版本。ide

 

 

3、volatile 非阻塞方式

该方式的思路是,线程在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 }

 

3、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语言快忘完了。。)atom

 

ps:注意上面变红部分,由于是static field,因此要用这个方法,不然用 public native long objectFieldOffset(Field var1)。

4、总结

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

相关文章
相关标签/搜索