zoukankan      html  css  js  c++  java
  • 曹工杂谈:一道阿里面试题,两个线程交替打印奇偶数

    一、前言

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

    update on 2020/6/7,下面的第二种方式,现在回头看,其实感觉写得不好,下面直接贴一种更直接的方式(目前的技术水平写的,应该比之前的写的好点)。

     1 @Slf4j
     2 public class OddEvenDemo {
     3     private static volatile int number = 0;
     4 
     5     public static void main(String[] args) {
     6         final Object monitor = new Object();
     7 
     8         /**
     9          * 奇数线程
    10          */
    11         Runnable callable = new Runnable() {
    12 
    13             @Override
    14             public void run() {
    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 = new Thread(callable);
    43         thread1.setName("odd");
    44         thread1.start();
    45 
    46         /**
    47          * 偶数线程
    48          */
    49         Runnable evenCallable = new Runnable() {
    50 
    51             @Override
    52             public void run() {
    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 = new Thread(evenCallable);
    82         thread.setName("even");
    83         thread.start();
    84 
    85 
    86     }
    87 }

    二、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)。

    四、总结

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

  • 相关阅读:
    进程通信方式-管道pipe
    进程间通信
    信号的发送与处理
    信号应用于事件通知
    信号的屏蔽,信号集
    信号的发送kill,raise,alarm,setitimer,abort,sigqueue
    信号处理函数的返回sigsetjmp/siglongjmp
    POJ 1562 Oil Deposits
    HDU 1016 Prime Ring Problem
    HDU 1010 Tempter of the Bone
  • 原文地址:https://www.cnblogs.com/grey-wolf/p/11217164.html
Copyright © 2011-2022 走看看