面試必問!Java 多線程中兩個線程交替執行,一個輸出偶數,一個輸出奇數


前言

樓主今天在面經上看到這個題,挺有意思,小小的題目對多線程的考量還挺多。大部分同學都會使用 synchronized 來實現。樓主今天帶來另外兩種優化實現,讓你面試的時候,傲視群雄!

第一種 synchronized

class ThreadPrintDemo2 {
  public static void main(String[] args) {
    final ThreadPrintDemo2 demo2 = new ThreadPrintDemo2();
    Thread t1 = new Thread(demo2::print1);
    Thread t2 = new Thread(demo2::print2);

    t1.start();
    t2.start();
  }

  public synchronized void print2() {
    for (int i = 1; i <= 100; i += 2) {
      System.out.println(i);
      this.notify();
      try {
        this.wait();
        Thread.sleep(100);// 防止打印速度過快導致混亂
      } catch (InterruptedException e) {
        // NO
      }
    }
  }

  public synchronized void print1() {
    for (int i = 0; i <= 100; i += 2) {
      System.out.println(i);
      this.notify();
      try {
        this.wait();
        Thread.sleep(100);// 防止打印速度過快導致混亂
      } catch (InterruptedException e) {
        // NO
      }
    }
  }
}

通過 synchronized 同步兩個方法,每次只能有一個線程進入,每打印一個數,就釋放鎖,另一個線程進入,拿到鎖,打印,喚醒另一個線程,然后掛起自己。循環反復,實現了一個最基本的打印功能。

但,如果你這么寫,面試官肯定是不滿意的。樓主將介紹一種更好的實現。

使用 CAS 實現

public class ThreadPrintDemo {

  static AtomicInteger cxsNum = new AtomicInteger(0);
  static volatile boolean flag = false;

  public static void main(String[] args) {

    Thread t1 = new Thread(() -> {
      for (; 100 > cxsNum.get(); ) {
        if (!flag && (cxsNum.get() == 0 || cxsNum.incrementAndGet() % 2 == 0)) {
          try {
            Thread.sleep(100);// 防止打印速度過快導致混亂
          } catch (InterruptedException e) {
            //NO
          }

          System.out.println(cxsNum.get());
          flag = true;
        }
      }
    }
    );

    Thread t2 = new Thread(() -> {
      for (; 100 > cxsNum.get(); ) {
        if (flag && (cxsNum.incrementAndGet() % 2 != 0)) {
          try {
            Thread.sleep(100);// 防止打印速度過快導致混亂
          } catch (InterruptedException e) {
            //NO
          }

          System.out.println(cxsNum.get());
          flag = false;
        }
      }
    }
    );

    t1.start();
    t2.start();
  }
}

我們通過使用 CAS,避免線程的上下文切換,然后呢,使用一個 volatile 的 boolean 變量,保證不會出現可見性問題,記住,這個 flag 一定要是 volatile 的,如果不是,可能你的程序運行起來沒問題,但最終一定會出問題,而且面試官會立馬鄙視你。

這樣就消除了使用 synchronized 導致的上下文切換帶來的損耗,性能更好。相信,如果你面試的時候,這么寫,面試官肯定很滿意。

但,我們還有性能更好的。

使用 volatile

class ThreadPrintDemo3{

  static volatile int num = 0;
  static volatile boolean flag = false;

  public static void main(String[] args) {

    Thread t1 = new Thread(() -> {
      for (; 100 > num; ) {
        if (!flag && (num == 0 || ++num % 2 == 0)) {

          try {
            Thread.sleep(100);// 防止打印速度過快導致混亂
          } catch (InterruptedException e) {
            //NO
          }

          System.out.println(num);
          flag = true;
        }
      }
    }
    );

    Thread t2 = new Thread(() -> {
      for (; 100 > num; ) {
        if (flag && (++num % 2 != 0)) {

          try {
            Thread.sleep(100);// 防止打印速度過快導致混亂
          } catch (InterruptedException e) {
            //NO
          }

          System.out.println(num);
          flag = false;
        }
      }
    }
    );

    t1.start();
    t2.start();

  }
}

我們使用 volatile 變量代替 CAS 變量,減輕使用 CAS 的消耗,注意,這里 ++num 不是原子的,但不妨礙,因為有 flag 變量控制。而 num 必須是 volatile 的,如果不是,會導致可見性問題。

到這里,如果你面試的時候這么寫,那么,offer 就不遠啦!哈哈😆!!

彩蛋 如何翻轉字符串?

class ReverseDemo {

  public static void main(String[] args) {

    String test = "abcdefg";

    System.out.println(new StringBuilder(test).reverse());

    char[] arr = test.toCharArray();

    for (int i = arr.length - 1; i >= 0; i--) {
      System.out.print(arr[i]);
    }

  }
}

這個就比較簡單了,兩種方式,一個是 StringBuilder 的 reverse 方法,一個是轉換成數組自己打印。自己轉換性能更好,reverse 方法內部步驟更多。

好啦,希望大家面試成功!!


免責聲明!

本站轉載的文章為個人學習借鑒使用,本站對版權不負任何法律責任。如果侵犯了您的隱私權益,請聯系本站郵箱yoyou2525@163.com刪除。



 
粵ICP備18138465號   © 2018-2025 CODEPRJ.COM