愛了,這篇Java并發(fā)編程技術(shù)點總結(jié)的太詳細(xì)了猬仁,建議收藏再看

前言

并發(fā)編程技術(shù)在Java中屬于重要知識點帝璧,對于以下內(nèi)容你有了解多少?

進程湿刽、線程的烁、協(xié)程關(guān)系概述

進程:本質(zhì)上是一個獨立執(zhí)行的程序,進程是操作系統(tǒng)進行資源分配和調(diào)度的基本概念诈闺,操作系統(tǒng)進行資源分配和調(diào)度的一個獨立單位渴庆。

?

線程:操作系統(tǒng)能夠進行運算調(diào)度的最小單位。它被包含在進程之中,是進程中的實際運作單位把曼。一個進程中可以并發(fā)多個線程杨帽,每條線程執(zhí)行不同的任務(wù),切換受系統(tǒng)控制嗤军。

?

協(xié)程:又稱為微線程注盈,是一種用戶態(tài)的輕量級線程,協(xié)程不像線程和進程需要進行系統(tǒng)內(nèi)核上的上下文切換叙赚,協(xié)程的上下文切換是由用戶自己決定的老客,有自己的上下文,所以說是輕量級的線程震叮,也稱之為用戶級別的線程胧砰,一個線程可以有多個協(xié)程,線程與進程都是同步機制苇瓣,而協(xié)程則是異步尉间。Java的原生語法中并沒有實現(xiàn)協(xié)程,目前python击罪、Lua和GO等語言支持哲嘲。

?

關(guān)系:一個進程可以有多個線程,它允許計算機同時運行兩個或多個程序媳禁。線程是進程的最小執(zhí)行單位眠副,CPU的調(diào)度切換的是進程和線程,進程和線程多了之后調(diào)度會消耗大量的CPU,CPU上真正運行的是線程,線程可以對應(yīng)多個協(xié)程敷矫。

協(xié)程對于多線程的優(yōu)缺點

優(yōu)點:

非秤褡椋快速的上下文切換,不用系統(tǒng)內(nèi)核的上下文切換,減小開銷

單線程即可實現(xiàn)高并發(fā),單核CPU可以支持上萬的協(xié)程

由于只有一個線程,也不存在同時寫變量的沖突忘闻,在協(xié)程中控制共享資源不需要加鎖

缺點:

協(xié)程無法利用多核資源,本質(zhì)也是個單線程

協(xié)程需要和進程配合才能運行在多CPU上

目前Java沒成熟的第三方庫恋博,存在風(fēng)險

調(diào)試debug存在難度齐佳,不利于發(fā)現(xiàn)問題

并發(fā)和并行的區(qū)別

并發(fā) (concurrency):一臺處理器上同時處理多個任務(wù),這個同時實際上是交替處理多個任務(wù)债沮,程序中可以同時擁有兩個或者多個線程炼吴,當(dāng)有多個線程在操作時,如果系統(tǒng)只有一個CPU疫衩,則它根本不可能真正同時進行一個以上的線程硅蹦,它只能把CPU運行時間劃分成若干個時間段,再將時間段分配給各個線程執(zhí)行。

?

并行(parallellism) :多個CPU上同時處理多個任務(wù)童芹,一個CPU執(zhí)行一個進程時涮瞻,另一個CPU可以執(zhí)行另一個進程,兩個進程互不搶占CPU資源假褪,可以同時進行署咽。

并發(fā)指在一段時間內(nèi)宏觀上去處理多個任務(wù)。 并行指同一個時刻生音,多個任務(wù)確實真的同時運行宁否。

Java里實現(xiàn)多線程的幾種方式

1.繼承Thread類

繼承Thread類,重寫里面run方法缀遍,創(chuàng)建實例慕匠,執(zhí)行start方法。

優(yōu)點:代碼編寫最簡單直接操作

缺點:無返回值域醇,繼承一個類后台谊,沒法繼承其他的類,拓展性差

publicclassThreadDemo1extendsThread{

@Override

publicvoidrun(){

System.out.println("繼承Thread實現(xiàn)多線程歹苦、名稱:"+Thread.currentThread().getName());

}

}

publicstaticvoidmain(String[] args){

?

ThreadDemo1 threadDemo1 =newThreadDemo1();

threadDemo1.setName("demo1");

threadDemo1.start();

System.out.println("主線程名稱:"+Thread.currentThread().getName());

?

}

2.實現(xiàn)Runnable接口

自定義類實現(xiàn)Runnable接口青伤,實現(xiàn)里面run方法督怜,創(chuàng)建Thread類殴瘦,使用Runnable接口的實現(xiàn)對象作為參數(shù)傳遞給Thread對象,調(diào)用start方法号杠。

?

優(yōu)點:線程類可以實現(xiàn)多個幾接口蚪腋,可以再繼承一個類

缺點:無返回值,不能直接啟動姨蟋,需要通過構(gòu)造一個Thread實例傳遞進去啟動

publicclassThreadDemo2implementsRunnable{

?

@Override

publicvoidrun(){

System.out.println("通過Runnable實現(xiàn)多線程屉凯、名稱:"+Thread.currentThread().getName());

}

}

publicstaticvoidmain(String[] args){

ThreadDemo2 threadDemo2 =newThreadDemo2();

Thread thread =newThread(threadDemo2);

thread.setName("demo2");

thread.start();

System.out.println("主線程名稱:"+Thread.currentThread().getName());

}

JDK8之后采用lambda表達(dá)式

publicstaticvoidmain(String[] args){

Thread thread =newThread(()->{

System.out.println("通過Runnable實現(xiàn)多線程、名稱:"+Thread.currentThread().getName());

});

thread.setName("demo2");

thread.start();

System.out.println("主線程名稱:"+Thread.currentThread().getName());

}

3.通過Callable和FutureTask方式

創(chuàng)建callable接口的實現(xiàn)類眼溶,并實現(xiàn)call方法悠砚,結(jié)合FutureTask類包裝callable對象,實現(xiàn)多線程堂飞。

優(yōu)點:有返回值灌旧,拓展性也高

缺點:jdk5以后才支持,需要重寫call方法绰筛,結(jié)合多個類比如FutureTask和Thread類

publicclassMyTaskimplementsCallable{

@Override

publicObjectcall()throwsException{

?

System.out.println("通過Callable實現(xiàn)多線程枢泰、名稱:"+Thread.currentThread().getName());

?

return"這是返回值";

}

}

public static void main(String[] args) {

?

MyTask myTask =newMyTask();

FutureTask futureTask =newFutureTask<>(myTask);

?

//FutureTask繼承了Runnable,可以放在Thread中啟動執(zhí)行

Thread thread =newThread(futureTask);

thread.setName("demo3");

thread.start();

System.out.println("主線程名稱:"+Thread.currentThread().getName());

?

try{

System.out.println(futureTask.get());

}catch(InterruptedException e) {

//阻塞等待中被中斷則拋出

e.printStackTrace();

}catch(ExecutionException e) {

//執(zhí)行過程發(fā)送異常被拋出

e.printStackTrace();

}

}

采用lambda表達(dá)式

public static void main(String[] args) {

?

FutureTask futureTask =newFutureTask<>(()->{

System.out.println("通過Callable實現(xiàn)多線程铝噩、名稱:"+Thread.currentThread().getName());

return"這是返回值";

});

//FutureTask繼承了Runnable衡蚂,可以放在Thread中啟動執(zhí)行

Thread thread =newThread(futureTask);

thread.setName("demo3");

thread.start();

System.out.println("主線程名稱:"+Thread.currentThread().getName());

?

try{

System.out.println(futureTask.get());

}catch(InterruptedException e) {

//阻塞等待中被中斷則拋出

e.printStackTrace();

}catch(ExecutionException e) {

//執(zhí)行過程發(fā)送異常被拋出

e.printStackTrace();

}

?

?

}

4.通過線程池創(chuàng)建線程

自定義Runnable接口,實現(xiàn)run方法,創(chuàng)建線程池毛甲,調(diào)用執(zhí)行方法并傳入對象年叮。

優(yōu)點:安全高性能,復(fù)用線程

缺點: jdk5后才支持玻募,需要結(jié)合Runnable進行使用

publicclassThreadDemo4implementsRunnable{

?

@Override

publicvoidrun(){

System.out.println("通過線程池+runnable實現(xiàn)多線程谋右,名稱:"+Thread.currentThread().getName());

}

}

publicstaticvoidmain(String[] args){

ExecutorService executorService = Executors.newFixedThreadPool(3);

?

for(inti=0;i<10;i++){

executorService.execute(newThreadDemo4());

}

?

System.out.println("主線程名稱:"+Thread.currentThread().getName());

?

//關(guān)閉線程池

? ? ? ? executorService.shutdown();

}

一般常用的Runnable 和 第四種線程池+Runnable,簡單方便擴展补箍,和高性能 (池化的思想)

Java線程常見的基本狀態(tài)

JDK的線程狀態(tài)分6種改执,JVM里面9種。

常見的5種狀態(tài)

創(chuàng)建(NEW):生成線程對象坑雅,但是并沒有調(diào)用該對象start()辈挂。

?

就緒(Runnable):當(dāng)調(diào)用線程對象的start()方法,線程就進入就緒狀態(tài)裹粤,但是此刻線程調(diào)度還沒把該線程設(shè)置為當(dāng)前線程终蒂,就是沒獲得CPU使用權(quán)。如果線程運行后遥诉,從等待或者睡眠中回來之后拇泣,也會進入就緒狀態(tài)。

運行(Running):程序?qū)⑻幱诰途w狀態(tài)的線程設(shè)置為當(dāng)前線程矮锈,即獲得CPU使用權(quán)霉翔,這個時候線程進入運行狀態(tài),開始運行run里面的邏輯苞笨。

?

阻塞(Blocked)

等待阻塞:進入該狀態(tài)的線程需要等待其他線程作出一定動作(通知或中斷)债朵,這種狀態(tài)的話CPU不會分配過來,他們需要被喚醒瀑凝,可能也會無限等待下去序芦。比如調(diào)用wait(狀態(tài)就會變成WAITING狀態(tài)),也可能通過調(diào)用sleep(狀態(tài)就會變成TIMED_WAITING)粤咪, join或者發(fā)出IO請求谚中,阻塞結(jié)束后線程重新進入就緒狀態(tài)。

同步阻塞:線程在獲取synchronized同步鎖失敗寥枝,即鎖被其他線程占用宪塔,它就會進入同步阻塞狀態(tài)。

備注:相關(guān)資料會用細(xì)分下面的狀態(tài)

等待(WAITING):進入該狀態(tài)的線程需要等待其他線程做出一些特定動作(通知或中斷)脉顿。

超時等待(TIMED_WAITING):該狀態(tài)不同于WAITING蝌麸,它可以在指定的時間后自行返回。

死亡(TERMINATED):一個線程run方法執(zhí)行結(jié)束艾疟,該線程就死亡了来吩,不能進入就緒狀態(tài)敢辩。

多線程開發(fā)常用方法

sleep

屬于線程Thread的方法;

讓線程暫緩執(zhí)行弟疆,等待預(yù)計時間之后再恢復(fù)戚长;

交出CPU使用權(quán),不會釋放鎖怠苔;

進入阻塞狀態(tài)TIME_WAITGING同廉,睡眠結(jié)束變?yōu)榫途wRunnable;

yield

屬于線程Thread的方法柑司;

暫停當(dāng)前線程的對象迫肖,去執(zhí)行其他線程;

交出CPU使用權(quán)攒驰,不會釋放鎖蟆湖,和sleep類似;

作用:讓相同優(yōu)先級的線程輪流執(zhí)行玻粪,但是不保證一定輪流隅津;

注意:不會讓線程進入阻塞狀態(tài),直接變?yōu)榫途wRunnable劲室,只需要重新獲得CPU使用權(quán)伦仍;

join

屬于線程Thread的方法;

在主線程上運行調(diào)用該方法很洋,會讓主線程休眠充蓝,不會釋放已經(jīng)持有的對象鎖;

讓調(diào)用join方法的線程先執(zhí)行完畢蹲缠,再執(zhí)行其他線程棺克;

wait

屬于Object的方法;

當(dāng)前線程調(diào)用對象的wait方法线定,會釋放鎖,進入線程的等待隊列确买;

需要依靠notify或者notifyAll喚醒斤讥,或者wait(timeout)時間自動喚醒;

notify

屬于Object的方法湾趾;

喚醒在對象監(jiān)視器上等待的單個線程芭商,選擇是任意的;

notifyAll

屬于Object的方法搀缠;

喚醒在對象監(jiān)視器上等待的全部線程铛楣;

線程的狀態(tài)轉(zhuǎn)換圖

Java中保證線程安全的方法

加鎖,比如synchronize/ReentrantLock

使用volatile聲明變量,輕量級同步艺普,不能保證原子性

使用線程安全類簸州,原子類AtomicXXX鉴竭,并發(fā)容器,同步CopyOnWriteArrayList/ConcurrentHashMap等

ThreadLocal本地私有變量/信號量Semaphore等

解析volatile關(guān)鍵字

volatile是輕量級的synchronized岸浑,保證了共享變量的可見性搏存,被volatile關(guān)鍵字修飾的變量,如果值發(fā)生了變化矢洲,其他線程立刻可見璧眠,避免出現(xiàn)臟讀現(xiàn)象。為什么會出現(xiàn)臟讀读虏?JAVA內(nèi)存模型簡稱JMM责静,JMM規(guī)定所有的變量存在在主內(nèi)存,每個線程有自己的工作內(nèi)存盖桥,線程對變量的操作都在工作內(nèi)存中進行泰演,不能直接對主內(nèi)存就行操作,使用volatile修飾變量葱轩,每次讀取前必須從主內(nèi)存屬性獲取最新的值睦焕,每次寫入需要立刻寫到主內(nèi)存中。volatile關(guān)鍵字修修飾的變量隨時看到的自己的最新值靴拱,假如線程1對變量v進行修改垃喊,那么線程2是可以馬上看見的。

?

volatile:保證可見性袜炕,但是不能保證原子性

synchronized:保證可見性本谜,也保證原子性

?什么是指令重排

指令重排序分兩類:編譯器重排序和運行時重排序

?

JVM在編譯Java代碼或者CPU執(zhí)行JVM字節(jié)碼時,對現(xiàn)有的指令進行重新排序偎窘,主要目的是優(yōu)化運行效率(不改變程序結(jié)果的前提)

?舉例:

int a = 3 //第一步 1

int b = 4 //第二步 2

int c =5 //第三步 3

int h = abc //第四步 4

?

定義順序 1,2,3,4

計算順序 1,3,2,4 和 2,1,3,4 結(jié)果都是一樣

什么是happens-before以及為什么需要happens-before

happens-before:A happens-before B就是A先行發(fā)生于B(這種說法不是很準(zhǔn)確)乌助,定義為hb(A, B)。在Java內(nèi)存模型中陌知,happens-before的意思是前一個操作的結(jié)果可以被后續(xù)操作獲取他托。JVM會對代碼進行編譯優(yōu)化,會出現(xiàn)指令重排序情況仆葡,為了避免編譯優(yōu)化對并發(fā)編程安全性的影響赏参,需要happens-before規(guī)則定義一些禁止編譯優(yōu)化的場景,保證并發(fā)編程的正確性沿盅。

happens-before八大規(guī)則

1.程序次序規(guī)則:在一個線程內(nèi)一段代碼的執(zhí)行結(jié)果是有序的把篓。就是還會指令重排,但是隨便它怎么排腰涧,結(jié)果是按照我們代碼的順序生成的不會變韧掩。

2.管程鎖定規(guī)則:就是無論是在單線程環(huán)境還是多線程環(huán)境,對于同一個鎖來說窖铡,一個線程對這個鎖解鎖之后疗锐,另一個線程獲取了這個鎖都能看到前一個線程的操作結(jié)果坊谁!(管程是一種通用的同步原語,synchronized就是管程的實現(xiàn))

3.volatile變量規(guī)則:就是如果一個線程先去寫一個volatile變量窒悔,然后一個線程去讀這個變量呜袁,那么這個寫操作的結(jié)果一定對讀的這個線程可見。

4.線程啟動規(guī)則:在主線程A執(zhí)行過程中简珠,啟動子線程B阶界,那么線程A在啟動子線程B之前對共享變量的修改結(jié)果對線程B可見。

5.線程終止規(guī)則:在主線程A執(zhí)行過程中聋庵,子線程B終止膘融,那么線程B在終止之前對共享變量的修改結(jié)果在線程A中可見。也稱線程join()規(guī)則祭玉。

6.線程中斷規(guī)則:對線程interrupt()方法的調(diào)用先行發(fā)生于被中斷線程代碼檢測到中斷事件的發(fā)生氧映,可以通過Thread.interrupted()檢測到是否發(fā)生中斷。

7.傳遞性規(guī)則:這個簡單的脱货,就是happens-before原則具有傳遞性岛都,即hb(A, B) , hb(B, C)振峻,那么hb(A, C)臼疫。

8.對象終結(jié)規(guī)則:這個也簡單的,就是一個對象的初始化的完成扣孟,也就是構(gòu)造函數(shù)執(zhí)行的結(jié)束一定 happens-before它的finalize()方法烫堤。

并發(fā)編程三要素

原子性:一個不可再被分割的顆粒,原子性指的是一個或多個操作要么全部執(zhí)行成功要么全部執(zhí)行失敗凤价,期間不能被中斷鸽斟,也不存在上下文切換,線程切換會帶來原子性的問題

int num = 1; // 原子操作

num++; // 非原子操作利诺,從主內(nèi)存讀取num到線程工作內(nèi)存富蓄,進行 +1,再把num寫到主內(nèi)存, 除非用原子類立轧,即java.util.concurrent.atomic里的原子變量類

?

解決辦法是可以用synchronized 或 Lock(比如ReentrantLock) 來把這個多步操作“變成”原子操作

publicclassTest{

privateintnum =0;

//使用lock格粪,每個對象都是有鎖,只有獲得這個鎖才可以進行對應(yīng)的操作

? ? Lock lock = new ReentrantLock();

? ? public? void add1(){

? ? ? ? lock.lock();

? ? ? ? try {

? ? ? ? ? ? num++;

? ? ? ? }finally {

? ? ? ? ? ? lock.unlock();

? ? ? ? }

? ? }

? ? //使用synchronized氛改,和上述是一個操作,這個是保證方法被鎖住而已比伏,上述的是代碼塊被鎖住

? ? public synchronized void add2(){

? ? ? ? num++;

? ? }

}

解決核心思想:把一個方法或者代碼塊看做一個整體胜卤,保證是一個不可分割的整體

?

有序性: 程序執(zhí)行的順序按照代碼的先后順序執(zhí)行,因為處理器可能會對指令進行重排序

JVM在編譯java代碼或者CPU執(zhí)行JVM字節(jié)碼時赁项,對現(xiàn)有的指令進行重新排序葛躏,主要目的是優(yōu)化運行效率(不改變程序結(jié)果的前提)

inta =3//第一步1

intb =4//第二步2

intc =5//第三步3

inth = a*b*c //第四步4

上面的例子 執(zhí)行順序1,2,3,4 和 2,1,3,4 結(jié)果都是一樣澈段,指令重排序可以提高執(zhí)行效率,但是多線程上可能會影響結(jié)果

?

假如下面的場景舰攒,正常是順序處理

//線程1

before();//處理初始化工作败富,處理完成后才可以正式運行下面的run方法

flag = true; //標(biāo)記資源處理好了,如果資源沒處理好摩窃,此時程序就可能出現(xiàn)問題

//線程2

while(flag){

? ? run(); //核心業(yè)務(wù)代碼

}

指令重排序后兽叮,導(dǎo)致順序換了,程序出現(xiàn)問題猾愿,且難排查

//線程1

flag = true; //標(biāo)記資源處理好了鹦聪,如果資源沒處理好,此時程序就可能出現(xiàn)問題

//線程2

while(flag){

? ? run(); //核心業(yè)務(wù)代碼

}

before();//處理初始化工作蒂秘,處理完成后才可以正式運行下面的run方法

可見性: 一個線程A對共享變量的修改,另一個線程B能夠立刻看到

// 線程 A 執(zhí)行

int num = 0;

// 線程 A 執(zhí)行

num++;

// 線程 B 執(zhí)行

System.out.print("num的值:" + num);

線程A執(zhí)行 i++ 后再執(zhí)行線程 B泽本,線程 B可能有2個結(jié)果,可能是0和1姻僧。

?

因為 i++ 在線程A中執(zhí)行運算规丽,并沒有立刻更新到主內(nèi)存當(dāng)中,而線程B就去主內(nèi)存當(dāng)中讀取并打印撇贺,此時打印的就是0赌莺;也可能線程A執(zhí)行完成更新到主內(nèi)存了,線程B的值是1。所以需要保證線程的可見性显熏,synchronized雄嚣、lock和volatile能夠保證線程可見性。

常見的進程間調(diào)度算法

先來先服務(wù)調(diào)度算法:

按照作業(yè)/進程到達(dá)的先后順序進行調(diào)度 喘蟆,即:優(yōu)先考慮在系統(tǒng)中等待時間最長的作業(yè)缓升,排在長進程后的短進程的等待時間長,不利于短作業(yè)/進程

?

短作業(yè)優(yōu)先調(diào)度算法:

短進程/作業(yè)(要求服務(wù)時間最短)在實際情況中占有很大比例蕴轨,為了使得它們優(yōu)先執(zhí)行港谊,對長作業(yè)不友好

?

高響應(yīng)比優(yōu)先調(diào)度算法:

在每次調(diào)度時,先計算各個作業(yè)的優(yōu)先權(quán):優(yōu)先權(quán)=響應(yīng)比=(等待時間+要求服務(wù)時間)/要求服務(wù)時間,因為等待時間與服務(wù)時間之和就是系統(tǒng)對該作業(yè)的響應(yīng)時間橙弱,所以 優(yōu)先權(quán)=響應(yīng)比=響應(yīng)時間/要求服務(wù)時間歧寺,選擇優(yōu)先權(quán)高的進行服務(wù)需要計算優(yōu)先權(quán)信息,增加了系統(tǒng)的開銷

時間片輪轉(zhuǎn)調(diào)度算法:

輪流的為各個進程服務(wù)棘脐,讓每個進程在一定時間間隔內(nèi)都可以得到響應(yīng)斜筐,由于高頻率的進程切換,會增加了開銷蛀缝,且不區(qū)分任務(wù)的緊急程度

?

優(yōu)先級調(diào)度算法:

根據(jù)任務(wù)的緊急程度進行調(diào)度顷链,高優(yōu)先級的先處理,低優(yōu)先級的慢處理屈梁,如果高優(yōu)先級任務(wù)很多且持續(xù)產(chǎn)生嗤练,那低優(yōu)先級的就可能很慢才被處理

常見的線程間調(diào)度算法

線程調(diào)度是指系統(tǒng)為線程分配CPU使用權(quán)的過程榛了,主要分兩種:

?

協(xié)同式線程調(diào)度(分時調(diào)度模式):線程執(zhí)行時間由線程本身來控制,線程把自己的工作執(zhí)行完之后煞抬,要主動通知系統(tǒng)切換到另外一個線程上霜大。最大好處是實現(xiàn)簡單,且切換操作對線程自己是可知的革答,沒啥線程同步問題战坤。壞處是線程執(zhí)行時間不可控制,如果一個線程有問題蝗碎,可能一直阻塞在那里湖笨。

?

搶占式線程調(diào)度:每個線程將由系統(tǒng)來分配執(zhí)行時間,線程的切換不由線程本身來決定(Java中蹦骑,Thread.yield()可以讓出執(zhí)行時間慈省,但無法獲取執(zhí)行時間)。線程執(zhí)行時間系統(tǒng)可控眠菇,也不會由一個線程導(dǎo)致整個進程阻塞边败。

?

?Java線程調(diào)度就是搶占式調(diào)度,優(yōu)先讓可運行池中優(yōu)先級高的線程占用CPU,如果可運行池中的線程優(yōu)先級相同,那就隨機選擇一個線程。所以我們?nèi)绻M承┚€程多分配一些時間捎废,給一些線程少分配一些時間笑窜,可以通過設(shè)置線程優(yōu)先級來完成。

JAVA的線程的優(yōu)先級登疗,以1到10的整數(shù)指定排截。當(dāng)多個線程可以運行時,JVM一般會運行最高優(yōu)先級的線程(Thread.MIN_PRIORITY至Thread.MAX_PRIORITY)辐益。在兩線程同時處于就緒runnable狀態(tài)時断傲,優(yōu)先級越高的線程越容易被系統(tǒng)選擇執(zhí)行,但是優(yōu)先級并不是100%可以獲得智政,只不過是機會更大而已认罩。

Java多線程里面常用的鎖

悲觀鎖:當(dāng)線程去操作數(shù)據(jù)的時候,總認(rèn)為別的線程會去修改數(shù)據(jù)续捂,所以它每次拿數(shù)據(jù)的時候都會上鎖垦垂,別的線程去拿數(shù)據(jù)的時候就會阻塞,比如synchronized

樂觀鎖:每次去拿數(shù)據(jù)的時候都認(rèn)為別人不會修改牙瓢,更新的時候會判斷是別人是否回去更新數(shù)據(jù)劫拗,通過版本來判斷,如果數(shù)據(jù)被修改了就拒絕更新矾克,比如CAS是樂觀鎖杨幼,但嚴(yán)格來說并不是鎖,是通過原子性來保證數(shù)據(jù)的同步聂渊,比如說數(shù)據(jù)庫的樂觀鎖差购,通過版本控制來實現(xiàn),CAS不會保證線程同步汉嗽,樂觀的認(rèn)為在數(shù)據(jù)更新期間沒有其他線程影響

小結(jié):悲觀鎖適合寫操作多的場景欲逃,樂觀鎖適合讀操作多的場景,樂觀鎖的吞吐量會比悲觀鎖多饼暑。

?

公平鎖:指多個線程按照申請鎖的順序來獲取鎖稳析,簡單來說,一個線程組里弓叛,能保證每個線程都能拿到鎖彰居,比如ReentrantLock(底層是同步隊列FIFO:First Input First Output來實現(xiàn))

非公平鎖:獲取鎖的方式是隨機獲取的,保證不了每個線程都能拿到鎖撰筷,也就是存在有線程餓死,一直拿不到鎖陈惰,比如synchronized、ReentrantLock

小結(jié):非公平鎖性能高于公平鎖毕籽,更能重復(fù)利用CPU的時間抬闯。

?

可重入鎖:也叫遞歸鎖,在外層使用鎖之后关筒,在內(nèi)層仍然可以使用溶握,并且不發(fā)生死鎖

不可重入鎖:若當(dāng)前線程執(zhí)行某個方法已經(jīng)獲取了該鎖,那么在方法中嘗試再次獲取鎖時蒸播,就會獲取不到被阻塞

小結(jié):可重入鎖能一定程度的避免死鎖 睡榆,synchronized、ReentrantLock 是重入鎖袍榆。

?

自旋鎖:一個線程在獲取鎖的時候胀屿,如果鎖已經(jīng)被其它線程獲取,那么該線程將循環(huán)等待蜡塌,然后不斷的判斷鎖是否能夠被成功獲取碉纳,直到獲取到鎖才會退出循環(huán),任何時刻最多只能有一個執(zhí)行單元獲得鎖

小結(jié):自旋鎖不會發(fā)生線程狀態(tài)的切換,一直處于用戶態(tài)馏艾,減少了線程上下文切換的消耗劳曹,缺點是循環(huán)會消耗CPU。常見的自旋鎖:TicketLock,CLHLock,MSCLock琅摩。

共享鎖:也叫S鎖/讀鎖铁孵,能查看但無法修改和刪除的一種數(shù)據(jù)鎖,加鎖后其它用戶可以并發(fā)讀取房资、查詢數(shù)據(jù)蜕劝,但不能修改,增加,刪除數(shù)據(jù)岖沛,該鎖可被多個線程所持有暑始,用于資源數(shù)據(jù)共享。

?

互斥鎖:也叫X鎖/排它鎖/寫鎖/獨占鎖/獨享鎖/ 該鎖每一次只能被一個線程所持有婴削,加鎖后任何試圖再次加鎖的線程會被阻塞廊镜,直到當(dāng)前線程解鎖。例子:如果 線程A 對 data1 加上排他鎖后唉俗,則其他線程不能再對 data1 加任何類型的鎖嗤朴,獲得互斥鎖的線程即能讀數(shù)據(jù)又能修改數(shù)據(jù)。

?

死鎖:兩個或兩個以上的線程在執(zhí)行過程中虫溜,由于競爭資源或者由于彼此通信而造成的一種阻塞的現(xiàn)象雹姊,若無外力作用,它們都將無法讓程序進行下去衡楞。

?

下面三種是JVM為了提高鎖的獲取與釋放效率而做的優(yōu)化吱雏,針對Synchronized的鎖升級,鎖的狀態(tài)是通過對象監(jiān)視器在對象頭中的字段來表明寺酪,是不可逆的過程坎背。

偏向鎖:一段同步代碼一直被一個線程所訪問,那么該線程會自動獲取鎖寄雀,獲取鎖的代價更低得滤。

輕量級鎖:當(dāng)鎖是偏向鎖的時候,被其他線程訪問盒犹,偏向鎖就會升級為輕量級鎖懂更,其他線程會通過自旋的形式嘗試獲取鎖,但不會阻塞急膀,且性能會高點沮协。

重量級鎖:當(dāng)鎖為輕量級鎖的時候,其他線程雖然是自旋卓嫂,但自旋不會一直循環(huán)下去慷暂,當(dāng)自旋一定次數(shù)的時候且還沒有獲取到鎖,就會進入阻塞晨雳,該鎖升級為重量級鎖行瑞,重量級鎖會讓其他申請的線程進入阻塞,性能也會降低餐禁。

?

分段鎖血久、行鎖、表鎖

編寫多線程死鎖的例子

死鎖:線程在獲得了鎖A并且沒有釋放的情況下去申請鎖B帮非,這時另一個線程已經(jīng)獲得了鎖B氧吐,在釋放鎖B之前又要先獲得鎖A讹蘑,因此閉環(huán)發(fā)生,陷入死鎖循環(huán)筑舅。

publicclassDeadLockDemo{

?

privatestaticString locka ="locka";

?

privatestaticString lockb ="lockb";

?

publicvoidmethodA(){

?

synchronized (locka){

System.out.println("我是A方法中獲得了鎖A "+Thread.currentThread().getName() );

?

//讓出CPU執(zhí)行權(quán)座慰,不釋放鎖

? ? ? ? ? ? try {

? ? ? ? ? ? ? ? Thread.sleep(2000);

? ? ? ? ? ? } catch (InterruptedException e) {

? ? ? ? ? ? ? ? e.printStackTrace();

? ? ? ? ? ? }

?

? ? ? ? ? ? synchronized(lockb){

? ? ? ? ? ? ? ? System.out.println("我是A方法中獲得了鎖B "+Thread.currentThread().getName() );

? ? ? ? ? ? }

? ? ? ? }

? ? }

?

? ? public void methodB(){

? ? ? ? synchronized (lockb){

? ? ? ? ? ? System.out.println("我是B方法中獲得了鎖B "+Thread.currentThread().getName() );

?

? ? ? ? ? ? //讓出CPU執(zhí)行權(quán),不釋放鎖

? ? ? ? ? ? try {

? ? ? ? ? ? ? ? Thread.sleep(2000);

? ? ? ? ? ? } catch (InterruptedException e) {

? ? ? ? ? ? ? ? e.printStackTrace();

? ? ? ? ? ? }

?

? ? ? ? ? ? synchronized(locka){

? ? ? ? ? ? ? ? System.out.println("我是B方法中獲得了鎖A "+Thread.currentThread().getName() );

? ? ? ? ? ? }

? ? ? ? }

?

? ? }

?

? ? public static void main(String [] args){

?

? ? ? ? System.out.println("主線程運行開始運行:"+Thread.currentThread().getName());

?

? ? ? ? DeadLockDemo deadLockDemo = new DeadLockDemo();

?

? ? ? ? new Thread(()->{

? ? ? ? ? ? deadLockDemo.methodA();

? ? ? ? }).start();

?

? ? ? ? new Thread(()->{

? ? ? ? ? ? deadLockDemo.methodB();

? ? ? ? }).start();

?

? ? ? ? System.out.println("主線程運行結(jié)束:"+Thread.currentThread().getName());

?

? ? }

?

}

對于上面的例子如何解決死鎖豁翎,常見的解決辦法有兩種:

調(diào)整申請鎖的范圍

調(diào)整申請鎖的順序

publicclassFixDeadLockDemo{

?

privatestaticString locka ="locka";

?

privatestaticString lockb ="lockb";

?

publicvoidmethodA(){

?

synchronized (locka){

System.out.println("我是A方法中獲得了鎖A "+Thread.currentThread().getName() );

?

//讓出CPU執(zhí)行權(quán)角骤,不釋放鎖

? ? ? ? ? ? try {

? ? ? ? ? ? ? ? Thread.sleep(2000);

? ? ? ? ? ? } catch (InterruptedException e) {

? ? ? ? ? ? ? ? e.printStackTrace();

? ? ? ? ? ? }

?

? ? ? ? }

?

? ? ? ? synchronized(lockb){

? ? ? ? ? ? System.out.println("我是A方法中獲得了鎖B "+Thread.currentThread().getName() );

? ? ? ? }

? ? }

?

?

? ? public void methodB(){

? ? ? ? synchronized (lockb){

? ? ? ? ? ? System.out.println("我是B方法中獲得了鎖B "+Thread.currentThread().getName() );

?

? ? ? ? ? ? //讓出CPU執(zhí)行權(quán),不釋放鎖

? ? ? ? ? ? try {

? ? ? ? ? ? ? ? Thread.sleep(2000);

?

? ? ? ? ? ? } catch (InterruptedException e) {

? ? ? ? ? ? ? ? e.printStackTrace();

? ? ? ? ? ? }

?

? ? ? ? }

?

? ? ? ? synchronized(locka){

? ? ? ? ? ? System.out.println("我是B方法中獲得了鎖A "+Thread.currentThread().getName() );

? ? ? ? }

? ? }

?

?

? ? public static void main(String [] args){

?

? ? ? ? System.out.println("主線程運行開始運行:"+Thread.currentThread().getName());

?

? ? ? ? FixDeadLockDemo deadLockDemo = new FixDeadLockDemo();

?

? ? ? ? for(int i=0; i<10;i++){

? ? ? ? ? ? new Thread(()->{

? ? ? ? ? ? ? ? deadLockDemo.methodA();

? ? ? ? ? ? }).start();

?

? ? ? ? ? ? new Thread(()->{

? ? ? ? ? ? ? ? deadLockDemo.methodB();

? ? ? ? ? ? }).start();

? ? ? ? }

?

? ? ? ? System.out.println("主線程運行結(jié)束:"+Thread.currentThread().getName());

?

? ? }

?

}

死鎖的4個必要條件:

互斥條件:進程對所分配到的資源不允許其他進程進行訪問心剥,若其他進程訪問該資源,只能等待背桐,直至占有該資源的進程使用完成后釋放該資源

請求和保持條件:進程獲得一定的資源之后优烧,又對其他資源發(fā)出請求,但是該資源可能被其他進程占有链峭,此事請求阻塞畦娄,但又對自己獲得的資源保持不放

不可剝奪條件:是指進程已獲得的資源,在未完成使用之前弊仪,不可被剝奪熙卡,只能在使用完后自己釋放

環(huán)路等待條件:是指進程發(fā)生死鎖后,若干進程之間形成一種頭尾相接的循環(huán)等待資源關(guān)系

這四個條件是死鎖的必要條件励饵,只要系統(tǒng)發(fā)生死鎖驳癌,這些條件必然成立,而只要上述條件之 一不滿足役听,就不會發(fā)生死鎖颓鲜。

設(shè)計一個簡單的不可重入鎖例子

不可重入鎖:若當(dāng)前線程執(zhí)行某個方法已經(jīng)獲取了該鎖,那么在其他方法中嘗試再次獲取鎖時典予,就會獲取不到被阻塞甜滨。

private voidmethodA(){

//獲取鎖 TODO

methodB();

}

?

private voidmethodB(){

//獲取鎖 TODO

//其他操作

}

/**

* 不可重入鎖 簡單例子

*

*? 不可重入鎖:若當(dāng)前線程執(zhí)行某個方法已經(jīng)獲取了該鎖,那么在其他方法中嘗試再次獲取鎖時瘤袖,就會獲取不到被阻塞

*/

public class UnreentrantLock {

?

private boolean isLocked =false;

?

public synchronized void lock() throws InterruptedException {

?

System.out.println("進入lock加鎖 "+Thread.currentThread().getName());

?

//判斷是否已經(jīng)被鎖衣摩,如果被鎖則當(dāng)前請求的線程進行等待

while(isLocked){

System.out.println("進入wait等待 "+Thread.currentThread().getName());

wait();

}

//進行加鎖

isLocked =true;

}

public synchronized voidunlock(){

System.out.println("進入unlock解鎖 "+Thread.currentThread().getName());

isLocked =false;

//喚醒對象鎖池里面的一個線程

notify();

}

}

?

?

?

public class Main {

private UnreentrantLock unreentrantLock = new UnreentrantLock();

//加鎖建議在try里面,解鎖建議在finally

public voidmethodA(){

try {

unreentrantLock.lock();

System.out.println("methodA方法被調(diào)用");

methodB();

}catch (InterruptedException e){

e.fillInStackTrace();

} finally {

unreentrantLock.unlock();

}

}

?

public voidmethodB(){

try {

unreentrantLock.lock();

System.out.println("methodB方法被調(diào)用");

}catch (InterruptedException e){

e.fillInStackTrace();

} finally {

unreentrantLock.unlock();

}

}

public static void main(String [] args){

//演示的是同個線程

new Main().methodA();

}

}

?

//同一個線程捂敌,重復(fù)獲取鎖失敗艾扮,形成死鎖,這個就是不可重入鎖

設(shè)計一個簡單的可重入鎖例子

可重入鎖:也叫遞歸鎖黍匾,在外層使用鎖之后栏渺,在內(nèi)層仍然可以使用,并且不發(fā)生死鎖

/**

* 可重入鎖 簡單例子

*

*? 可重入鎖:也叫遞歸鎖锐涯,在外層使用鎖之后磕诊,在內(nèi)層仍然可以使用,并且不發(fā)生死鎖

*/

public class ReentrantLock {

?

private boolean isLocked =false;

?

//用于記錄是不是重入的線程

private Thread lockedOwner = null;

?

//累計加鎖次數(shù),加鎖一次累加1霎终,解鎖一次減少1

private int lockedCount = 0;

?

public synchronized void lock() throws InterruptedException {

?

System.out.println("進入lock加鎖 "+Thread.currentThread().getName());

?

Thread thread = Thread.currentThread();

?

//判斷是否是同個線程獲取鎖, 引用地址的比較

while(isLocked && lockedOwner != thread ){

System.out.println("進入wait等待 "+Thread.currentThread().getName());

System.out.println("當(dāng)前鎖狀態(tài) isLocked = "+isLocked);

System.out.println("當(dāng)前count數(shù)量 lockedCount =? "+lockedCount);

wait();

}

?

//進行加鎖

isLocked =true;

lockedOwner = thread;

lockedCount++;

}

public synchronized voidunlock(){

System.out.println("進入unlock解鎖 "+Thread.currentThread().getName());

?

Thread thread = Thread.currentThread();

?

//線程A加的鎖滞磺,只能由線程A解鎖,其他線程B不能解鎖

if(thread == this.lockedOwner){

lockedCount--;

if(lockedCount == 0){

isLocked =false;

lockedOwner = null;

//喚醒對象鎖池里面的一個線程

notify();

}

}

}

}

publicclassMain{

//private UnreentrantLock unreentrantLock = new UnreentrantLock();

? ? private ReentrantLock reentrantLock = new ReentrantLock();

?

? ? //加鎖建議在try里面莱褒,解鎖建議在finally

? ? public void? methodA(){

?

? ? ? ? try {

? ? ? ? ? ? reentrantLock.lock();

? ? ? ? ? ? System.out.println("methodA方法被調(diào)用");

? ? ? ? ? ? methodB();

?

? ? ? ? }catch (InterruptedException e){

? ? ? ? ? ? e.fillInStackTrace();

?

? ? ? ? } finally {

? ? ? ? ? ? reentrantLock.unlock();

? ? ? ? }

?

? ? }

?

? ? public void methodB(){

?

? ? ? ? try {

? ? ? ? ? ? reentrantLock.lock();

? ? ? ? ? ? System.out.println("methodB方法被調(diào)用");

?

? ? ? ? }catch (InterruptedException e){

? ? ? ? ? ? e.fillInStackTrace();

?

? ? ? ? } finally {

? ? ? ? ? ? reentrantLock.unlock();

? ? ? ? }

? ? }

?

? ? public static void main(String [] args){

? ? ? ? for(int i=0 ;i<10;i++){

? ? ? ? ? ? //演示的是同個線程

? ? ? ? ? ? new Main().methodA();

? ? ? ? }

? ? }

}

歡迎補充;骼А!广凸!

?著作權(quán)歸作者所有,轉(zhuǎn)載或內(nèi)容合作請聯(lián)系作者
  • 序言:七十年代末阅茶,一起剝皮案震驚了整個濱河市,隨后出現(xiàn)的幾起案子谅海,更是在濱河造成了極大的恐慌脸哀,老刑警劉巖,帶你破解...
    沈念sama閱讀 222,104評論 6 515
  • 序言:濱河連續(xù)發(fā)生了三起死亡事件扭吁,死亡現(xiàn)場離奇詭異撞蜂,居然都是意外死亡,警方通過查閱死者的電腦和手機侥袜,發(fā)現(xiàn)死者居然都...
    沈念sama閱讀 94,816評論 3 399
  • 文/潘曉璐 我一進店門蝌诡,熙熙樓的掌柜王于貴愁眉苦臉地迎上來,“玉大人枫吧,你說我怎么就攤上這事浦旱。” “怎么了由蘑?”我有些...
    開封第一講書人閱讀 168,697評論 0 360
  • 文/不壞的土叔 我叫張陵闽寡,是天一觀的道長。 經(jīng)常有香客問我尼酿,道長爷狈,這世上最難降的妖魔是什么? 我笑而不...
    開封第一講書人閱讀 59,836評論 1 298
  • 正文 為了忘掉前任裳擎,我火速辦了婚禮涎永,結(jié)果婚禮上,老公的妹妹穿的比我還像新娘鹿响。我一直安慰自己羡微,他們只是感情好,可當(dāng)我...
    茶點故事閱讀 68,851評論 6 397
  • 文/花漫 我一把揭開白布惶我。 她就那樣靜靜地躺著妈倔,像睡著了一般。 火紅的嫁衣襯著肌膚如雪绸贡。 梳的紋絲不亂的頭發(fā)上盯蝴,一...
    開封第一講書人閱讀 52,441評論 1 310
  • 那天毅哗,我揣著相機與錄音,去河邊找鬼捧挺。 笑死虑绵,一個胖子當(dāng)著我的面吹牛,可吹牛的內(nèi)容都是我干的闽烙。 我是一名探鬼主播翅睛,決...
    沈念sama閱讀 40,992評論 3 421
  • 文/蒼蘭香墨 我猛地睜開眼,長吁一口氣:“原來是場噩夢啊……” “哼黑竞!你這毒婦竟也來了捕发?” 一聲冷哼從身側(cè)響起,我...
    開封第一講書人閱讀 39,899評論 0 276
  • 序言:老撾萬榮一對情侶失蹤摊溶,失蹤者是張志新(化名)和其女友劉穎爬骤,沒想到半個月后,有當(dāng)?shù)厝嗽跇淞掷锇l(fā)現(xiàn)了一具尸體莫换,經(jīng)...
    沈念sama閱讀 46,457評論 1 318
  • 正文 獨居荒郊野嶺守林人離奇死亡拉岁,尸身上長有42處帶血的膿包…… 初始之章·張勛 以下內(nèi)容為張勛視角 年9月15日...
    茶點故事閱讀 38,529評論 3 341
  • 正文 我和宋清朗相戀三年惰爬,在試婚紗的時候發(fā)現(xiàn)自己被綠了。 大學(xué)時的朋友給我發(fā)了我未婚夫和他白月光在一起吃飯的照片。...
    茶點故事閱讀 40,664評論 1 352
  • 序言:一個原本活蹦亂跳的男人離奇死亡疙咸,死狀恐怖,靈堂內(nèi)的尸體忽然破棺而出巩掺,到底是詐尸還是另有隱情,我是刑警寧澤页畦,帶...
    沈念sama閱讀 36,346評論 5 350
  • 正文 年R本政府宣布胖替,位于F島的核電站,受9級特大地震影響独令,放射性物質(zhì)發(fā)生泄漏好芭。R本人自食惡果不足惜,卻給世界環(huán)境...
    茶點故事閱讀 42,025評論 3 334
  • 文/蒙蒙 一招狸、第九天 我趴在偏房一處隱蔽的房頂上張望。 院中可真熱鬧瓢颅,春花似錦挽懦、人聲如沸。這莊子的主人今日做“春日...
    開封第一講書人閱讀 32,511評論 0 24
  • 文/蒼蘭香墨 我抬頭看了看天上的太陽。三九已至客年,卻和暖如春漠吻,著一層夾襖步出監(jiān)牢的瞬間,已是汗流浹背绍傲。 一陣腳步聲響...
    開封第一講書人閱讀 33,611評論 1 272
  • 我被黑心中介騙來泰國打工, 沒想到剛下飛機就差點兒被人妖公主榨干…… 1. 我叫王不留烫饼,地道東北人试读。 一個月前我還...
    沈念sama閱讀 49,081評論 3 377
  • 正文 我出身青樓鹏往,卻偏偏與公主長得像,于是被迫代替她去往敵國和親韩容。 傳聞我的和親對象是個殘疾皇子唐瀑,可洞房花燭夜當(dāng)晚...
    茶點故事閱讀 45,675評論 2 359