多執行緒、併發(很亂)
先複習下
我們先了解一下Thread的幾個重要方法。
a、start()方法,呼叫該方法開始執行該執行緒;
b、stop()方法,呼叫該方法強制結束該執行緒執行;
c、join方法,呼叫該方法等待該執行緒結束,用於主執行緒等待子執行緒執行結束。
d、sleep()方法,呼叫該方法該執行緒進入等待。
e、run()方法,呼叫該方法直接執行執行緒的run()方法,但是執行緒呼叫start()方法時也會執行run()方法,區別就是一個是由執行緒排程執行run()方法,一個是直接呼叫了執行緒中的run()方法!!
自己的理解:
首先object提供了wait和notify,網上很多都講得非常奇怪
這兩個方法是必須要跟synchronize一起用的
- wait方法:被鎖的物件.wait()表示,正在使用該鎖的執行緒,不再擁有該鎖,所以執行緒也就停止,進入了等待notify喚醒的block區。
- notify:被鎖的物件.notify()表示,在block區中找一個優先順序高的執行緒喚醒,進入runnable,等待獲得鎖
所以,它和sleep是沒有可比性的,sleep是操控執行緒,而它是操控物件
****鎖型別
可重入鎖:在執行物件中所有同步方法不用再次獲得鎖
可中斷鎖:在等待獲取鎖過程中可中斷
公平鎖: 按等待獲取鎖的執行緒的等待時間進行獲取,等待時間長的具有優先獲取鎖權利
讀寫鎖:對資源讀取和寫入的時候拆分為2部分處理,讀的時候可以多執行緒一起讀,寫的時候必須同步地寫
那麼lock和synchronized的區別對比如下:
1)synchronized 在成功完成功能或者丟擲異常時,虛擬機器會自動釋放執行緒佔有的鎖;而Lock物件在發生異常時,如果沒有主動呼叫unLock()方法去釋放鎖,則鎖物件會一直持有,因此使用Lock時需要在finally塊中釋放鎖;
2)lock介面鎖可以通過多種方法來嘗試獲取鎖包括立即返回是否成功的tryLock(),以及一直嘗試獲取的lock()方法和嘗試等待指定時間長度獲取的方法,相對靈活了許多比synchronized;
3) 通過在讀多,寫少的高併發情況下,我們用ReentrantReadWriteLock分別獲取讀鎖和寫鎖來提高系統的效能,因為讀鎖是共享鎖,即可以同時有多個執行緒讀取共享資源,而寫鎖則保證了對共享資源的修改只能是單執行緒的。
synchronized與Lock的區別
1、我把兩者的區別分類到了一個表中,方便大家對比:
類別 | synchronized | Lock |
---|---|---|
存在層次 | Java的關鍵字,在jvm層面上 | 是一個類 |
鎖的釋放 | 1、以獲取鎖的執行緒執行完同步程式碼,釋放鎖 2、執行緒執行發生異常,jvm會讓執行緒釋放鎖 | 在finally中必須釋放鎖,不然容易造成執行緒死鎖 |
鎖的獲取 | 假設A執行緒獲得鎖,B執行緒等待。如果A執行緒阻塞,B執行緒會一直等待 | 可以嘗試獲得鎖,執行緒可以不用一直等待 |
鎖狀態 | 無法判斷 | 可以判斷 |
鎖型別 | 可重入 不可中斷 非公平 | 可重入 、可判斷 、可公平(兩者皆可)、 可中斷 |
效能 | 差不多 | 差不多 |
多執行緒中斷機制
在 java中啟動執行緒非常容易,大多數情況下是讓一個執行緒執行完自己的任務然後自己停掉。
一個執行緒在未正常結束之前, 被強制終止是很危險的事情. 因為它可能帶來完全預料不到的嚴重後果,比如會帶著自己所持有的鎖而永遠的休眠,遲遲不歸還鎖等。
但是呼叫 interrupt() 方法會設定執行緒的中斷標記,此時呼叫 interrupted() 方法會返回 true。因此可以在迴圈體中使用 interrupted() 方法來判斷執行緒是否處於中斷狀態,從而提前結束執行緒
如果一個執行緒的 run() 方法執行一個無限迴圈,並且沒有執行 sleep() 等會丟擲 InterruptedException 的操作(阻塞、限期等待或者無限期等待狀態),那麼呼叫執行緒的 interrupt() 方法就無法使執行緒提前結束(如IO阻塞、sync操作)。
執行緒中斷是一種協作機制,呼叫執行緒物件的interrupt方法並不一定就中斷了正在執行的執行緒,它只是要求執行緒自己在合適的時間中斷自己。
await() signal() signalAll()
java.util.concurrent 類庫中提供了 Condition 類來實現執行緒之間的協調,可以在 Condition 上呼叫 await() 方法使執行緒等待,其它執行緒呼叫 signal() 或 signalAll() 方法喚醒等待的執行緒。相比於 wait() 這種等待方式,await() 可以指定等待的條件,因此更加靈活。
使用 Lock 來獲取一個 Condition 物件。
public class AwaitSignalExample { private Lock lock = new ReentrantLock(); private Condition condition = lock.newCondition(); public void before() { lock.lock(); try { System.out.println("before"); condition.signalAll(); } finally { lock.unlock(); } } public void after() { lock.lock(); try { condition.await(); System.out.println("after"); } catch (InterruptedException e) { e.printStackTrace(); } finally { lock.unlock(); } } }
public static void main(String[] args) { ExecutorService executorService = Executors.newCachedThreadPool(); AwaitSignalExample example = new AwaitSignalExample(); executorService.execute(() -> example.after()); executorService.execute(() -> example.before()); }
before after很明顯,相對於synchronized,Lock這種可以控制多個condition的方式,更加方便。
Java記憶體模型
Java 記憶體模型試圖遮蔽各種硬體和作業系統的記憶體訪問差異,以實現讓 Java 程式在各種平臺下都能達到一致的記憶體訪問效果。
主記憶體與工作記憶體
處理器上的暫存器的讀寫的速度比記憶體快幾個數量級,為了解決這種速度矛盾,在它們之間加入了快取記憶體。
加入快取記憶體帶來了一個新的問題:快取一致性。如果多個快取共享同一塊主記憶體區域,那麼多個快取的資料可能會不一致,需要一些協議來解決這個問題。
所有的變數都儲存在主記憶體中,每個執行緒還有自己的工作記憶體,工作記憶體儲存在快取記憶體或者暫存器中,儲存了該執行緒使用的變數的主記憶體副本拷貝。
執行緒只能直接操作工作記憶體中的變數,不同執行緒之間的變數值傳遞需要通過主記憶體來完成。
記憶體間互動操作
Java 記憶體模型定義了 8 個操作來完成主記憶體和工作記憶體的互動操作。
- read:把一個變數的值從主記憶體傳輸到工作記憶體中
- load:在 read 之後執行,把 read 得到的值放入工作記憶體的變數副本中
- use:把工作記憶體中一個變數的值傳遞給執行引擎
- assign:把一個從執行引擎接收到的值賦給工作記憶體的變數
- store:把工作記憶體的一個變數的值傳送到主記憶體中
- write:在 store 之後執行,把 store 得到的值放入主記憶體的變數中
- lock:作用於主記憶體的變數
- unlock
記憶體模型三大特性
1.原子性
Java 記憶體模型保證了 read、load、use、assign、store、write、lock 和 unlock 操作具有原子性,例如對一個 int 型別的變數執行 assign 賦值操作,這個操作就是原子性的。但是 Java 記憶體模型允許虛擬機器將沒有被 volatile 修飾的 64 位資料(long,double)的讀寫操作劃分為兩次 32 位的操作來進行,即 load、store、read 和 write 操作可以不具備原子性
有一個錯誤認識就是,int 等原子性的變數在多執行緒環境中不會出現執行緒安全問題。前面的執行緒不安全示例程式碼中,cnt 變數屬於 int 型別變數,1000 個執行緒對它進行自增操作之後,得到的值為 997 而不是 1000。
2. 可見性
可見性指當一個執行緒修改了共享變數的值,其它執行緒能夠立即得知這個修改。Java 記憶體模型是通過在變數修改後將新值同步回主記憶體,在變數讀取前從主記憶體重新整理變數值來實現可見性的。
volatile 可保證可見性。synchronized 也能夠保證可見性,對一個變數執行 unlock 操作之前,必須把變數值同步回主記憶體。final 關鍵字也能保證可見性:被 final 關鍵字修飾的欄位在構造器中一旦初始化完成,並且沒有發生 this 逃逸(其它執行緒可以通過 this 引用訪問到初始化了一半的物件),那麼其它執行緒就能看見 final 欄位的值。
對前面的執行緒不安全示例中的 cnt 變數用 volatile 修飾,不能解決執行緒不安全問題,因為 volatile 並不能保證操作的原子性。
3. 有序性
有序性是指:在本執行緒內觀察,所有操作都是有序的。在一個執行緒觀察另一個執行緒,所有操作都是無序的,無序是因為發生了指令重排序。
在 Java 記憶體模型中,允許編譯器和處理器對指令進行重排序,重排序過程不會影響到單執行緒程式的執行,卻會影響到多執行緒併發執行的正確性。
volatile 關鍵字通過新增記憶體屏障的方式來禁止指令重排,即重排序時不能把後面的指令放到記憶體屏障之前。
也可以通過 synchronized 來保證有序性,它保證每個時刻只有一個執行緒執行同步程式碼,相當於是讓執行緒順序執行同步程式碼。
J.U.C - AQS
java.util.concurrent(J.U.C)大大提高了併發效能,AQS 被認為是 J.U.C 的核心。
CountdownLatch
用來控制一個執行緒等待多個執行緒。
維護了一個計數器 cnt,每次呼叫 countDown() 方法會讓計數器的值減 1,減到 0 的時候,那些因為呼叫 await() 方法而在等待的執行緒就會被喚醒。
public class CountdownLatchExample { public static void main(String[] args) throws InterruptedException { final int totalThread = 10; CountDownLatch countDownLatch = new CountDownLatch(totalThread); ExecutorService executorService = Executors.newCachedThreadPool(); for (int i = 0; i < totalThread; i++) { executorService.execute(() -> { System.out.print("run.."); countDownLatch.countDown(); }); } countDownLatch.await(); System.out.println("end"); executorService.shutdown(); } }
run..run..run..run..run..run..run..run..run..run..end
volatile關鍵字
1.volatile保證可見性
一旦一個共享變數(類的成員變數、類的靜態成員變數)被volatile修飾之後,那麼就具備了兩層語義:
保證了不同執行緒對這個變數進行操作時的可見性,即一個執行緒修改了某個變數的值,這新值對其他執行緒來說是立即可見的。
- 第一:使用volatile關鍵字會強制將修改的值立即寫入主存;
- 第二:使用volatile關鍵字的話,當執行緒2進行修改時,會導致執行緒1的工作記憶體中快取變數stop的快取行無效(反映到硬體層的話,就是CPU的L1或者L2快取中對應的快取行無效);
- 第三:由於執行緒1的工作記憶體中快取變數stop的快取行無效,所以執行緒1再次讀取變數stop的值時會去主存讀取。
2.volatile不能確保原子性
3.volatile保證有序性
volatile關鍵字能禁止指令重排序,所以volatile能在一定程度上保證有序性。
- 當程式執行到volatile變數的讀操作或者寫操作時,在其前面的操作的更改肯定全部已經進行,且結果已經對後面的操作可見;在其後面的操作肯定還沒有進行;
- 在進行指令優化時,不能將在對volatile變數的讀操作或者寫操作的語句放在其後面執行,也不能把volatile變數後面的語句放到其前面執行。
123456789 | //執行緒1: context = loadContext(); //語句1 inited = true ; //語句2 //執行緒2: while (!inited ){ sleep() } doSomethingwithconfig(context); |
有可能語句2會在語句1之前執行,那麼久可能導致context還沒被初始化,而執行緒2中就使用未初始化的context去進行操作,導致程式出錯。
這裡如果用volatile關鍵字對inited變數進行修飾,就不會出現這種問題了,因為當執行到語句2時,必定能保證context已經初始化完畢。
volatile的應用場景
synchronized關鍵字是防止多個執行緒同時執行一段程式碼,那麼就會很影響程式執行效率,而volatile關鍵字在某些情況下效能要優於synchronized,但是要注意volatile關鍵字是無法替代synchronized關鍵字的,因為volatile關鍵字無法保證操作的原子性。通常來說,使用volatile必須具備以下2個條件:
1)對變數的寫操作不依賴於當前值
2)該變數沒有包含在具有其他變數的不變式中
下面列舉幾個Java中使用volatile的幾個場景。
①.狀態標記量
123456789 | volatile boolean flag = false ; //執行緒1 while (!flag){ doSomething(); } //執行緒2 public void setFlag() { 相關推薦多執行緒、併發(很亂)先複習下我們先了解一下Thread的幾個重要方法。a、start()方法,呼叫該方法開始執行該執行緒;b、stop()方法,呼叫該方法強制結束該執行緒執行;c、join方法,呼叫該方法等待該執行緒結束,用於主執行緒等待子執行緒執行結束。d、sleep()方法,呼叫該方法該執行 iOS 多執行緒使用總結(很實用)每次準備開始新的航行,總是要複習一遍演算法啊,多執行緒啊,記憶體管理啊之類的理論和應用知識,這次把他們整理成文件,方便以後的學習和不斷的積累進步。 多執行緒給我留下的是痛苦的記憶,當時在上家創業公司的最後階段,就是被Feature Phone上面的多執行緒方案導致bug叢生,搞的焦頭爛額。 Java基礎學習總結(104)——多執行緒、併發、工具類相關的面試題執行緒的概念 執行緒是程式執行的最小單位,也是作業系統排程和分派CPU的最小單元,是程序中的一個實體,是程序中的實際運作單位。可以在一個程序中啟動多個執行緒來完成不同的任務,這些執行緒共享該程序擁有的資源。 執行緒程序區別 程序是程式的實體,也是執行緒的容器,一個程序可以包含多個執行緒,程序是資源分配的基本單 Java多執行緒與併發(三)Condition等待和喚醒 在我們的並行程式中,避免不了某些寫成要預先規定好的順序執行,例如:先新增後修改,先買後賣,先進後出,對於這些場景,使用JUC的Conditon物件再合適不過了。 JUC中提供了Condition物件,用於讓指定執行緒等待與喚 Java多執行緒與併發(二)Synchronized執行緒同步機制 很多執行緒同時對同一個資料或者檔案進行訪問的時候,對於這個檔案如果進行併發讀寫可能會產生問題。 多執行緒機制來保證同一個時間,只有一個執行緒對這個資源進行讀寫,來保證多執行緒環境下是健壯的。 程式碼案例: Java多執行緒與併發(一)多執行緒與併發的基礎問題 併發就是指程式同時處理多個任務的能力(一個程式被多個使用者訪問都能看到自己預期的結果) 併發的根源在於對多工情況下訪問資源的有效控制! 併發背後的問題 public class DownloadSimple { 【Java併發程式設計】之六:Runnable和Thread實現多執行緒的區別(含程式碼)Java中實現多執行緒有兩種方法:繼承Thread類、實現Runnable介面,在程式開發中只要是多執行緒,肯定永遠以實現Runnable介面為主,因為實現Runnable介面相比繼承Th 綜合應用題:多執行緒複製檔案(知識點:多執行緒、隨機讀寫流)要求:使用多執行緒複製一個檔案(使用多執行緒複製一個檔案可以加快檔案的複製速度) 程式碼: package 多執行緒複製檔案; import java.io.File; import java.io.FileNotFoundException; impor 多執行緒和併發(三)使用join方法讓執行緒按順序執行一.執行緒的join方法作用 join方法把指定的執行緒新增到當前執行緒中,可以不給引數直接thread.join(),也可以給一個時間引數,單位為毫秒thread.join(500)。事實上join方法是通過wait方法來實現的。比如執行緒A中加入了執行緒B.join方法 Java 多執行緒與併發(六):AQS我們前面幾張提到過,JUC 這個包裡面的工具類的底層就是使用 CAS 和 volatile 來保證執行緒安全的,整個 JUC 包裡面的類都是基於它們構建的。今天我們介紹一個非常重要的同步器,這個類是 JDK 在 CAS 和 volatile 的基礎上為我們提供的一個同步工具類。 背景 AbstractQueu 面試必備——Java多執行緒與併發(一)1.程序和執行緒的 (1)由來 1)序列 最初的計算機只能接受一些特定的指令,使用者輸入一個指令,計算機就做出一個操作。當用戶在思考或者輸入時,計算機就在等待。顯然這樣效率低下,在很多時候,計算機都處在等待狀態。 2)批處理 提高計算機的效率,不用等待使用者的輸入,把一系列需要操作的指令寫下來,形成一個清單 python爬蟲——多執行緒+協程(threading+gevent)以下摘自這篇文章:https://blog.csdn.net/qq_23926575/article/details/76375337 在爬蟲中廣泛運用的多執行緒+協程的解決方案,親測可提高效率至少十倍以上。 本文既然提到了執行緒和協程,我覺得有必要在此對程序、執行緒、協程做一個簡單的對 java多執行緒快速入門(十一)在方法上面加synchonizd用的是this鎖 package com.cppdy; class MyThread7 implements Runnable { private Integer ticketCount = 100; public boolean falg = tr java多執行緒快速入門(十二)在靜態方法上面加synchonizd用的是位元組碼檔案鎖 package com.cppdy; class MyThread8 implements Runnable { private static Integer ticketCount = 100; public boolea java多執行緒快速入門(十六)ThreadLocal關鍵字實現每個執行緒有自己的變數 package com.cppdy; class Number { private int num; public static ThreadLocal<Integer> threadLocal = new Th java多執行緒快速入門(十八)Lock鎖是JDK1.5之後推出的併發包裡面的關鍵字(注意捕獲異常,釋放鎖) Lock與synchronized的區別 Lock鎖可以人為的釋放鎖(相當於汽車中的手動擋) synchronized當執行緒執行完畢或者丟擲異常的話,鎖自動釋放(相當於汽車中的自動擋) Condition用法 Java定時任務Timer排程器【二】 多執行緒原始碼分析(圖文版)上一節通過一個小例子分析了Timer執行過程,牽涉的執行執行緒雖然只有兩個,但實際場景會比上面複雜一些。 首先通過一張簡單類圖(只列出簡單的依賴關係)看一下Timer暴露的介面。 為了演示Timer所暴露的介面,下面舉一個極端的例子(每一個介面方法面 CVI多執行緒資料保護(安全佇列)一個執行緒產生資料,加入安全佇列;一個執行緒顯示資料,讀取安全佇列。 #include <windows.h> #include <stdio.h> #include <utility.h> int CVICALLBACK Thre Java 多執行緒學習筆記(十一) 單例設計模式(延遲載入/懶漢模式)DCL解決執行緒安全問題package extthread; import test.MyObject; public class MyThread extends Thread { @Override publi 多執行緒優化思路(轉載的)樣例程式 程式功能:求從1一直到 APPLE_MAX_VALUE (100000000) 相加累計的和,並賦值給 apple 的a 和b ;求 orange 資料結構中的 a[i]+b[i ] 的和,迴圈 ORANGE_MAX_VALUE(1000000) 次。 |