日韩成人免费在线_国产成人一二_精品国产免费人成电影在线观..._日本一区二区三区久久久久久久久不

當前位置:首頁 > 科技  > 軟件

看完后,你再也不用怕面試問并發編程啦

來源: 責編: 時間:2023-12-21 17:11:32 214觀看
導讀引言為什么很多大廠喜歡問并發編程呢?因為并發編程是開發人員的一個分水嶺。很多好幾年開發經驗的開發人員可能也沒有實際的并發編程經驗,要么就是在一些沒有挑戰性的中臺實現了所謂的分布式鎖,但是沒有并發量去考驗,要么

引言

為什么很多大廠喜歡問并發編程呢?因為并發編程是開發人員的一個分水嶺。很多好幾年開發經驗的開發人員可能也沒有實際的并發編程經驗,要么就是在一些沒有挑戰性的中臺實現了所謂的分布式鎖,但是沒有并發量去考驗,要么就是笑著說其實工作中用不上,這些開發人員后面會逐漸被AI淘汰,CURD的東西花這么多錢請你們干嘛呢?為什么不直接請個便宜的應屆生呢?鍛煉一兩年絕對不比這些開發人員差。因此,努力越過分水嶺,往架構組件的能力出發吧。這篇文章將會是你的出發點,這里會詳細介紹JDK 的并發包的原理及使用方法DeS28資訊網——每日最新資訊28at.com

1、JUC并發編程概述

J.U.C并發包,即java.util.concurrent包,是JDK的核心工具包,是JDK1.5之后,由 Doug Lea實現并引入。 DeS28資訊網——每日最新資訊28at.com

整個java.util.concurrent包,按照功能可以大致劃分如下:DeS28資訊網——每日最新資訊28at.com

  • juc-locks 鎖框架
  • juc-atomic 原子類框架
  • juc-sync 同步器框架、工具類
  • juc-collections 集合框架

課程J.U.C,分析所有基于的源碼為Oracle JDK1.8 DeS28資訊網——每日最新資訊28at.com

2、多線程基礎:進程、線程

多線程概念介紹DeS28資訊網——每日最新資訊28at.com

  • 進程:我們把運行中的程序叫做進程(概念)。每個進程都會占用內存與CPU資源(動態性)。進程與進程之間各自占用各自的內存資源,互相獨立(獨立性)。
  • 線程:線程就是進程中的一個執行單元,負責當前進程中程序的執行。一個進程可以包含多個線程。一個進程包含了多個線程就是多線程。多線程可以提高程序的并行運行效率。

     線程簡述: 線程是進程的執行單元,用來執行代碼。DeS28資訊網——每日最新資訊28at.com

為什么使用多線程?DeS28資訊網——每日最新資訊28at.com

多線程有什么用?這里舉例說明:DeS28資訊網——每日最新資訊28at.com

比如看學習視頻時候:我們在看視頻的同時,還可以聽到聲音,還可以看到廣告以及彈幕,這里至少用到四個線程,當其中一個線程卡死如放不了彈幕不影響播放廣告。DeS28資訊網——每日最新資訊28at.com

DeS28資訊網——每日最新資訊28at.com

如上圖,要達到并行執行的效果,這里就要用到多線程。DeS28資訊網——每日最新資訊28at.com

  • 并行: 兩個或兩個以上的事件在同一時刻發生(同時發生)
  • 并發: 兩個或兩個以上的事件在一個時間段內發生(交替執行)

線程調度DeS28資訊網——每日最新資訊28at.com

計算機通常只有一個CPU時,在任意時刻只能執行一條計算機指令,每一個進程只有獲得CPU的使用權才能執行指令。所謂多進程并發運行,從宏觀上看,其實是各個進程輪流獲得CPU的使用權,分別執行各自的任務。那么,就會有多個線程處于就緒狀態等到CPU,JVM就負責了線程的調度。JVM采用的是搶占式調度,沒有采用分時調度,因此可能造成多線程執行結果的的隨機性。DeS28資訊網——每日最新資訊28at.com

說明:在單核CPU中,同一個時刻只有一個線程執行,根據CPU時間片算法依次為每個線程服務,這就叫線程調度。DeS28資訊網——每日最新資訊28at.com

3、多線程編程:wait()、notify()、notifyAll()

目標: 線程等待和喚醒使用。DeS28資訊網——每日最新資訊28at.com

介紹DeS28資訊網——每日最新資訊28at.com

等待和喚醒:通常是兩個線程之間的事情一個線程等待另外一個線程負責喚醒DeS28資訊網——每日最新資訊28at.com

等待和喚醒DeS28資訊網——每日最新資訊28at.com

  • wait() 等待
  • notity() 喚醒單個
  • notityAll() 喚醒全部

Object類:DeS28資訊網——每日最新資訊28at.com

public final void wait();             // 導致當前線程等待public final native void wait(long timeout) throws InterruptedException;public final native void notify();    // 喚醒正在等待的單個線程public final native void notifyAll(); // 喚醒正在等待的全部線程

注意:wait和notify必須是在同步代碼塊中,使用鎖對象調用DeS28資訊網——每日最新資訊28at.com

  • wait()方法的作用?

     使當前線程阻塞DeS28資訊網——每日最新資訊28at.com

  • notify()方法的作用?

     喚醒正在等待的單個線程DeS28資訊網——每日最新資訊28at.com

  • notifyAll()方法的作用?

     喚醒所有等待(對象的)線程,哪一個線程將會第一個處理取決于操作系統的實現。DeS28資訊網——每日最新資訊28at.com

  • 為什么wait和notify方法放在Object?

     因為wait和notify需要使用鎖對象來調用,而任何對象都可以作為鎖,所以放在Object類中。DeS28資訊網——每日最新資訊28at.com

  • 詳細介紹

1、wait()、notify()、notifyAll() 方法是Object的本地final方法,子類無法被重寫。DeS28資訊網——每日最新資訊28at.com

2、wait() 使當前線程阻塞,前提是必須先獲得鎖,一般配合synchronized 關鍵字使用。   DeS28資訊網——每日最新資訊28at.com

即,一般在 synchronized 同步代碼塊里使用 wait()、notify、notifyAll() 方法。DeS28資訊網——每日最新資訊28at.com

3、由于 wait()、notify()、notifyAll() 方法在 synchronized 代碼塊執行,說明當前線程一定是獲取了鎖的。當線程執行wait()方法時候,會釋放當前的鎖,然后讓出CPU,進入等待狀態。只有當 notify()/notifyAll() 被執行時候,才會喚醒一個或多個正處于等待狀態的線程,然后繼續往下執行,直到執行完 synchronized 代碼塊的代碼或是中途遇到wait(),再次釋放鎖。也就是說,notify()/notifyAll() 的執行只是喚醒沉睡的線程,而不會立即釋放鎖,鎖的釋放要看代碼塊的具體執行情況。所以在編程中,盡量在使用了 notify()/notifyAll() 后立即退出臨界區,以喚醒其他線程讓其獲得鎖。DeS28資訊網——每日最新資訊28at.com

4、notify 和 wait 的順序不能錯,如果A線程先執行notify方法,B線程在執行wait方法,那么B線程是無法被喚醒的。DeS28資訊網——每日最新資訊28at.com

5、notify 和 notifyAll的區別:   DeS28資訊網——每日最新資訊28at.com

  • notify: 只喚醒一個等待(對象的)線程并使該線程開始執行。所以如果有多個線程等待一個對象,這個方法只會喚醒其中一個線程,選擇哪個線程取決于操作系統對多線程管理的實現。      
  • notifyAll: 會喚醒所有等待(對象的)線程,盡管哪一個線程將會第一個處理取決于操作系統的實現。如果當前情況下有多個線程需要被喚醒,推薦使用notifyAll方法。

代碼:DeS28資訊網——每日最新資訊28at.com

package cn.itcast.thread;import java.util.concurrent.TimeUnit;/** * 測試 wait()、notify()、notifyAll() */public class Test1 { public static void main(String[] args) { // 創建對象 Object obj = new Object(); // 線程t1 new Thread(() -> { synchronized (obj) { try { System.out.println(Thread.currentThread().getName() + "wait 前"); obj.wait(); // 等待、線程阻塞(釋放鎖) System.out.println(Thread.currentThread().getName() + "wait 后"); } catch (InterruptedException e) { e.printStackTrace(); } } }, "t1:").start(); // 線程t2 new Thread(() -> { synchronized (obj) { try { System.out.println(Thread.currentThread().getName() + "wait 前"); obj.wait(); // 等待、線程阻塞(釋放鎖) System.out.println(Thread.currentThread().getName() + "wait 后"); } catch (InterruptedException e) { e.printStackTrace(); } } }, "t2:").start(); // 線程t3 new Thread(() -> { synchronized (obj) { try { // 休眠2秒 TimeUnit.SECONDS.sleep(2); //目的讓前2個線程進入等待狀態 } catch (InterruptedException e) { e.printStackTrace(); } System.out.println("notifyAll 前"); obj.notifyAll(); // 喚醒全部等待的線程,不會釋放鎖 System.out.println("notifyAll 后"); } }, "t3:").start(); }

小結DeS28資訊網——每日最新資訊28at.com

  • wait()方法的作用?

導致當前線程等待。(釋放鎖,讓出CPU)DeS28資訊網——每日最新資訊28at.com

  • notify()方法的作用?

喚醒正在等待的單個線程。(不釋放鎖,不讓出CPU)DeS28資訊網——每日最新資訊28at.com

4、多線程編程:線程狀態及狀態轉換

目標理解線程6種狀態以及狀態轉換。DeS28資訊網——每日最新資訊28at.com

線程狀態DeS28資訊網——每日最新資訊28at.com

線程可以處于以下狀態之一:DeS28資訊網——每日最新資訊28at.com

  • NEW 尚未啟動的線程處于此狀態。
  • RUNNABLE 在Java虛擬機中執行的線程處于此狀態。
  • BLOCKED 被阻塞等待的線程處于此狀態。
  • WAITING 無限等待另一個線程執行特定動作的線程處于此狀態。
  • TIMED_WAITING 一個正在限時等待另一個線程執行一個動作的線程處于這一狀態。
  • TERMINATED 已退出的線程處于此狀態。

     Thread.State 枚舉類中進行了定義。DeS28資訊網——每日最新資訊28at.com

狀態轉換DeS28資訊網——每日最新資訊28at.com

DeS28資訊網——每日最新資訊28at.com

代碼:DeS28資訊網——每日最新資訊28at.com

package cn.itcast.thread;/** 測試: 線程6種狀態 */public class Test2 { // 方法1 (RUNNABLE: 運行) public static void test1() { new Thread(() -> { synchronized (Test2.class) { while (true) { } } }, "t1-runnable").start(); //線程運行中 } // 方法2 (TIMED_WAITING : 超時等待) public static void test2() { new Thread(() -> { synchronized (Test2.class) { while (true) { try { Test2.class.wait(100); } catch (InterruptedException e) { e.printStackTrace(); } } } }, "t2-timed_waiting").start(); //線程超時等待中 } // 方法3 public static void test3() { // (RUNNABLE: 運行) new Thread(() -> { synchronized (Test2.class) { while (true) { } } }, "t3-runnable").start(); // (BLOCKED: 阻塞) new Thread(() -> { synchronized (Test2.class) { //由于上面沒有釋放鎖,被阻塞中 } }, "t4-BLOCKED").start(); } // 方法4 (WAITING : 等待) public static void test4() { new Thread(() -> { synchronized (Test2.class) { while (true) { try { Test2.class.wait(); } catch (InterruptedException e) { e.printStackTrace(); } } } }, "t5-waiting").start(); //線程等待中 } public static void main(String[] args) { //test1(); // 線程運行中 //test2(); // 線程超時等待中 test3(); // 線程阻塞中 //test4(); // 線程等待中 }}

查看進程堆棧DeS28資訊網——每日最新資訊28at.com

使用jstack可查看指定進程(pid)的堆棧信息,用以分析線程執行狀態:DeS28資訊網——每日最新資訊28at.com

  • 進入cmd: Win + R

DeS28資訊網——每日最新資訊28at.com

  • 輸入jstack 進程號

DeS28資訊網——每日最新資訊28at.com

5、多線程編程:join、yield、sleep區別?

目標: 學習如何控制線程執行順序、線程讓步、優先級。DeS28資訊網——每日最新資訊28at.com

join 作用DeS28資訊網——每日最新資訊28at.com

  • join()方法【加入線程】,把指定的線程加入到當前線程,可以將兩個交替執行的線程合并為順序執行的線程。線程調用了join方法,那么就要一直運行到該線程結束,才會運行其他線程。這樣可以控制線程執行順序。
  • join()方法,內部實現使用了 synchronized 會占用鎖。線程結束,鎖釋放。
  •  join(long millis)

     – 如果為0表示永遠等待,其實是等到線程結束后。DeS28資訊網——每日最新資訊28at.com

     – 傳入指定的時間會調用wait(millis), 時間到鎖釋放,不再等待。DeS28資訊網——每日最新資訊28at.com

yield 作用DeS28資訊網——每日最新資訊28at.com

  • thread.yield()【線程讓步】 讓出CPU的時間片盡量切換到其它線程去執行。
  • 使正在運行中的線程重新變成就緒狀態,并重新競爭 CPU 的調度權。它可能會獲取到,也有可能被其它線程獲取到。

yield 和 sleep 的異同DeS28資訊網——每日最新資訊28at.com

  • 優先級:sleep休眠線程后,會給其他線程執行機會,不考慮線程的優先級問題;yield讓步后只有優先級高于或等于當前線程的線程才有執行機會。
  • 狀態:sleep當前線程由運行態進入超時等待狀態;yield當前線程由運行態到就緒態。
  • 異常:sleep方法在聲明時拋出InterruptedException異常,所以在使用時要么try捕獲要么throws拋出;而yield沒有聲明異常。

線程優先級DeS28資訊網——每日最新資訊28at.com

線程的優先級說明該線程在程序中的重要性。系統會根據優先級決定首先使用哪個線程,但這并不意味著優先級低的線程得不到運行,只是它運行的機率比較小而已,比如垃圾回收機制。DeS28資訊網——每日最新資訊28at.com

優先級范圍1-10,默認為5,比如設置最高優先級為10:DeS28資訊網——每日最新資訊28at.com

t1.setPriority(Thread.MAX_PRIORITY);DeS28資訊網——每日最新資訊28at.com

代碼:DeS28資訊網——每日最新資訊28at.com

package cn.itcast.thread;/** * join() : 加入線程 * yield() : 線程讓步 * sleep() : 線程休眠 */public class Test3 {    public static void main(String[] args) throws InterruptedException {        // 線程1        Thread t1 = new Thread(() -> {            for (int i = 1; i <= 20; i++) {                System.out.println(Thread.currentThread().getName() + i);                // 線程讓步                //Thread.yield();            }        },"t1:");        // 線程2        Thread t2 = new Thread(() -> {            for (int i = 1; i <= 20; i++) {                System.out.println(Thread.currentThread().getName() + i);            }        },"t2:");        // 啟動線程        t1.start();        // 加入線程        t1.join();        // 設置線程優先級        //t1.setPriority(Thread.MIN_PRIORITY);        t2.start();        // 設置線程優先級        //t2.setPriority(Thread.MIN_PRIORITY);    }}

小結DeS28資訊網——每日最新資訊28at.com

  • join作用?

 加入線程,線程調用了join方法,那么就要一直運行到該線程結束,才會運行其他線程. 這樣可以控制線程執行順序。DeS28資訊網——每日最新資訊28at.com

  • yield作用?

 線程讓步,讓出CPU的時間片盡量切換其他線程去執行。DeS28資訊網——每日最新資訊28at.com

  • 線程優先級?

     設置線程優先級,優先級可以設置1-10,數字越大代表優先級越高。DeS28資訊網——每日最新資訊28at.com

     在Java語言中,每個線程都有一個優先級,當線程調控器有機會選擇新的線程時,線程的優先級越高越有可能先被選擇執行。DeS28資訊網——每日最新資訊28at.com

6、并發編程需要處理的問題:死鎖問題

并發編程的目的是為了讓程序運行得更快,但是,并不是啟動更多的線程就能讓程序最大限度地并發執行。在進行并發編程時,如果希望通過多線程執行任務讓程序運行得更快,會面臨非常多的挑戰,比如死鎖的問題、上下文切換的問題。DeS28資訊網——每日最新資訊28at.com

描述DeS28資訊網——每日最新資訊28at.com

鎖是個非常有用的工具,運用場景非常多,因為它使用起來非常簡單,而且易于理解。但同時它也會帶來一些困擾,那就是可能會引起死鎖,一旦產生死鎖,就會造成系統功能不可用。讓我們先來看一段代碼,這段代碼會引起死鎖,使線程t1 和 線程t2 互相等待對方釋放鎖。DeS28資訊網——每日最新資訊28at.com

演示DeS28資訊網——每日最新資訊28at.com

么是死鎖?多線程競爭共享資源,導致線程相互等待,程序無法向下執行。DeS28資訊網——每日最新資訊28at.com

DeS28資訊網——每日最新資訊28at.com

執行sleep的時候會讓出CPU,但不是釋放鎖。 DeS28資訊網——每日最新資訊28at.com

package cn.itcast.thread;/** 學習死鎖的概念和解決死鎖 */public class DeadLock { // 定義兩個對象作為鎖 private static Object objA = new Object(); private static Object objB = new Object(); public static void main(String[] args) { // 線程1 Thread t1 = new Thread(() -> { // 同步鎖 synchronized (objA){ try { // 線程休眠(讓出CPU,不釋放鎖) Thread.sleep(1000); } catch (InterruptedException e) { e.printStackTrace(); } System.out.println("AAAAAAA"); // 同步鎖 synchronized (objB){ System.out.println("BBBBBBB"); } } }); // 線程2 Thread t2 = new Thread(() -> { // 同步鎖 synchronized (objB){ System.out.println("CCCCCCC"); // 同步鎖 synchronized (objA){ System.out.println("DDDDDDD"); } } }); t1.start(); t2.start(); }}

上面的代碼只是演示死鎖的場景,在現實中你可能不會寫出這樣的代碼。但是,在一些更為復雜的場景中,你可能會遇到這樣的問題,比如t1拿到鎖之后,因為一些異常情況沒有釋放鎖(死循環)。又或者是t1拿到一個數據庫鎖,釋放鎖的時候拋出了異常,沒釋放掉。一旦出現死鎖,業務是可感知的,因為不能繼續提供服務了。DeS28資訊網——每日最新資訊28at.com

查看線程執行情況DeS28資訊網——每日最新資訊28at.com

打開cmd命令dos窗口輸入如下命令DeS28資訊網——每日最新資訊28at.com

jps命令:查看Java程序進程id信息DeS28資訊網——每日最新資訊28at.com

jstack命令:查看指定進程堆棧信息DeS28資訊網——每日最新資訊28at.com

DeS28資訊網——每日最新資訊28at.com

DeS28資訊網——每日最新資訊28at.com

如何避免死鎖?DeS28資訊網——每日最新資訊28at.com

現在我們介紹避免死鎖的幾個常見方法:DeS28資訊網——每日最新資訊28at.com

  • 避免一個線程同時獲取多個鎖。
  • 避免一個線程在鎖內同時占用多個資源,盡量保證每個鎖只占用一個資源。
  • 嘗試使用定時鎖,使用lock.tryLock(timeout)來替代使用內部鎖機制。

小結DeS28資訊網——每日最新資訊28at.com

1.什么是死鎖: 多線程競爭共享資源,導致線程相互等待,程序無法向下執行。DeS28資訊網——每日最新資訊28at.com

2.死鎖產生的條件DeS28資訊網——每日最新資訊28at.com

  • 有多個線程   
  • 有多把鎖   
  • 有同步代碼塊嵌套

3.如何避免死鎖: 干掉其死鎖產生的條件中一個條件即可。DeS28資訊網——每日最新資訊28at.com

7、并發編程需要處理的問題:上下文過度切換

多線程一定快嗎?DeS28資訊網——每日最新資訊28at.com

DeS28資訊網——每日最新資訊28at.com

測試代碼DeS28資訊網——每日最新資訊28at.com

代碼演示串行和并發執行并累加操作的時間,請分析: 下面的代碼并發執行一定比串行執行快嗎?DeS28資訊網——每日最新資訊28at.com

package cn.itcast.thread;public class Test4 {    // 定義變量    private static final long count = 1000000000;    public static void main(String[] args) throws InterruptedException {        concurrency();        serial();    }    // 定義方法1(使用線程)    private static void concurrency() throws InterruptedException {        long start = System.currentTimeMillis();        // 創建線程 循環累加        Thread thread = new Thread(() -> {            int a = 0;            for (long i = 0; i < count; i++) {                a += 5;            }        });        // 開啟線程        thread.start();        // 循環累減        int b = 0;        for (long i = 0; i < count; i++) {            b--;        }        long time = System.currentTimeMillis() - start;       // thread.join();        System.out.println("concurrency :" + time + "ms,b=" + b);    }    // 定義方法2 (不用線程)    private static void serial() {        long start = System.currentTimeMillis();        // 循環累加        int a = 0;        for (long i = 0; i < count; i++) {            a += 5;        }        // 循環累減        int b = 0;        for (long i = 0; i < count; i++) {            b--;        }        long time = System.currentTimeMillis() - start;        System.out.println("serial:" + time + "ms,b=" + b );    }}

測試結果DeS28資訊網——每日最新資訊28at.com

上述問題的答案是“不一定”,測試結果如表所示:DeS28資訊網——每日最新資訊28at.com

DeS28資訊網——每日最新資訊28at.com

當并發執行累加操作不超過百萬次時,速度會比串行執行累加操作要慢。那么,為什么并發執行的速度會比串行慢呢?這是因為線程有創建和上下文切換的開銷。DeS28資訊網——每日最新資訊28at.com

上下文切換DeS28資訊網——每日最新資訊28at.com

  • 即使是單核處理器也支持多線程執行代碼,CPU 通過給每個線程分配CPU 時間片來實現這個機制。時間片是CPU 分配給各個線程的時間,因為時間片非常短,所以CPU 通過不停地切換線程執行,讓我們感覺多個線程是同時執行的,時間片一般是幾十毫秒(ms)。
  • CPU 通過時間片分配算法來循環執行任務,當前任務執行一個時間片后會切換到下一個任務。但是,在切換前會保存上一個任務的狀態,以便下次切換回這個任務時,可以再加載這個任務的狀態。所以任務從保存到再加載的過程就是一次上下文切換。

例如:這就像我們同時讀兩本書,當我們在讀一本英文的技術書時,發現某個單詞不認識,于是便打開中英文字典,但是在放下英文技術書之前,大腦必須先記住這本書讀到了多少頁的第多少行,等查完單詞之后,能夠繼續讀這本書。這樣的切換是會影響讀書效率的,同樣上下文切換也會影響多線程的執行速度。DeS28資訊網——每日最新資訊28at.com

如何減少上下文切換DeS28資訊網——每日最新資訊28at.com

減少上下文切換的方法有無鎖并發編程、CAS算法、使用最少線程和使用協程。DeS28資訊網——每日最新資訊28at.com

  • 無鎖并發編程: 多線程競爭鎖時,會引起上下文切換,所以多線程處理數據時,可以用一些辦法來避免使用鎖,如將數據的ID按照Hash 算法取模分段,不同的線程處理不同段的數據。
  • CAS算法:Java的Atomic包使用CAS算法來更新數據,而不需要加鎖。
  • 使用最少線程:避免創建不需要的線程,比如任務很少,但是創建了很多線程來處理,這樣會造成大量線程都處于等待狀態。
  • 協程:在單線程里實現多任務的調度,并在單線程里維持多個任務間的切換。

小結DeS28資訊網——每日最新資訊28at.com

強烈建議多使用JDK并發包提供的并發原子類和工具類來解決并發問題,因為這些類都已經通過了充分的測試和優化,均可解決了上面提到的幾個挑戰。DeS28資訊網——每日最新資訊28at.com

8、Java內存模型(Java Memory Model)

什么是JMM內存模型?DeS28資訊網——每日最新資訊28at.com

Java內存模型(即Java Memory Model,簡稱JMM)。Java內存模型跟CPU緩存模型類似,是基于CPU緩存模型來建立的,Java內存模型是標準化的,屏蔽了底層不同計算機的區別。DeS28資訊網——每日最新資訊28at.com

JMM本身是一種抽象的概念,并不真實存在,它描述的是一組規則或規范,通過這組規范定義了程序中各個變量(包括實例字段,靜態字段和構成數組對象的元素)的訪問方式。由于JVM運行程序的實體是線程,而每個線程創建時JVM都會為其創建一個工作內存(有些地方稱為棧空間),用于存儲線程私有的數據。而Java內存模型中規定所有共享變量都存儲在主內存,主內存是共享內存區域,所有線程都可以訪問。DeS28資訊網——每日最新資訊28at.com

線程對共享變量的操作(讀取賦值等)必須在工作內存中進行,首先要將變量從主內存拷貝的自己的工作內存空間,然后對變量進行操作,操作完成后再將變量寫回主內存,不能直接操作主內存中的變量,工作內存中存儲著主內存中的變量副本拷貝,前面說過,工作內存是每個線程的私有數據區域,因此不同的線程間無法訪問對方的工作內存,線程間的通信(傳值)必須通過主內存來完成,其簡要訪問過程如下圖:DeS28資訊網——每日最新資訊28at.com

DeS28資訊網——每日最新資訊28at.com

關于JMM中的主內存和工作內存說明如下:DeS28資訊網——每日最新資訊28at.com

  • 主內存

要存儲的是Java實例對象,所有線程創建的實例對象都存放在主內存中,不管該實例對象是成員變量還是方法中的本地變量(也稱局部變量),當然也包括了共享的類信息、常量、靜態變量。由于是共享數據區域,多條線程對同一個變量進行訪問可能會發現線程安全問題。DeS28資訊網——每日最新資訊28at.com

  • 工作內存

主要存儲當前方法的所有本地變量信息(工作內存中存儲著主內存中的變量副本拷貝),每個線程只能訪問自己的工作內存,即線程中的本地變量對其它線程是不可見的,就算是兩個線程執行的是同一段代碼,它們也會各自在自己的工作內存中創建屬于當前線程的本地變量,當然也包括了字節碼行號指示器、相關Native方法的信息。注意由于工作內存是每個線程的私有數據,線程間無法相互訪問工作內存,因此存儲在工作內存的數據不存在線程安全問題。DeS28資訊網——每日最新資訊28at.com

啟動2個線程,線程A讀取主內存的共享變量數據,之后線程B修改共享變量數據,線程A無法感知:DeS28資訊網——每日最新資訊28at.com

package cn.itcast.thread;public class Test5 {    // 定義flag屬性    private static boolean flag = false;    public static void main(String[] args) throws InterruptedException {        // 創建線程1        new Thread(() -> {            long num = 0;            while (!flag){                num++;            }            // 如果沒有打印,說明當前線程無法感知flag的修改            System.out.println("num = " + num);        }).start();        // 休眠1000毫秒        Thread.sleep(1000);        // 創建線程2        new Thread(() -> {            // 修改flag            flag = true;            System.out.println("flag = " + flag);        }).start();    }}

運行效果:沒有任務打印輸出,上面的線程無法感知flag的修改。DeS28資訊網——每日最新資訊28at.com

9Java并發編程三大特性

Java并發編程三個特性: 可見性、原子性、有序性。DeS28資訊網——每日最新資訊28at.com

  • 可見性:一個線程修改了某個共享變量,其狀態能夠立即被其他線程知曉,通常被解釋為將線程本地狀態反映到主內存上,volatile就是負責保證可見性的。
  • 原子性:相關操作不會中途被其他線程干擾,一般通過同步機制實現。
  • 有序性:保證線程內串行語義,避免指令重排。

9.1 可見性

可見性表示的是,如果有線程更新了某一個共享變量的值,則其它線程要能夠立即感知到最新的內容。如果不能保證可見性,則可能出現類似于數據庫中的臟讀情況。DeS28資訊網——每日最新資訊28at.com

前文介紹JMM的時候也提到了,如果要保證可見性,那么變量被一個線程修改后,需要將其修改后的最新值同步回主存,然后其它線程要讀取該變量時,需要從主存刷新最新的值到本地內存,就這樣通過主存實現可見性。但是將最新值同步回主存的時機是沒有強制要求的,也不知道其它線程什么時候可能會去從主存刷新最新值,所以普通變量在多線程操作時是保證不了可見性的。DeS28資訊網——每日最新資訊28at.com

這時有一個比較好使的關鍵字:volatile。JMM對它定義了一些特殊的訪問規則,它能保證修改后的最新值能立即同步到主存,同時,每次使用都從主存刷新。所以volatile能夠保證多線程場景下的可見性。DeS28資訊網——每日最新資訊28at.com

volatile 介紹DeS28資訊網——每日最新資訊28at.com

在多線程并發編程中synchronized和volatile都扮演著重要的角色,volatile是輕量級的synchronized,它在多處理器開發中保證了共享變量的“可見性”。可見性的意思是當一個線程修改一個共享變量時,另外一個線程能讀到這個修改的值。如果volatile變量修飾符使用恰當的話,它比synchronized的使用和執行成本更低,因為它不會引起線程上下文的切換和調度。DeS28資訊網——每日最新資訊28at.com

volatile 使用DeS28資訊網——每日最新資訊28at.com

package cn.itcast.thread;/** volatile 實現多線程訪問共享成員時的可見性 */public class Test6 { // volatile 實現多線程訪問共享成員時的可見性. private static volatile boolean flag = false; public static void main(String[] args) throws InterruptedException { // 創建線程1 new Thread(() -> { long num = 0; while (!flag){ num++; } // 如果沒有打印,說明當前線程無法感知flag的修改 System.out.println("num = " + num); }).start(); // 休眠1000毫秒 Thread.sleep(1000); // 創建線程2 new Thread(() -> { // 修改flag flag = true; System.out.println("flag = " + flag); }).start(); }}

9.2 原子性

在計算機中,它表示的是一個操作,可能包含一個或多個步驟,這些步驟要么全部執行成功要么全部執行失敗,并且執行的過程中不能被其它操作打斷,這類似于數據庫中事務的原子性概念。DeS28資訊網——每日最新資訊28at.com

數據原子操作,Java 內存模型對主內存與工作內存之間的具體交互協議定義了八種原子操作:DeS28資訊網——每日最新資訊28at.com

1. lock(鎖定): 作用于主內存的變量,把一個變量標記為一條線程獨占狀態。

2. unlock(解鎖): 作用于主內存的變量,把一個處于鎖定狀態的變量釋放出來,釋放后的變量才可以被其他線程鎖定。

3. read(讀取): 作用于主內存的變量,把一個變量值從主內存傳輸到線程的工作內存中,以便隨后的load動作使用。

4. load(載入): 作用于工作內存的變量,它把read操作從主內存中得到的變量值放入工作內存的變量副本中。

5. use(使用): 作用于工作內存的變量,把工作內存中的一個變量值傳遞給執行引擎。

6. assign(賦值): 作用于工作內存的變量,它把一個從執行引擎接收到的值賦給工作內存的變量。

7. store(存儲): 作用于工作內存的變量,把工作內存中的一個變量的值傳送到主內存中,以便隨后的write的操作。

8. write(寫入): 作用于工作內存的變量,它把store操作從工作內存中的一個變量的值傳送到主內存的變量中。DeS28資訊網——每日最新資訊28at.com

如果要把一個變量從主內存中復制到工作內存中,就需要按順序地執行read和load操作,如果把變量從工作內存中同步到主內存中,就需要按順序地執行store和write操作。但Java內存模型只要求上述操作必須按順序執行,而沒有保證必須是連續執行。DeS28資訊網——每日最新資訊28at.com

對應如下的流程圖:DeS28資訊網——每日最新資訊28at.com

DeS28資訊網——每日最新資訊28at.com

比如:i = i + 1,就是一個非原子操作,它涉及到獲取i,獲取1,相加,賦值等4個操作,所以在多線程情況下可能會出現并發問題。DeS28資訊網——每日最新資訊28at.com

比如 i = i+1,我們要保證它的原子性 該怎么做呢?可以通過八種操作中的lock和unlock來達到目的。但是JVM并沒有把lock和unlock操作直接開放給用戶使用,我們的Java代碼中,就是大家所熟知的synchronized關鍵字保證原子性DeS28資訊網——每日最新資訊28at.com

代碼:DeS28資訊網——每日最新資訊28at.com

package cn.itcast.thread;public class Test7 { // 無法在多線程的情況下實現原子自遞增的問題。 private static int count = 0; // 定義累計的方法 public synchronized static void inc(){ try { Thread.sleep(1); } catch (InterruptedException e) { e.printStackTrace(); } count++; } public static void main(String[] args) throws InterruptedException { // 循環創建線程 for(int i = 0; i < 1000; i++){ new Thread(() -> { inc(); }).start(); } Thread.sleep(4000); System.out.println("y運行結果:"+count); }}

9.3 有序性

Java內存模型中,允許編輯器和處理器對指令進行重排序,但是重排序過程不會影響到單線程程序的執行,卻會影響到多線程并發執行的正確性。DeS28資訊網——每日最新資訊28at.com

從字面上的意思理解,有序就是要保證代碼按照既定的順序依次執行。但是CPU(或編譯器)出于性能優化的目的,在保證不會對程序運行結果產生影響的前提下,代碼的執行順序可能會和我們既定的順序不一致。DeS28資訊網——每日最新資訊28at.com

示例DeS28資訊網——每日最新資訊28at.com

int i = 1;
int j = 1;DeS28資訊網——每日最新資訊28at.com

這兩行代碼互相沒有任何依賴關系,誰先執行還是后執行,對程序運行結果都不會有什么影響。經過指令重排后,可能 int j = 1; 就比int i = 1;先執行了。不同的CPU架構可能支持不同的重排規則,像Load-Load、Load-Store、Store-Store、Store-Load等等。DeS28資訊網——每日最新資訊28at.com

指令重排的后果在并發的情況下有時會是嚴重的,比如以下代碼:DeS28資訊網——每日最新資訊28at.com

public void execute(){ int a = 0; int b = 1; int c = a + b;}

這里a=0,b=1兩句可以隨便排序,不影響程序邏輯結果,但c=a+b這句必須在前兩句的后面執行。 DeS28資訊網——每日最新資訊28at.com

從前面那個例子可以看到,重排序在多線程環境下出現的概率還是挺高的,在關鍵字上有volatile和synchronized可以禁用重排序。DeS28資訊網——每日最新資訊28at.com

可能上面說的比較繞,舉個簡單的例子:DeS28資訊網——每日最新資訊28at.com

// x、y為非volatile變量// flag為volatile變量 x = 2; //語句1y = 0; //語句2flag = true; //語句3x = 4; //語句4y = -1; //語句5

10Java中的鎖:鎖的種類

在面試過程時,經常會被問到各種各樣的鎖,如樂觀鎖、讀寫鎖等等,非常繁多,在此做一個總結。介紹的內容如下:DeS28資訊網——每日最新資訊28at.com

? 樂觀鎖/悲觀鎖DeS28資訊網——每日最新資訊28at.com

? 獨享鎖/共享鎖DeS28資訊網——每日最新資訊28at.com

? 互斥鎖/讀寫鎖DeS28資訊網——每日最新資訊28at.com

? 可重入鎖DeS28資訊網——每日最新資訊28at.com

? 公平鎖/非公平鎖DeS28資訊網——每日最新資訊28at.com

? 分段鎖DeS28資訊網——每日最新資訊28at.com

? 偏向鎖/輕量級鎖/重量級鎖DeS28資訊網——每日最新資訊28at.com

? 自旋鎖DeS28資訊網——每日最新資訊28at.com

樂觀鎖/悲觀鎖DeS28資訊網——每日最新資訊28at.com

樂觀鎖與悲觀鎖并不是特指某兩種類型的鎖,是人們定義出來的概念或思想,主要是指看待并發同步的角度。DeS28資訊網——每日最新資訊28at.com

樂觀鎖:顧名思義,就是很樂觀,每次去拿數據的時候都認為別人不會修改,所以不會上鎖,但是在更新的時候會判斷一下在此期間別人有沒有去更新這個數據,可以使用版本號等機制。樂觀鎖適用于多讀的應用類型,這樣可以提高吞吐量,在Java中java.util.concurrent.atomic包下面的原子變量類就是使用了樂觀鎖的一種實現方式CAS(Compare and Swap 比較并交換)實現的。DeS28資訊網——每日最新資訊28at.com

悲觀鎖:總是假設最壞的情況,每次去拿數據的時候都認為別人會修改,所以每次在拿數據的時候都會上鎖,這樣別人想拿這個數據就會阻塞直到它拿到鎖。比如Java里面的synchronized關鍵字的實現就是悲觀鎖。DeS28資訊網——每日最新資訊28at.com

悲觀鎖適合寫操作非常多的場景,樂觀鎖適合讀操作非常多的場景,不加鎖會帶來大量的性能提升。DeS28資訊網——每日最新資訊28at.com

悲觀鎖在Java中的使用,就是利用各種鎖。DeS28資訊網——每日最新資訊28at.com

樂觀鎖在Java中的使用,是無鎖編程,常常采用的是CAS算法,典型的例子就是原子類,通過CAS自旋實現原子操作的更新。DeS28資訊網——每日最新資訊28at.com

獨享鎖/共享鎖DeS28資訊網——每日最新資訊28at.com

 獨享鎖是指該鎖一次只能被一個線程所持有。DeS28資訊網——每日最新資訊28at.com

 共享鎖是指該鎖可被多個線程所持有。DeS28資訊網——每日最新資訊28at.com

 對于Java ReentrantLock而言,其是獨享鎖。但是對于Lock的另一個實現類ReadWriteLock,其讀鎖是共享鎖,其寫鎖是獨享鎖。DeS28資訊網——每日最新資訊28at.com

 讀鎖的共享鎖可保證并發讀是非常高效的,寫的過程是互斥的。DeS28資訊網——每日最新資訊28at.com

 對于synchronized而言,當然是獨享鎖。DeS28資訊網——每日最新資訊28at.com

互斥鎖/讀寫鎖DeS28資訊網——每日最新資訊28at.com

 獨享鎖/共享鎖就是一種廣義的說法,互斥鎖/讀寫鎖就是具體的實現。DeS28資訊網——每日最新資訊28at.com

 互斥鎖在Java中的具體實現就是ReentrantLock。DeS28資訊網——每日最新資訊28at.com

 讀寫鎖在Java中的具體實現就是ReadWriteLock。DeS28資訊網——每日最新資訊28at.com

可重入鎖DeS28資訊網——每日最新資訊28at.com

 可重入鎖又名遞歸鎖,是指在同一個線程在外層方法獲取鎖的時候,在進入內層方法會自動獲取鎖。說的有點抽象,下面會有一個代碼的示例。DeS28資訊網——每日最新資訊28at.com

對于Java ReetrantLock而言,從名字就可以看出是一個重入鎖,其名字是Reentrant Lock 重新進入鎖。DeS28資訊網——每日最新資訊28at.com

對于synchronized而言,也是一個可重入鎖。可重入鎖的一個好處是可一定程度避免死鎖。DeS28資訊網——每日最新資訊28at.com

synchronized void setA() throws Exception{    Thread.sleep(1000);    setB();}synchronized void setB() throws Exception{    Thread.sleep(1000);}

上面的代碼就是一個可重入鎖的一個特點。如果不是可重入鎖的話,setB可能不會被當前線程執行,可能造成死鎖。DeS28資訊網——每日最新資訊28at.com

公平鎖/非公平鎖DeS28資訊網——每日最新資訊28at.com

公平鎖是指多個線程按照申請鎖的順序來獲取鎖。DeS28資訊網——每日最新資訊28at.com

非公平鎖是指多個線程獲取鎖的順序并不是按照申請鎖的順序,有可能后申請的線程比先申請的線程優先獲取鎖。DeS28資訊網——每日最新資訊28at.com

對于Java ReetrantLock而言,通過構造函數指定該鎖是否是公平鎖,默認是非公平鎖。非公平鎖的優點在于吞吐量比公平鎖大。DeS28資訊網——每日最新資訊28at.com

對于synchronized而言,也是一種非公平鎖。DeS28資訊網——每日最新資訊28at.com

分段鎖DeS28資訊網——每日最新資訊28at.com

分段鎖其實是一種鎖的設計,并不是具體的一種鎖,對于ConcurrentHashMap而言,其并發的實現就是通過分段鎖的形式來實現高效的并發操作。DeS28資訊網——每日最新資訊28at.com

我們以ConcurrentHashMap來說一下分段鎖的含義以及設計思想,ConcurrentHashMap中的分段鎖稱為Segment,它即類似于HashMap(JDK7和JDK8中HashMap的實現)的結構,即內部擁有一個Entry數組,數組中的每個元素又是一個鏈表;同時又是一個ReentrantLock(Segment繼承了ReentrantLock)。DeS28資訊網——每日最新資訊28at.com

分段鎖的設計目的是細化鎖的粒度,當操作不需要更新整個數組的時候,就僅僅針對數組中的一項進行加鎖操作。DeS28資訊網——每日最新資訊28at.com

偏向鎖/輕量級鎖/重量級鎖DeS28資訊網——每日最新資訊28at.com

偏向鎖是指一段同步代碼一直被一個線程所訪問,那么該線程會自動獲取鎖。降低獲取鎖的代價。DeS28資訊網——每日最新資訊28at.com

輕量級鎖是指當鎖是偏向鎖的時候,被另一個線程所訪問,偏向鎖就會升級為輕量級鎖,其他線程會通過自旋的形式嘗試獲取鎖,不會阻塞,提高性能。DeS28資訊網——每日最新資訊28at.com

重量級鎖是指當鎖為輕量級鎖的時候,另一個線程雖然是自旋,但自旋不會一直持續下去,當自旋一定次數的時候,還沒有獲取到鎖,就會進入阻塞,該鎖膨脹為重量級鎖。重量級鎖會讓他申請的線程進入阻塞,性能降低。DeS28資訊網——每日最新資訊28at.com

自旋鎖DeS28資訊網——每日最新資訊28at.com

 在Java中,自旋鎖是指嘗試獲取鎖的線程不會立即阻塞,而是采用循環的方式去嘗試獲取鎖,這樣的好處是減少線程上下文切換的消耗,缺點是循環會消耗CPU。DeS28資訊網——每日最新資訊28at.com

11、Java中的鎖:synchronized

synchronized是并發編程中接觸的最基本的同步關鍵字,是一種重量級鎖,也是java內置的同步機制,首先我們知道synchronized提供了互斥性和可見性,那么我們可以通過使用它來保證并發的安全。DeS28資訊網——每日最新資訊28at.com

synchronized三種用法:DeS28資訊網——每日最新資訊28at.com

  • 對象鎖

當使用synchronized修飾類普通方法時,那么當前加鎖的級別就是實例對象,當多個線程并發訪問該對象的同步方法、同步代碼塊時,會進行同步。DeS28資訊網——每日最新資訊28at.com

  • 類鎖

當使用synchronized修飾類靜態方法時,那么當前加鎖的級別就是類,當多個線程并發訪問該類(所有實例對象)的同步方法以及同步代碼塊時,會進行同步。DeS28資訊網——每日最新資訊28at.com

  • 同步代碼塊

當使用synchronized修飾代碼塊時,那么當前加鎖的級別就是synchronized(X)中配置的x對象實例,當多個線程并發訪問該對象的同步方法、同步代碼塊以及當前的代碼塊時,會進行同步。DeS28資訊網——每日最新資訊28at.com

使用同步代碼塊時要注意的是不要使用String類型對象,因為String常量池的存在,所以很容易導致出問題。DeS28資訊網——每日最新資訊28at.com

synchronized 同步代碼塊

synchronized 同步代碼塊解決線程安全問題DeS28資訊網——每日最新資訊28at.com

什么是線程安全問題: 多線程操作共享數據導致共享數據出現錯亂DeS28資訊網——每日最新資訊28at.com

出現線程安全問題的條件:
1.有多個線程
2.有共享數據
3.多線程操作共享數據DeS28資訊網——每日最新資訊28at.com

代碼:DeS28資訊網——每日最新資訊28at.com

package cn.itcast.thread;/** 學習使用同步代碼塊解決線程安全問題 */public class Test8 { // 定義票的總數量 private static int ticket = 100; public static void main(String[] args) { Runnable runnable = () ->{ // 循環買票 while (true){ try { Thread.sleep(1); } catch (InterruptedException e) { e.printStackTrace(); } // 同步代碼塊(類鎖) synchronized (Test8.class) { if (ticket > 0) { ticket--; System.out.println(Thread.currentThread().getName() + "賣了一張票,剩余:" + ticket); } else { // 票沒了 break; } } } }; // 創建3個線程 Thread t1 = new Thread(runnable, "窗口1"); Thread t2 = new Thread(runnable, "窗口2"); Thread t3 = new Thread(runnable, "窗口3"); t1.start(); t2.start(); t3.start(); }}

小結DeS28資訊網——每日最新資訊28at.com

  • 哪些對象可以作為鎖?

任意對象。DeS28資訊網——每日最新資訊28at.com

  • synchronized鎖對象時候要注意什么?

多線程并發方法同步代碼塊,需要鎖同一個對象。DeS28資訊網——每日最新資訊28at.com

  • synchronized中的鎖的作用是什么?

同步代碼塊中有鎖的線程進入,無鎖的線程需要等待。DeS28資訊網——每日最新資訊28at.com

synchronized 同步方法DeS28資訊網——每日最新資訊28at.com

synchronized 同步方法解決線程安全問題DeS28資訊網——每日最新資訊28at.com

語法DeS28資訊網——每日最新資訊28at.com

// 普通同步方法,對當前一個實例對象加鎖,多線程操作同一個對象實例進行同步操作public synchronized void 方法名() { ...}// 靜態同步方法,對類加鎖,多線程操作當前類所有實例對象進行同步操作public static synchronized void 方法名() { ...}

代碼:DeS28資訊網——每日最新資訊28at.com

package cn.itcast.thread;/** 學習使用同步代碼塊解決線程安全問題 */public class Test9 { // 定義票的總數量 private static int ticket = 100; public static void main(String[] args) { Runnable runnable = () ->{ // 循環買票 while (true){ try { Thread.sleep(1); } catch (InterruptedException e) { e.printStackTrace(); } sell(); } }; // 創建3個線程 Thread t1 = new Thread(runnable, "窗口1"); Thread t2 = new Thread(runnable, "窗口2"); Thread t3 = new Thread(runnable, "窗口3"); t1.start(); t2.start(); t3.start(); } // 同步方法 private static synchronized void sell() { // 同步代碼塊(類鎖) if (ticket > 0) { ticket--; System.out.println(Thread.currentThread().getName() + "賣了一張票,剩余:" + ticket); } }}

小結DeS28資訊網——每日最新資訊28at.com

synchronized是通過對象內部的一個叫==監視器鎖==來實現的,但是監視器鎖本質又是依賴底層的操作系統的Mutex(互斥) Lock來實現的,而操系統實現線程之間的切換造成帶量CPU資源浪費,這個成本非常的高,狀態之間的轉換需要相對比較長的時間,這就是為什么Synchronized效率低的原因,因此這種依賴于操作系統Mutex Lock所實現的鎖我們稱之為:==重量級鎖==。JDK中對Synchronized做的種種優化,其核心都是為了減少這種重量級鎖的使用,JDK1.5以后,為來減少獲得鎖和釋放鎖所帶來的性能消耗, JDK引入了:”輕量級鎖“和”偏向鎖“進行優化,這個優化自動的無需開發人員介入。DeS28資訊網——每日最新資訊28at.com

synchronized 屬于最基本的線程通信機制,基于對象監視器實現的。Java中的每個對象都與一個監視器相關聯,一個線程可以鎖定或解鎖。一次只有一個線程可以鎖定監視器。試圖鎖定該監視器的任何其他線程都會被阻塞,直到它們可以獲得該監視器上的鎖定為止。DeS28資訊網——每日最新資訊28at.com

12Java中的鎖:ReentrantLock

目標學習使用ReentrantLock可重入鎖解決線程安全問題DeS28資訊網——每日最新資訊28at.com

介紹DeS28資訊網——每日最新資訊28at.com

  • synchronized 是內部鎖,自動化的上鎖與釋放鎖,而lock是手動的,需要人為的上鎖和釋放鎖,lock比較靈活,但是代碼相對較多
  • lock接口異常的時候不會自動的釋放鎖,同樣需要手動的釋放鎖,所以一般寫在finally語句塊中,而synchronized則會在異常的時候自動的釋放鎖

API方法DeS28資訊網——每日最新資訊28at.com

  • lock()

用來獲取鎖,如果鎖被其他線程獲取,處于等待狀態。如果采用Lock,必須主動去釋放鎖,并且在發生異常的時候,不會自動釋放鎖。因此一般來說,使用Lock必須早try{}catch{}塊中進行,并且將釋放鎖的操作放在finally塊中進行,以保證鎖一定被釋放,防止死鎖發生。DeS28資訊網——每日最新資訊28at.com

  • lockInterruptibly()

通過這個這個方法去獲取鎖時,如果線程正在等待獲取鎖,則這個線程能夠響應中斷,即中斷線程的等待狀態。DeS28資訊網——每日最新資訊28at.com

  • tryLock()

tryLock方法是有返回值的,它表示用來嘗試獲取鎖,如果獲取成功,則返回true,如果獲取失敗(即鎖已經由其他線程獲取),則返回false,也就是說這個方法無論如何都會立即返回。在獲取不到鎖的時候,不會再那一直等待。DeS28資訊網——每日最新資訊28at.com

  • tryLock(long time, TimeUnit unit)

與tryLock類似,只不過是有等待時間,在等待時間內獲取到鎖返回true,超時返回false。DeS28資訊網——每日最新資訊28at.com

  • unlock()

釋放鎖,一定要在finally塊中釋放。DeS28資訊網——每日最新資訊28at.com

Lock鎖使用語法DeS28資訊網——每日最新資訊28at.com

Lock介紹:  DeS28資訊網——每日最新資訊28at.com

  • 比synchronized更靈活,可以自己調用方法    
  • void lock() 獲得鎖    
  • void unlock() 釋放鎖

Lock實現類:  DeS28資訊網——每日最新資訊28at.com

ReentrantLockDeS28資訊網——每日最新資訊28at.com

Lock使用標準方式DeS28資訊網——每日最新資訊28at.com

l.lock(); // 獲得鎖    try {        操作共享資源的代碼    } finally {        l.unlock(); // 釋放鎖    }

代碼:DeS28資訊網——每日最新資訊28at.com

package cn.itcast.thread;import java.util.concurrent.locks.ReentrantLock;/** 學習使用Lock解決線程安全問題 */public class Test10 {    // 定義票的總數量    private static int ticket = 100;    public static void main(String[] args) {        // 創建可重入鎖對象        ReentrantLock lock = new ReentrantLock();        Runnable runnable = () -> {            // 循環賣票            while (true) {                try {                    Thread.sleep(10);                    // 獲得鎖                    lock.lock();                    if (ticket > 0) {                        ticket--;                        System.out.println(Thread.currentThread().getName() +                                "賣了一張票,剩余:" + ticket);                    } else {                        break;                    }                } catch (InterruptedException e) {                    e.printStackTrace();                } finally {                    // 釋放鎖                    lock.unlock();                }            }        };        // 創建3個線程        Thread t1 = new Thread(runnable, "窗口1");        Thread t2 = new Thread(runnable, "窗口2");        Thread t3 = new Thread(runnable, "窗口3");        t1.start();        t2.start();        t3.start();    }}

13、Java中的鎖:ReentrantReadWriteLock

目標:掌握Readwriterlock讀寫鎖分析和場景DeS28資訊網——每日最新資訊28at.com

ReadWriteLock接口介紹DeS28資訊網——每日最新資訊28at.com

ReadWriteLock也是一個接口,在它里面只定義了兩個方法:DeS28資訊網——每日最新資訊28at.com

package java.util.concurrent.locks;public interface ReadWriteLock { Lock readLock(); Lock writeLock();}

一個用來獲取讀鎖,一個用來獲取寫鎖。也就是說將文件的讀寫操作分開,分成2個鎖來分配給線程,從而使得多個線程可以同時進行讀操作。下面的ReentrantReadWriteLock實現了ReadWriteLock接口。 DeS28資訊網——每日最新資訊28at.com

ReentrantReadWriteLock介紹DeS28資訊網——每日最新資訊28at.com

ReentrantReadWriteLock里面提供了很多豐富的方法,不過最主要的有兩個方法readLock() 和 writeLock()用來獲取讀鎖和寫鎖。 DeS28資訊網——每日最新資訊28at.com

ReentrantReadWriteLock可重入讀寫鎖DeS28資訊網——每日最新資訊28at.com

package cn.itcast.thread;import java.util.concurrent.locks.ReentrantReadWriteLock;// ReentrantReadWriteLock: 可重入讀寫鎖 (讀鎖是共享鎖,寫鎖是獨享鎖)public class Test11 { // 定義可重入讀寫鎖 private ReentrantReadWriteLock rw = new ReentrantReadWriteLock(); public static void main(String[] args) { final Test11 test = new Test11();  new Thread(() -> { test.get(Thread.currentThread()); }).start(); new Thread(() -> { test.get(Thread.currentThread()); }).start(); } public void get(Thread thread) { // 讀鎖是共享鎖 rw.readLock().lock(); // 寫鎖是獨享鎖 //rw.writeLock().lock(); try { for (int i = 0; i < 50; i++){ System.out.println(thread.getName() + "正在進行讀操作"); } System.out.println(thread.getName() + "讀操作完畢"); }finally { rw.readLock().unlock(); //rw.writeLock().unlock(); } }}

小結DeS28資訊網——每日最新資訊28at.com

ReentrantReadWriteLock的優勢與應用場景DeS28資訊網——每日最新資訊28at.com

1. 大大提升了讀操作的效率。

2. 不過要注意的是,如果有一個線程已經占用了讀鎖,則此時其他線程如果要申請寫鎖,則申請寫鎖的線程會一直等待釋放讀鎖。

3. 如果有一個線程已經占用了寫鎖,則此時其他線程如果申請寫鎖或者讀鎖,則申請的線程會一直等待釋放寫鎖。

4. ReentrantReadWriteLock適合讀多寫少的應用場景DeS28資訊網——每日最新資訊28at.com

14原子更新基本類型

目標掌握java.util.concurrent.atomic包下原子更新基本類型DeS28資訊網——每日最新資訊28at.com

介紹DeS28資訊網——每日最新資訊28at.com

Java從JDK 1.5開始提供了java.util.concurrent.atomic包(以下簡稱Atomic包),這個包中的原子操作類提供了一種用法簡單、性能高效、線程安全地更新一個變量的方式。actomic實現原子性操作采用的是CAS算法保證原子性操作,性能高效。DeS28資訊網——每日最新資訊28at.com

CAS原理分析:DeS28資訊網——每日最新資訊28at.com

使用CAS(Compare-And-Swap)比較并交換,操作保證數據原子性DeS28資訊網——每日最新資訊28at.com

CAS算法是 JDK對并發操作共享數據的支持,包含了3個操作數 DeS28資訊網——每日最新資訊28at.com

第一個操作數內存值value(V) DeS28資訊網——每日最新資訊28at.com

第二個操作數預估值expect(E) DeS28資訊網——每日最新資訊28at.com

第三個操作數更新值new(N)DeS28資訊網——每日最新資訊28at.com

含義當多線程每個線程執行寫的操作時,每個線程都會讀取主存最新內存值value,并設置預估的值,只有最新內存值與預估值一致的線程,就會將需要更新的值更新到主存中,其他線程就會失敗保證原子性操作;這樣就解決了synchronized排隊導致性能低下的問題。DeS28資訊網——每日最新資訊28at.com

DeS28資訊網——每日最新資訊28at.com

java.util.concurrent.atomic包的原子類:DeS28資訊網——每日最新資訊28at.com

DeS28資訊網——每日最新資訊28at.com

原子更新基本類型DeS28資訊網——每日最新資訊28at.com

DeS28資訊網——每日最新資訊28at.com

含義DeS28資訊網——每日最新資訊28at.com

AtomicBooleanDeS28資訊網——每日最新資訊28at.com

原子更新布爾類型DeS28資訊網——每日最新資訊28at.com

AtomicIntegerDeS28資訊網——每日最新資訊28at.com

原子更新整型DeS28資訊網——每日最新資訊28at.com

AtomicLongDeS28資訊網——每日最新資訊28at.com

原子更新長整型DeS28資訊網——每日最新資訊28at.com

上面3個類提供方法完全一樣,所以我們以AtomicInteger為例進行講解API方法:DeS28資訊網——每日最新資訊28at.com

方法DeS28資訊網——每日最新資訊28at.com

含義DeS28資訊網——每日最新資訊28at.com

int addAndGet(int delta)DeS28資訊網——每日最新資訊28at.com

以原子方式將輸入的數值與實例中的值(AtomicInteger里的value)相加,并返回結果DeS28資訊網——每日最新資訊28at.com

boolean compareAndSet(int expect,int update)DeS28資訊網——每日最新資訊28at.com

如果輸入的數值等于預期值,則以原子方式將該值設置為輸入的值DeS28資訊網——每日最新資訊28at.com

int getAndIncrement()DeS28資訊網——每日最新資訊28at.com

以原子方式將當前值加1,注意,這里返回的是自增前的值DeS28資訊網——每日最新資訊28at.com

void lazySet(int newValue)DeS28資訊網——每日最新資訊28at.com

最終會設置成newValue,使用lazySet設置值后,可能導致其他線程在之后的一小段時間內還是可以讀到舊的值DeS28資訊網——每日最新資訊28at.com

int getAndSet(int newValue)DeS28資訊網——每日最新資訊28at.com

以原子方式設置為newValue的值,并返回舊值。DeS28資訊網——每日最新資訊28at.com

int incrementAndGet()DeS28資訊網——每日最新資訊28at.com

以原子方式將當前值加1,注意,這里返回的是自增后的值DeS28資訊網——每日最新資訊28at.com

代碼:DeS28資訊網——每日最新資訊28at.com

package cn.itcast.thread;import java.util.ArrayList;import java.util.List;import java.util.concurrent.atomic.AtomicInteger;public class Test12 implements Runnable { // 定義整型并發原子對象 private static AtomicInteger atomicInteger = new AtomicInteger(0); @Override public void run() { try { // 線程休眠 Thread.sleep(200); } catch (InterruptedException e) { e.printStackTrace(); } // 進行原子性操作+1 System.out.println(atomicInteger.incrementAndGet()); } public static void main(String[] args) throws InterruptedException { // 創建List集合 List<Thread> list = new ArrayList<>(); Test12 task = new Test12(); // 開啟多線程進行操作共享變量 for (int i = 0; i <10 ; i++) { Thread thread = new Thread(task); list.add(thread); thread.start(); } for (Thread thread : list) { thread.join(); // 確保所有thread全部運行 } System.out.println("遞增結果:" + atomicInteger.get()); }}

運行效果DeS28資訊網——每日最新資訊28at.com

DeS28資訊網——每日最新資訊28at.com


DeS28資訊網——每日最新資訊28at.com

15原子更新數組類型

目標掌握java.util.concurrent.atomic包下原子更新數組類型DeS28資訊網——每日最新資訊28at.com

介紹DeS28資訊網——每日最新資訊28at.com

通過原子的方式更新數組里的某個元素,Atomic包提供了以下3個類。這幾個類提供的方法幾乎一樣,所以本節僅以AtomicIntegerArray為例進行講解。DeS28資訊網——每日最新資訊28at.com

DeS28資訊網——每日最新資訊28at.com

含義DeS28資訊網——每日最新資訊28at.com

AtomicIntegerArrayDeS28資訊網——每日最新資訊28at.com

原子更新整型數組里的元素DeS28資訊網——每日最新資訊28at.com

AtomicLongArrayDeS28資訊網——每日最新資訊28at.com

原子更新長整型數組里的元素DeS28資訊網——每日最新資訊28at.com

AtomicReferenceArrayDeS28資訊網——每日最新資訊28at.com

原子更新引用類型數組里的元素DeS28資訊網——每日最新資訊28at.com

AtomicIntegerArray類:DeS28資訊網——每日最新資訊28at.com

方法DeS28資訊網——每日最新資訊28at.com

含義DeS28資訊網——每日最新資訊28at.com

int addAndGet(int i,int delta)DeS28資訊網——每日最新資訊28at.com

以原子方式將輸入值與數組中索引i的元素相加DeS28資訊網——每日最新資訊28at.com

boolean compareAndSet(int i,int expect,int update)DeS28資訊網——每日最新資訊28at.com

如果當前值等于預期值,則以原子方式將數組位置i的元素設置成update值DeS28資訊網——每日最新資訊28at.com

代碼:DeS28資訊網——每日最新資訊28at.com

package cn.itcast.thread;import java.util.ArrayList;import java.util.List;import java.util.concurrent.atomic.AtomicIntegerArray;public class Test13 implements Runnable{ // 定義數組 private static int[] ints = {0,2,3}; // 定義原子整型數組對象 private static AtomicIntegerArray atomicIntegerArray = new AtomicIntegerArray(ints); @Override public void run() { try { // 線程休眠 Thread.sleep(200); } catch (InterruptedException e) { e.printStackTrace(); } // 操作普通數組中的第一個元素 +1 ints[0] = ints[0] + 1; // 操作原子數組中的第一個元素 +1 System.out.println(atomicIntegerArray.incrementAndGet(0)); } public static void main(String[] args) throws InterruptedException { // 創建List集合 List<Thread> list = new ArrayList<>(); Test13 task = new Test13(); // 開啟多線程進行操作共享變量 for (int i = 0; i <10 ; i++) { Thread thread = new Thread(task); list.add(thread); thread.start(); } for (Thread thread : list) { thread.join(); // 確保所有thread全部運行 } System.out.println("原子數組操作結果:" + atomicIntegerArray.get(0)); // 10 System.out.println("普通數組操作結果:" + ints[0]); // 不一定 }}

運行效果DeS28資訊網——每日最新資訊28at.com

DeS28資訊網——每日最新資訊28at.com

注意:DeS28資訊網——每日最新資訊28at.com

數組value通過構造方法傳遞進去,然后AtomicIntegerArray會將當前數組復制一份,所以當AtomicIntegerArray對內部的數組元素進行修改時,不會影響傳入的數組。DeS28資訊網——每日最新資訊28at.com

16原子更新引用類型

目標使用原子操作更新引用類型數據(也就是原子更新多個變量)DeS28資訊網——每日最新資訊28at.com

介紹DeS28資訊網——每日最新資訊28at.com

原子更新基本類型的AtomicInteger,只能更新一個變量,如果要原子更新多個變量,就需要使用這個原子更新引用類型提供的類。Atomic包提供了以下3個類:DeS28資訊網——每日最新資訊28at.com

DeS28資訊網——每日最新資訊28at.com

介紹DeS28資訊網——每日最新資訊28at.com

AtomicReferenceDeS28資訊網——每日最新資訊28at.com

原子更新引用類型DeS28資訊網——每日最新資訊28at.com

AtomicReferenceFieldUpdaterDeS28資訊網——每日最新資訊28at.com

原子更新引用類型里的字段DeS28資訊網——每日最新資訊28at.com

AtomicMarkableReferenceDeS28資訊網——每日最新資訊28at.com

原子更新帶有標記位的引用類型。可以原子更新一個布爾類型的標記位和引用類型。構造方法AtomicMarkableReference(V initialRef,boolean initialMark)DeS28資訊網——每日最新資訊28at.com

AtomicReference類:DeS28資訊網——每日最新資訊28at.com

方法DeS28資訊網——每日最新資訊28at.com

介紹DeS28資訊網——每日最新資訊28at.com

boolean compareAndSet(V expect, V update)DeS28資訊網——每日最新資訊28at.com

如果是期望值expect與當前內存值一樣,更新為updateDeS28資訊網——每日最新資訊28at.com

代碼:DeS28資訊網——每日最新資訊28at.com

package cn.itcast.thread;import java.util.concurrent.atomic.AtomicReference;public class Test14 { public static void main(String[] args) { // 1. 創建一個User對象封裝數據 User user = new User("李小華", 18); // 2. 創建一個原子引用類型AtomicReference操作User類型數據 AtomicReference<User> atomicReference = new AtomicReference<>(); // 3. 將user對象的數據存入原子引用類型對象中 atomicReference.set(user); // 4. 更新原子引用類型存儲的數據 atomicReference.compareAndSet(user, new User("李中華", 20)); // 5. 打印普通user對象數據與原子引用類型對象數據 System.out.println("普通對象數據:"+ user +",對象hashcode: " + user.hashCode()); System.out.println("原子引用類型對象數據:" + atomicReference.get() + ",對象hashcode: " + atomicReference.get().hashCode()); }}

運行效果DeS28資訊網——每日最新資訊28at.com

DeS28資訊網——每日最新資訊28at.com

17并發工具類:CountDownLatch(倒計數閉鎖)

目標:掌握CountDownLatch使用(實現等待其他線程處理完才繼續運行當前線程)DeS28資訊網——每日最新資訊28at.com

介紹DeS28資訊網——每日最新資訊28at.com

CountDownLatch是一個同步輔助類,也叫倒計數閉鎖,在完成一組正在其他線程中執行的操作之前,它允許一個或多個線程一直等待。用給定的計數初始化 CountDownLatch。這個輔助類可以進行計算遞減,所以在當前計數到達零之前,可以讓現場一直受阻塞。到達0之后,會釋放所有等待的線程,執行后續操作。DeS28資訊網——每日最新資訊28at.com

CountDownLatch類DeS28資訊網——每日最新資訊28at.com

說明DeS28資訊網——每日最新資訊28at.com

CountDownLatch(int count)DeS28資訊網——每日最新資訊28at.com

創建CountDownLatch 實例并設置預定計數次數。DeS28資訊網——每日最新資訊28at.com

void countDown()DeS28資訊網——每日最新資訊28at.com

遞減鎖存器的計數,如果計數到達零,則釋放所有等待的線程。如果當前計數大于零,則將計數減少1。DeS28資訊網——每日最新資訊28at.com

void await()DeS28資訊網——每日最新資訊28at.com

使當前線程在鎖存器倒計數至零之前一直等待,除非線程被中斷。如果當前的計數為零,則此方法立即返回。DeS28資訊網——每日最新資訊28at.com

CountDownLatch 是通過一個計數器來實現的,計數器的初始值為線程的數量。每當一個線程完成了自己的任務后,計數器的值就會減 1。當計數器值到達 0 時,表示所有的線程已經完成了任務,然后在閉鎖上等待的線程就可以恢復執行任務。DeS28資訊網——每日最新資訊28at.com

DeS28資訊網——每日最新資訊28at.com

傳統join阻塞案例代碼:DeS28資訊網——每日最新資訊28at.com

package cn.itcast.thread;public class Test15 { public static void main(String[] args) throws InterruptedException { // 創建線程1 Thread t1 = new Thread(() -> { System.out.println("parser1 finish"); }); // 創建線程2 Thread t2 = new Thread(() -> { System.out.println("parser2 finish"); }); t1.start(); t2.start(); t1.join(); // join阻塞 t2.join(); // join阻塞 System.out.println("join方式: all parser finish"); }}

join阻塞效果:DeS28資訊網——每日最新資訊28at.com

DeS28資訊網——每日最新資訊28at.com

使用CountDownLatch實現阻塞代碼優化:DeS28資訊網——每日最新資訊28at.com

package cn.itcast.thread;import java.util.concurrent.CountDownLatch;public class Test16 { // 定義倒計數閉鎖對象 private static CountDownLatch countDownLatch = new CountDownLatch(2); public static void main(String[] args) throws InterruptedException { // 創建線程1 Thread t1 = new Thread(() -> { System.out.println("parser1 finish"); countDownLatch.countDown(); // 計算遞減1 }); // 創建線程2 Thread t2 = new Thread(() -> { System.out.println("parser2 finish"); countDownLatch.countDown(); // 計算遞減1 }); t1.start(); t2.start(); countDownLatch.await(); // 阻塞,計算為0釋放阻塞,運行后面的代碼 System.out.println("join方式: all parser finish"); }}

使用CountDownLatch實現阻塞效果:DeS28資訊網——每日最新資訊28at.com

DeS28資訊網——每日最新資訊28at.com

CountDownLatch倒計數閉鎖好處實現線程最大并發執行。DeS28資訊網——每日最新資訊28at.com

18并發工具類:CyclicBarrier(同步屏障)

目標掌握CyclicBarrier的使用DeS28資訊網——每日最新資訊28at.com

介紹DeS28資訊網——每日最新資訊28at.com

CyclicBarrier是JDK 1.5的 java.util.concurrent 并發包中提供的一個并發工具類。 DeS28資訊網——每日最新資訊28at.com

  • 謂 Cyclic 即循環的意思,所謂 Barrier 即屏障的意思。
  • CyclicBarrier (可重用屏障/柵欄)類似于 CountDownLatch功能一樣,都有讓多個線程等待同步然后再開始下一步動作。
  • CyclicBarrier 可以使一定數量的線程反復地在屏障位置處匯集。當線程到達屏障位置時將調用 await() 方法,這個方法將阻塞直到所有線程都到達屏障位置。如果所有線程都到達屏障位置,那么屏障將打開,此時所有的線程都將被釋放,而屏障將被重置以便下次使用。

DeS28資訊網——每日最新資訊28at.com

CyclicBarrier類DeS28資訊網——每日最新資訊28at.com

說明DeS28資訊網——每日最新資訊28at.com

CyclicBarrier(int parties)DeS28資訊網——每日最新資訊28at.com

創建對象,參數表示屏障攔截的線程數量,初始化相互等待的線程數量DeS28資訊網——每日最新資訊28at.com

int await()DeS28資訊網——每日最新資訊28at.com

告訴CyclicBarrier自己已經到達了屏障,然后當前線程被阻塞返回值int為達到屏障器的索引: 索引未達到屏障線程數量-1,0表示最后一個達到屏障DeS28資訊網——每日最新資訊28at.com

int getParties()DeS28資訊網——每日最新資訊28at.com

獲取 CyclicBarrier 打開屏障的線程數量DeS28資訊網——每日最新資訊28at.com

void reset()DeS28資訊網——每日最新資訊28at.com

使CyclicBarrier回歸初始狀態,它做了兩件事。如果有正在等待的線程,則會拋出 BrokenBarrierException 異常,且這些線程停止等待,繼續執行。將是否破損標志位broken置為 false。DeS28資訊網——每日最新資訊28at.com

boolean isBroken()DeS28資訊網——每日最新資訊28at.com

獲取是否破損標志位broken的值,此值有以下幾種情況。1.CyclicBarrier初始化時,broken=false表示屏障未破損。 2.如果正在等待的線程被中斷則broken=true表示屏障破損。 3.如果正在等待的線程超時 則broken=true表示屏障破損。 4.如果有線程調用 CyclicBarrier.reset() 方法,則broken=false,表示屏障回到未破損狀態。DeS28資訊網——每日最新資訊28at.com

int getNumberWaiting()DeS28資訊網——每日最新資訊28at.com

獲取達到屏障阻塞等待的線程數DeS28資訊網——每日最新資訊28at.com

CyclicBarrier(int parties,Runnable barrierAction)DeS28資訊網——每日最新資訊28at.com

用于所有線程到達屏障時,優先執行barrierAction的線程DeS28資訊網——每日最新資訊28at.com

代碼:DeS28資訊網——每日最新資訊28at.com

package cn.itcast.thread;import java.util.concurrent.CyclicBarrier;import java.util.concurrent.TimeUnit;public class Test17 {    // 定義同步屏障對象    private static CyclicBarrier cb = new CyclicBarrier(2);    public static void main(String[] args) {        // 創建線程1        new Thread(() -> {            try {                System.out.println("達到屏障阻塞線程數:" + cb.getNumberWaiting());                cb.await(); // 達到屏障阻塞,+1                System.out.println("運行結束1"); // 不會運行            } catch (Exception e) {                e.printStackTrace();            }        }).start();        // 創建線程2        new Thread(() -> {            try {                System.out.println("達到屏障阻塞線程數:" + cb.getNumberWaiting());                cb.await(); // 達到屏障阻塞,+1                System.out.println("運行結束2"); // 不會運行            } catch (Exception e) {                e.printStackTrace();            }        }).start();        try {            TimeUnit.SECONDS.sleep(2);            // 會運行,沒有到達屏障,不會阻塞            System.out.println("主線程完成,攔截線程數:" + cb.getParties()                    + ",達到屏障阻塞線程數:" + cb.getNumberWaiting());        } catch (InterruptedException e) {            e.printStackTrace();        }    }}

運行效果DeS28資訊網——每日最新資訊28at.com

由于兩個子線程的調度是由CPU決定的,兩個子線程都有可能先執行,所以會產生兩種輸出:DeS28資訊網——每日最新資訊28at.com

DeS28資訊網——每日最新資訊28at.com

說明:由于所有線程都達到屏障,所有阻塞線程被釋放,所以阻塞線程為0DeS28資訊網——每日最新資訊28at.com

如果把new CyclicBarrier(2)修改成new CyclicBarrier(3),則主線程和子線程會永遠等待,因為沒有第三個線程執行await方法,即沒有第三個線程到達屏障,所以之前到達屏障的兩個線程都不會繼續執行。DeS28資訊網——每日最新資訊28at.com

private static CyclicBarrier cb = new CyclicBarrier(3);

DeS28資訊網——每日最新資訊28at.com

19、并發工具類:Semaphore(信號量)

目標:掌握Semaphore的使用DeS28資訊網——每日最新資訊28at.com

介紹DeS28資訊網——每日最新資訊28at.com

Semaphore(信號量)限制著訪問某些資源的線程數量,在到達限制的線程數量之前,線程能夠繼續進行資源的訪問,一旦訪問資源的數量到達限制的線程數量,這個時候線程就不能夠再去獲取資源,只有等待有線程退出資源的獲取。DeS28資訊網——每日最新資訊28at.com

應用場景DeS28資訊網——每日最新資訊28at.com

比如模擬一個停車場停車信號,假設停車場只有兩個車位,一開始兩個車位都是空的。這時同時來了兩輛車,看門人允許它們進入停車場,然后放下車攔。以后來的車必須在入口等待,直到停車場中有車輛離開。這時,如果有一輛車離開停車場,看門人得知后,打開車攔,放入一輛,如果又離開一輛,則又可以放入一輛,如此往復。DeS28資訊網——每日最新資訊28at.com

DeS28資訊網——每日最新資訊28at.com

API方法DeS28資訊網——每日最新資訊28at.com

信號量維護了一個許可集。如有必要,在許可可用前會阻塞每一個 acquire(),然后再獲取該許可。每個 release() 添加一個許可,從而可能釋放一個正在阻塞的獲取者讓其運行。但是,不使用實際的許可對象,Semaphore 只對可用許可的號碼進行計數,并采取相應的行動。 DeS28資訊網——每日最新資訊28at.com

所以一個Semaphore 信號量有且僅有 3 種操作,且它們全部是原子的。 DeS28資訊網——每日最新資訊28at.com

  • 初始化許可集、增加許可、獲取許可。
  • 增加許可,release()方法釋放一個阻塞,增加一個許可。
  • 獲取許可,acquire()方法獲取許可,再獲取許可前處于阻塞等待。

方法DeS28資訊網——每日最新資訊28at.com

說明DeS28資訊網——每日最新資訊28at.com

Semaphore(int permits)DeS28資訊網——每日最新資訊28at.com

permits是允許同時運行的線程數目創建指定數據線程的信號量DeS28資訊網——每日最新資訊28at.com

Semaphore(int permits, boolean fair)DeS28資訊網——每日最新資訊28at.com

permits是允許同時運行的線程數目創建指定數據線程的信號量;fair指定是公平模式還是非公平模式,默認非公平模式DeS28資訊網——每日最新資訊28at.com

void acquire()DeS28資訊網——每日最新資訊28at.com

方法阻塞,直到申請獲取到許可證才可以運行當前線程DeS28資訊網——每日最新資訊28at.com

void release()DeS28資訊網——每日最新資訊28at.com

釋放當前線程一個阻塞的 acquire() 方法,方法增加一個許可證DeS28資訊網——每日最新資訊28at.com

intavailablePermits()DeS28資訊網——每日最新資訊28at.com

返回此信號量中當前可用的許可證數DeS28資訊網——每日最新資訊28at.com

intgetQueueLength()DeS28資訊網——每日最新資訊28at.com

返回正在等待獲取許可證的線程數DeS28資訊網——每日最新資訊28at.com

booleanhasQueuedThreads()DeS28資訊網——每日最新資訊28at.com

是否有線程正在等待獲取許可證DeS28資訊網——每日最新資訊28at.com

void reducePermits(int reduction)DeS28資訊網——每日最新資訊28at.com

減少reduction個許可證,是個protected方法DeS28資訊網——每日最新資訊28at.com

Collection getQueuedThreads()DeS28資訊網——每日最新資訊28at.com

返回所有等待獲取許可證的線程集合,是個protected方法DeS28資訊網——每日最新資訊28at.com

代碼:DeS28資訊網——每日最新資訊28at.com

package cn.itcast.thread;import java.util.concurrent.Semaphore;import java.util.concurrent.TimeUnit;public class Test18 {    public static void main(String[] args) {        // 1. 創建信號量對象控制并發線程數量,設置許可數5個(同時運行5個線程)        Semaphore semaphore = new Semaphore(5, true);        // 2. 循環運行10個線程(會看到每次只允許5個線程)        for (int i = 0; i < 10; i++) {            new Thread(() -> {                try {                    // 2.1 申請獲取許可                    semaphore.acquire();                    // 2.2 運行業務                    System.out.println(Thread.currentThread().getName() + "車,進入停車場");                    TimeUnit.SECONDS.sleep(3);// 讓當前線程休眠(讓線程多運行一會,方便觀察效果)                    System.out.println(Thread.currentThread().getName() + "車,離開停車場");                    // 2.3 釋放阻塞,增加一個許可(讓下一個阻塞的線程運行)                    semaphore.release();                } catch (Exception e) {                    e.printStackTrace();                }            }).start();        }    }}

運行效果DeS28資訊網——每日最新資訊28at.com

DeS28資訊網——每日最新資訊28at.com

特點DeS28資訊網——每日最新資訊28at.com

Semaphore 在計數器不為 0 的時候對線程就放行,一旦達到 0,那么所有請求資源的新線程都會被阻塞,包括增加請求到許可的線程,Semaphore 是不可重入的。 DeS28資訊網——每日最新資訊28at.com

  • 每一次請求一個許可都會導致計數器減少 1,同樣每次釋放一個許可都會導致計數器增加 1,一旦達到 0,新的許可請求線程將被掛起。

Semaphore 有兩種模式,公平模式 和 非公平模式(默認使用)DeS28資訊網——每日最新資訊28at.com

  • 公平模式就是調用 acquire 的順序就是獲取許可證的順序,遵循 FIFO。
Semaphore semaphore = new Semaphore(許可數, true); // 公平模式
  • 非公平模式是搶占式的,也就是有可能一個新的獲取線程恰好在一個許可證釋放時得到了這個許可證,而前面還有等待的線程。
Semaphore semaphore = new Semaphore(許可數, false); // 非公平模式,(默認)

小結DeS28資訊網——每日最新資訊28at.com

  • Semaphore 的使用(3個操作)

     – 初始化許可集DeS28資訊網——每日最新資訊28at.com

     – 增加許可,release()方法釋放一個阻塞,增加一個許可。DeS28資訊網——每日最新資訊28at.com

     – 獲取許可,acquire()方法獲取許可,再獲取許可前處于阻塞等待。DeS28資訊網——每日最新資訊28at.com

20并發工具類:Exchanger(交換數據)

目標:掌握Exchanger的使用DeS28資訊網——每日最新資訊28at.com

介紹DeS28資訊網——每日最新資訊28at.com

Exchanger(交換者)是一個用于線程間協作的工具類,可以用于進行線程間的數據交換。DeS28資訊網——每日最新資訊28at.com

交換數據原理DeS28資訊網——每日最新資訊28at.com

  • Exchanger提供一個同步點,在這個同步點兩個線程可以交換彼此的數據。
  • 兩個線程通過 exchange() 方法交換數據。
  • 第一個線程先執行 exchange() 方法,會一直等待第二個線程也執行exchange()到達同步點時,兩個線程交換數據,將本線程生產出來的數據傳遞給對方。
  • 使用 Exchanger 的重點是用對的線程使用 exchange() 方法。

DeS28資訊網——每日最新資訊28at.com

API方法DeS28資訊網——每日最新資訊28at.com

方法DeS28資訊網——每日最新資訊28at.com

說明DeS28資訊網——每日最新資訊28at.com

V exchange(V x)DeS28資訊網——每日最新資訊28at.com

用于進行線程間的數據交換DeS28資訊網——每日最新資訊28at.com

V exchange(V x, long timeout, TimeUnit unit)DeS28資訊網——每日最新資訊28at.com

設置交換數據并等待超時時間DeS28資訊網——每日最新資訊28at.com

代碼:DeS28資訊網——每日最新資訊28at.com

package cn.itcast.thread;import java.util.concurrent.Exchanger;public class Test19 {    public static void main(String[] args) {        // 1. 創建交換數據對象,并設置傳輸數據的類型        Exchanger<String> exchanger = new Exchanger<>();        // 2. 啟動2個線程進行交換數據        // 創建線程1        new Thread(() -> {            // 2.1 定義交換的數據            String girl1 = "【柳巖】";            System.out.println(Thread.currentThread().getName() + "說:我的女友 " + girl1);            System.out.println(Thread.currentThread().getName() + "說:等待線程2交換數據");            // 2.2 將數據交換給線程2,并拿到線程2的數據            try {                String b = exchanger.exchange(girl1);//注意:如果線程2沒有到達同步點,當前線程會被阻塞一直等到                //成功獲取線程2的數據后                System.out.println(Thread.currentThread().getName() + "說:我拿到了 " + b);            } catch (InterruptedException e) {                e.printStackTrace();            }        }).start();        // 創建線程2        new Thread(()->{            // 2.3 定義交換的數據            String girl2 = "【楊冪】";            System.out.println(Thread.currentThread().getName()+"說:我的女友 " + girl2);            System.out.println(Thread.currentThread().getName()+"說:等待線程1交換數據");            // 2.4 將數據交換給線程1,并拿到線程1的數據            try {                String a = exchanger.exchange(girl2);                // 成功獲取線程1的數據后                System.out.println(Thread.currentThread().getName()+"說:我拿到了 " + a);            } catch (InterruptedException e) {                e.printStackTrace();            }        }).start();    }}

運行效果DeS28資訊網——每日最新資訊28at.com

DeS28資訊網——每日最新資訊28at.com

注意:如果兩個線程有一個沒有執行exchange()方法,則會一直等待,如果擔心有特殊情況發生,避免一直等待,可以使用exchange(V x,longtimeout,TimeUnit unit)設置最大等待時長。DeS28資訊網——每日最新資訊28at.com

21、總結

整篇文章內容較多,這里做個總結。1~7小節講了并發編程的核心基礎概念;在對并發有了一定的基礎了解后,8~9小節講了JVM對并發問題的設計——JMM;從10小節開始詳細介紹了JDK 并發包里面的常用工具。這里只是一個入門級別的了解,但是萬丈高樓平地起,這些基礎是后面提升必不可缺的知識,希望大家可以掌握它。DeS28資訊網——每日最新資訊28at.com

作者介紹

蔡柱梁,51CTO社區編輯,從事Java后端開發8年,做過傳統項目廣電BOSS系統,后投身互聯網電商,負責過訂單,TMS,中間件等。DeS28資訊網——每日最新資訊28at.com

本文鏈接:http://www.www897cc.com/showinfo-26-51249-0.html看完后,你再也不用怕面試問并發編程啦

聲明:本網頁內容旨在傳播知識,若有侵權等問題請及時與本網聯系,我們將在第一時間刪除處理。郵件:2376512515@qq.com

上一篇: useEffect 實踐案例:自定義 Hook

下一篇: JS問題:項目中如何區分使用防抖或節流?

標簽:
  • 熱門焦點
  • 小米官宣:2023年上半年出貨量中國第一!

    今日早間,小米電視官方微博帶來消息,稱2023年小米電視上半年出貨量達到了中國第一,同時還表示小米電視的巨屏風暴即將開始。“公布一個好消息2023年#小米電視上半年出貨量中國
  • Redmi Pad評測:紅米充滿野心的一次嘗試

    從Note系列到K系列,從藍牙耳機到筆記本電腦,紅米不知不覺之間也已經形成了自己頗有競爭力的產品體系,在中端和次旗艦市場上甚至要比小米新機的表現來得更好,正所謂“大丈夫生居
  • 5月iOS設備好評榜:iPhone 14僅排第43?

    來到新的一月,安兔兔的各個榜單又重新匯總了數據,像安卓陣營的榜單都有著比較大的變動,不過iOS由于設備的更新換代并沒有那么快,所以相對來說變化并不大,特別是iOS好評榜,老款設
  • K6:面向開發人員的現代負載測試工具

    K6 是一個開源負載測試工具,可以輕松編寫、運行和分析性能測試。它建立在 Go 和 JavaScript 之上,它被設計為功能強大、可擴展且易于使用。k6 可用于測試各種應用程序,包括 Web
  • 虛擬鍵盤 API 的妙用

    你是否在遇到過這樣的問題:移動設備上有一個固定元素,當激活虛擬鍵盤時,該元素被隱藏在了鍵盤下方?多年來,這一直是 Web 上的默認行為,在本文中,我們將探討這個問題、為什么會發生
  • 小紅書1周漲粉49W+,我總結了小白可以用的N條漲粉筆記

    作者:黃河懂運營一條性教育視頻,被54萬人&ldquo;珍藏&rdquo;是什么體驗?最近,情感博主@公主是用鮮花做的,火了!僅僅憑借一條視頻,光小紅書就有超過128萬人,為她瘋狂點贊!更瘋狂的是,這
  • 造車兩年股價跌六成,小米的估值邏輯變了嗎?

    如果從小米官宣造車后的首個交易日起持有小米集團的股票,那么截至2023年上半年最后一個交易日,投資者將浮虧59.16%,同區間的恒生科技指數跌幅為52.78%
  • AI芯片初創公司Tenstorrent獲三星和現代1億美元投資

    Tenstorrent是一家由芯片行業資深人士Jim Keller領導的加拿大初創公司,專注于開發人工智能芯片,該公司周三表示,已經從現代汽車集團和三星投資基金等
  • OPPO K11樣張首曝:千元機影像“卷”得真不錯!

    一直以來,OPPO K系列機型都保持著較為均衡的產品體驗,歷來都是2K價位的明星機型,去年推出的OPPO K10和OPPO K10 Pro兩款機型憑借各自的出色配置,堪稱有
Top 主站蜘蛛池模板: 江口县| 汨罗市| 前郭尔| 大田县| 莱芜市| 德钦县| 梅河口市| 康乐县| 德格县| 道孚县| 民乐县| 腾冲县| 杭锦后旗| 怀远县| 徐闻县| 阳泉市| 梅州市| 红原县| 元阳县| 清水县| 陆良县| 左权县| 五寨县| 聊城市| 虞城县| 五大连池市| 朝阳区| 华阴市| 庆云县| 明溪县| 东安县| 蕲春县| 南宁市| 南昌县| 莆田市| 镇平县| 滨州市| 渝中区| 且末县| 南平市| 武安市|