- Java多線程(一)
- 一、線程的定義
- 二、Synchronize線程同步
- 三、偏向鎖、自旋鎖、重量級鎖
- 四、volatile關鍵字
- 4.1.普通變量運算的物理意義
- 4.2.有無解決的方案
- 4.3.volatile的幾個特性(參考https://www.cnblogs.com/kubidemanong/p/9505944.html)
- 五、Compare And Swap無鎖自旋優化技術和ABA版本號機制
- 5.1.CAS操作原理
- 5.2.atomic包
- 5.3.關于大并發下AtomicInteger和LongAdder和Sync的效率(百萬并發以上)
- 六、樂觀鎖和悲觀鎖(參考https://blog.csdn.net/qq_34337272/article/details/81072874)
- 七、公平鎖、非公平鎖
- 7.1.概念
- 7.3.重入鎖
?
一、線程的定義
一個程序中不同的分支進行執行
二、Synchronize線程同步
概念:線程同步指的是,即當有一個線程在對內存進行操作時,其他線程都不可以對這個內存地址進行操作,直到該線程完成操作, 其他線程才能對該內存地址進行操作。
多線程訪問同一個資源會產生線程安全問題,所以需要進行加鎖。
package com.littlepage.test; class MyThread implements Runnable{ //票數是多個線程的共享資源 private int ticket = 10; @Override public void run() { while(ticket > 0) { try { Thread.sleep(200); } catch (InterruptedException e) { e.printStackTrace(); } System.out.println(Thread.currentThread().getName()+"還剩下"+ticket--+"張票"); } } } public class Test { public static void main(String[] args) { MyThread thread1 = new MyThread(); new Thread(thread1,"黃牛A").start(); new Thread(thread1,"黃牛B").start(); new Thread(thread1,"黃牛C").start(); } }
多運行幾次,發現出現了負數
這就是線程安全問題,為了解決這個問題,我們采用加鎖進行控制
@Override public synchronized void run() { while(ticket > 0) { try { Thread.sleep(200); } catch (InterruptedException e) { e.printStackTrace(); } System.out.println(Thread.currentThread().getName()+"還剩下"+ticket--+"張票"); } }
增加了一個鎖之后,打印結果為
我們在方法上加synchronize,實際上等同下面這個寫法
class MyThread implements Runnable{ //票數是多個線程的共享資源 private int ticket = 10; @Override public void run() { synchronized (MyThread.class) { while(ticket > 0) { try { Thread.sleep(200); } catch (InterruptedException e) { e.printStackTrace(); } System.out.println(Thread.currentThread().getName()+"還剩下"+ticket--+"張票"); } } } }
這個MyThread.class是一個互斥鎖(mutex),C程序中,我們mutex的實現是,指定一個信號量,對其進行pv操作實現鎖機制,Java中的對象鎖,深糾底層,原理是鎖是存在對象頭里面的,什么是Java對象頭?Java對象頭是一個與對象本身無關的一個額外區域,我們在使用鎖的時候,實際上對Java對象頭內部指針進行了操作。
三、偏向鎖、自旋鎖、重量級鎖
- Java在使用synchronize關鍵字,首先在Java對象頭進行markword,記錄這個線程id,只有一個線程對這個對象加鎖,這個鎖叫做偏向鎖。
- 偏向鎖如果有線程爭用,那么升級為自旋鎖?一個鎖如果被使用,那么另外一個線程打算爭搶這把鎖,則會陷入while循環,形成自旋。這叫做自旋鎖。
- 旋10次之后,升級為重量級鎖。重量級鎖以OS為主體。那么重量級鎖不會占用CPU。
- 注:鎖不能降級
四、volatile關鍵字
4.1.普通變量運算的物理意義
一般程序來說,我們定義一個a ,使用a++進行操作,在物理上,實際是將a放入高速緩存(cache),經過運算后,再放回內存,這樣就出現線程安全問題,幾個線程同時進行a++操作,可能沒有達到最終加值效果。在比如是類似銀行匯款的業務中,那么問題就出來了。
4.2.有無解決的方案
volatile關鍵字,在Java語言和C程序中都有,這個關鍵字可以硬性規定,先將變量加鎖,使變量在主存中進行計算,之后再釋放鎖,所以,變量不會出現運算失誤狀態。
4.3.volatile的幾個特性(參考https://www.cnblogs.com/kubidemanong/p/9505944.html)
- 可見性
在多線程環境下,如果一個線程修改了,其他線程能立即讀到。這是因為他們讀取的時候不會先把變量讀進自己的緩存,直接在內存讀取
- 緩存一致性協議
線程中處理器一直在總線上進行查看進行探測情況,一旦發現其他處理器要修改這個內存地址的值,那么,就讓自己的高速緩存區變為無效狀態,從內存進行讀取
- 有序型
int a=1; int b=2;
我們寫完這個話后,因為無論執行哪句話都沒影響,所以,虛擬機會對這兩句指令進行重排序、
所以,我們可以使用volatile保證有序
重排序會遇到線程安全問題
五、Compare And Swap無鎖自旋優化技術和ABA版本號機制
5.1.CAS操作原理
CAS操作:CAS存在3個參數,變量V,舊值A,新值B,內存值與B相等的時候,才會去修改B,否則直接返回。
CAS是一種CPU原語。
cas(V,Expected,NewValue) if V==E E=New otherwise try again or fail -CPU對原語支持
java.util.concurrent.atomic下有很多CAS操作的類
5.2.atomic包
例如AtomicInteger,這個對象調用incrementAndGet方法,和int a=0;a++;不一樣,這個是線程安全的,無論起多少個線程,都能在無鎖狀態進行線程安全操作。在舊版本Java中,沒有偏向,自旋,重量級升級說吧的概念,所以AtomicInteger速度會比synchronize快很多,因為是無鎖操作。現在來講,速度應該差不多。
public static void main(String[] args){ AtomicInteger count = new AtomicInteger(0); for(int i=0;i<1000;i++) { new Thread(new Runnable() { @Override public void run() { count.incrementAndGet(); } }); } }
包內其他對象
5.3.關于大并發下AtomicInteger和LongAdder和Sync的效率(百萬并發以上)
5.4.版本號機制(參考https://blog.csdn.net/qq_34337272/article/details/81072874)
一般是在數據表中加上一個數據版本號version字段,表示數據被修改的次數,當數據被修改時,version值會加一。當線程A要更新數據值時,在讀取數據的同時也會讀取version值,在提交更新時,若剛才讀取到的version值為當前數據庫中的version值相等時才更新,否則重試更新操作,直到更新成功。
5.5.ABA問題(參考https://blog.csdn.net/qq_34337272/article/details/81072874)
如果一個變量V初次讀取的時候是A值,并且在準備賦值的時候檢查到它仍然是A值,那我們就能說明它的值沒有被其他線程修改過了嗎?很明顯是不能的,因為在這段時間它的值可能被改為其他值,然后又改回A,那CAS操作就會誤認為它從來沒有被修改過。這個問題被稱為CAS操作的?"ABA"問題。
JDK 1.5 以后的?AtomicStampedReference 類
就提供了此種能力,其中的?compareAndSet 方法
就是首先檢查當前引用是否等于預期引用,并且當前標志是否等于預期標志,如果全部相等,則以原子方式將該引用和該標志的值設置為給定的更新值。
六、樂觀鎖和悲觀鎖(參考https://blog.csdn.net/qq_34337272/article/details/81072874)
- 樂觀鎖
總是假設最好的情況,每次去拿數據的時候都認為別人不會修改,所以不會上鎖,但是在更新的時候會判斷一下在此期間別人有沒有去更新這個數據,可以使用版本號機制和CAS算法實現。樂觀鎖適用于多讀的應用類型,這樣可以提高吞吐量,像數據庫提供的類似于write_condition機制,其實都是提供的樂觀鎖。在Java中java.util.concurrent.atomic包下面的原子變量類就是使用了樂觀鎖的一種實現方式CAS實現的。
- 悲觀鎖
總是假設最壞的情況,每次去拿數據的時候都認為別人會修改,所以每次在拿數據的時候都會上鎖,這樣別人想拿這個數據就會阻塞直到它拿到鎖(共享資源每次只給一個線程使用,其它線程阻塞,用完后再把資源轉讓給其它線程)。傳統的關系型數據庫里邊就用到了很多這種鎖機制,比如行鎖,表鎖等,讀鎖,寫鎖等,都是在做操作之前先上鎖。Java中synchronized和ReentrantLock等獨占鎖就是悲觀鎖思想的實現。
七、公平鎖、非公平鎖
7.1.概念
公平鎖:加鎖之前先檢查是否有排隊等待的線程,有的話優先處理排前面的線程,先來先得。
非公平鎖:線程加鎖時直接嘗試獲取,獲取不到就到隊尾等待
7.2.Java中的公平鎖和非公平鎖
//非公平鎖 Lock nonFairLock=new ReentrantLock(true);//默認空參是true //公平鎖 Lock fairLock=new ReentrantLock(false);
注:非公平鎖比公平鎖快很多
7.3.重入鎖
ReetranLock含義是重入鎖,什么意思呢,可以進行加鎖多次,和解鎖多次,比如
public class NoVisibility{ public static void main(String[] args){ Lock lock=new ReentrantLock(); lock.lock(); lock.lock(); lock.unlock(); lock.unlock(); } }
?