并發(fā)編程基礎(chǔ)
Java線程模型
發(fā)生了系統(tǒng)調(diào)用的鎖患久,就是重量鎖
MMU: 虛擬地址映射
線程類型
用戶線程:使用Java代碼創(chuàng)建的線程贡避。
內(nèi)核線程:操作系統(tǒng)對(duì)應(yīng)的線程
對(duì)應(yīng)關(guān)系
一對(duì)一
一個(gè)用戶線程對(duì)應(yīng)一個(gè)內(nèi)核線程
優(yōu)點(diǎn):簡(jiǎn)單庵寞,幾乎所有對(duì)線程的操作都交給了內(nèi)核線程北苟。
缺點(diǎn):
對(duì)用戶線程的大部分操作會(huì)映射到內(nèi)核線程上,引起用戶態(tài)和內(nèi)核態(tài)的頻繁切換怕篷。
創(chuàng)建大量線程對(duì)系統(tǒng)性能有影響历筝。
多對(duì)一
多個(gè)用戶線程對(duì)應(yīng)一個(gè)內(nèi)核線程
優(yōu)點(diǎn):用戶線程的很多操作對(duì)內(nèi)核來說是透明的,不需要進(jìn)行頻繁的用戶態(tài)和內(nèi)核態(tài)的切換廊谓,線程的創(chuàng)建梳猪、調(diào)度、同步非痴舯裕快春弥。
缺點(diǎn):
其中一個(gè)線程阻塞,其他用戶線程也無法執(zhí)行叠荠。
內(nèi)核不知道用戶有哪些線程匿沛,無法像內(nèi)核線程一樣實(shí)現(xiàn)完整的調(diào)度、優(yōu)先級(jí)等榛鼎。
多對(duì)多
用戶態(tài)和內(nèi)核態(tài)
linux系統(tǒng)虛擬地址映射
物理地址空間 :物理地址就是真實(shí)地址逃呼,對(duì)應(yīng)真實(shí)的內(nèi)存條。內(nèi)存像一個(gè)數(shù)組借帘,每個(gè)存儲(chǔ)單元被分配了一個(gè)地址蜘渣,就是物理地址。
虛擬地址空間 : 每個(gè)進(jìn)程擁有一個(gè)巨大的連續(xù)內(nèi)存空間肺然,甚至比內(nèi)存空間更大,這是一個(gè)“假象”腿准。
CPU使用虛擬地址像內(nèi)存尋址际起,通過內(nèi)存管理單元MMU硬件,把虛擬地址轉(zhuǎn)換成真實(shí)的物理地址吐葱。
CPU有四種不同的執(zhí)行級(jí)別0-3街望,linux用0表示內(nèi)核態(tài),3表示用戶態(tài)弟跑。
切換方式
- 系統(tǒng)調(diào)用 (關(guān)注重點(diǎn))
- 異常(不是Java中的異常)
- 外圍設(shè)備中斷
CPU上下文
CPU寄存器和程序計(jì)數(shù)器就是CPU的上下文灾前,都是CPU在運(yùn)行任務(wù)前,必須的依賴環(huán)境孟辑。
CPU寄存器:CPU內(nèi)置的容量小哎甲、速度極快的內(nèi)存蔫敲。
程序計(jì)數(shù)器:用來存儲(chǔ)CPU正在執(zhí)行的指令位置,或者即將執(zhí)行的下一條指令位置炭玫。
CPU上下文切換
將前一個(gè)任務(wù)的CPU上下文保存奈嘿,然后加載新任務(wù)的上下文到寄存器和程序計(jì)數(shù)器,最后再跳轉(zhuǎn)到程序計(jì)數(shù)器所指的新位置吞加,運(yùn)行新的任務(wù)裙犹。
CPU上下文切換類型
進(jìn)程上下文切換
系統(tǒng)調(diào)用:從用戶態(tài)到內(nèi)核態(tài)的轉(zhuǎn)變,通過系統(tǒng)調(diào)用來完成衔憨。 屬于同進(jìn)程內(nèi)的CPU上下文切換叶圃。一次系統(tǒng)調(diào)用過程,發(fā)生兩次CPU上下文切換(用戶態(tài) —> 內(nèi)核態(tài) —> 內(nèi)核態(tài))
進(jìn)程間上下文切換:進(jìn)程的上下文不僅包括了虛擬內(nèi)存践图、棧盗似、全局變量等用戶空間資源,還包括了內(nèi)核堆棧平项、寄存器等內(nèi)核空間資源赫舒。比系統(tǒng)調(diào)用多了一步,在保存內(nèi)核態(tài)資源之前闽瓢,需要把該進(jìn)程的用戶態(tài)資源保存下來接癌,加載了下一進(jìn)程的內(nèi)核態(tài)后,需要刷新新進(jìn)程的虛擬內(nèi)存和用戶棧扣讼。
進(jìn)程上下文切換場(chǎng)景:
- CPU輪轉(zhuǎn)缺猛,進(jìn)程分配的時(shí)間片耗盡。
- 系統(tǒng)資源不足
- 調(diào)用sleep等方法將自己主動(dòng)掛起
- 有優(yōu)先級(jí)更高的進(jìn)程運(yùn)行
線程上下文切換
- 兩個(gè)線程屬于不同進(jìn)程: 資源不共享椭符,切換過程和進(jìn)程上下文切換一樣荔燎。
- 屬于同一進(jìn)程:虛擬內(nèi)存共享,只需要切換線程的私有數(shù)據(jù)销钝、寄存器等不共享的數(shù)據(jù)有咨。
并發(fā)相關(guān)知識(shí)
對(duì)象頭
基本對(duì)象布局:對(duì)象頭(96bit)+ [實(shí)例數(shù)據(jù)] + [對(duì)齊填充數(shù)據(jù)]
實(shí)例數(shù)據(jù)就是定義的全局變量所占的內(nèi)存。
對(duì)象的大小必須為8字節(jié)(byte)的整數(shù)倍蒸健。1byte = 8 bit
添加這個(gè)依賴座享,可以打印出對(duì)象的信息ClassLayout.parseClass(A.class).toPrintable();
<dependency>
<groupId>org.openjdk.jol</groupId>
<artifactId>jol-core</artifactId>
<version>0.9</version>
</dependency>
大小端模式
小端模式:高字節(jié)存在高地址,低字節(jié)存在低地址似忧。所以打印出來是反的渣叛。
如 1的字節(jié)碼為:00000000 00000000 00000000 00000001
打印出來就是:00000001 00000000 00000000 00000000
對(duì)象頭:
mark world 占64bit
Klass point 指向?qū)ο蟮脑獢?shù)據(jù)。開啟了指針壓縮盯捌,4位淳衙。沒開啟,就占8位
偏向鎖 01 輕量鎖 00 重量鎖 10 GC 11
偏向鎖:
101 可偏向
001 不可偏向 計(jì)算了hashCode之后就不能偏向了
公平鎖和非公平鎖
總結(jié):
一朝排隊(duì),永遠(yuǎn)排隊(duì)
基于JDK1.8的解釋:
非公平鎖
首先會(huì)在加鎖的時(shí)候去搶鎖(公平鎖不會(huì)上來就拿鎖)
如果加鎖失敗箫攀,判斷鎖是否被人持有了肠牲,沒有被人持有的話,會(huì)直接去進(jìn)行加鎖(公平鎖會(huì)判斷是否有人排隊(duì))匠童,成功進(jìn)入代碼塊埂材。失敗則進(jìn)入隊(duì)列
進(jìn)入隊(duì)列后,如果前面那個(gè)是head,則再次嘗試加鎖汤求,失敗則park俏险,進(jìn)行真正的排隊(duì)。
static final class NonfairSync extends Sync {
private static final long serialVersionUID = 7316153563782823691L;
/**
* Performs lock. Try immediate barge, backing up to normal
* acquire on failure.
*/
final void lock() {
// 第1步 加鎖的時(shí)候就去獲取鎖
if (compareAndSetState(0, 1))
setExclusiveOwnerThread(Thread.currentThread());
else
acquire(1);
}
/**
*acquire(1)方法會(huì)調(diào)用到該方法扬绪,再次嘗試獲取鎖
*
*/
protected final boolean tryAcquire(int acquires) {
return nonfairTryAcquire(acquires);
}
}
abstract static class Sync extends AbstractQueuedSynchronizer {
final boolean nonfairTryAcquire(int acquires) {
final Thread current = Thread.currentThread();
int c = getState();
if (c == 0) {
// 第2步 鎖沒別人持有竖独,會(huì)去再次嘗試拿鎖
if (compareAndSetState(0, acquires)) {
setExclusiveOwnerThread(current);
return true;
}
}
// 重入鎖
else if (current == getExclusiveOwnerThread()) {
int nextc = c + acquires;
if (nextc < 0) // overflow
throw new Error("Maximum lock count exceeded");
setState(nextc);
return true;
}
return false;
}
...
}
公平鎖
- 調(diào)用lock的時(shí)候不會(huì)去嘗試加鎖,回去查看隊(duì)列中有沒有排隊(duì)的節(jié)點(diǎn)Node挤牛,如果有則進(jìn)入隊(duì)列(并不等于排隊(duì))莹痢,
- 會(huì)再次進(jìn)行查看前面那個(gè)節(jié)點(diǎn)是否為head,如果是墓赴,則再次嘗試拿鎖竞膳。失敗則排隊(duì)park
static final class FairSync extends Sync {
private static final long serialVersionUID = -3000897897090466540L;
final void lock() {
acquire(1);
}
/**
* Fair version of tryAcquire. Don't grant access unless
* recursive call or no waiters or is first.
*/
protected final boolean tryAcquire(int acquires) {
final Thread current = Thread.currentThread();
int c = getState();
if (c == 0) {
// 會(huì)判斷是否有節(jié)點(diǎn)在排隊(duì)
if (!hasQueuedPredecessors() &&
compareAndSetState(0, acquires)) {
setExclusiveOwnerThread(current);
return true;
}
}
else if (current == getExclusiveOwnerThread()) {
int nextc = c + acquires;
if (nextc < 0)
throw new Error("Maximum lock count exceeded");
setState(nextc);
return true;
}
return false;
}
}
public abstract class AbstractQueuedSynchronizer
extends AbstractOwnableSynchronizer
implements java.io.Serializable {
final boolean acquireQueued(final Node node, int arg) {
boolean failed = true;
try {
boolean interrupted = false;
for (;;) {
final Node p = node.predecessor();
if (p == head && tryAcquire(arg)) {
setHead(node);
p.next = null; // help GC
failed = false;
return interrupted;
}
if (shouldParkAfterFailedAcquire(p, node) &&
parkAndCheckInterrupt())
interrupted = true;
}
} finally {
if (failed)
cancelAcquire(node);
}
}
}
是否開啟了偏向模式?涉及到偏向的撤銷诫硕。
偏向延遲的時(shí)間是4秒鐘坦辟。JVM自身的代碼,基本上不存在偏向鎖章办,所以把偏向鎖給禁用掉了锉走,因?yàn)槠虻某蜂N很耗費(fèi)性能。而過了4秒鐘之后藕届,JVM認(rèn)為自己已經(jīng)執(zhí)行完成了挪蹭,而用戶寫的代碼不知道是不是偏向鎖,所以開啟偏向鎖休偶。自己可以在啟動(dòng)的時(shí)候設(shè)置JVM的參數(shù)梁厉,設(shè)置啟動(dòng)延遲為0秒。-XX:BiasedLockingStartupDelay=0 -XX:+UseBiasedLocking //啟用偏向鎖
輕量鎖00加鎖成功一定是要無鎖的狀態(tài)
pthread_mutex_lock 重量鎖
JMH 基準(zhǔn)測(cè)試 java性能測(cè)試
public class A {
static boolean isRunning = true;
static int i;
void test(){
log.info("t1.start-----");
while (isRunning){
// 代碼中不做什么事情
i++;
i = i + 3;
}
log.error("t1.end-----");
}
public static void main(String[] args) throws InterruptedException {
A a = new A();
new Thread(a::test,"t1").start();
Thread.sleep(200);
// 睡眠過后椅贱,更改這個(gè)值懂算,不會(huì)讓t1線程停止運(yùn)行。isRunning改成volite可以實(shí)現(xiàn)庇麦,不是因?yàn)椴豢梢娦裕且驗(yàn)榻沽酥噶钪嘏? // 其實(shí)是Jvm對(duì)代碼進(jìn)行了優(yōu)化喜德,發(fā)現(xiàn)while循環(huán)中并沒有做什么操作山橄,創(chuàng)建了一個(gè)臨時(shí)變量等于isRunning,來控制了循環(huán)
isRunning = false;
}
}
happend before JVM對(duì)代碼進(jìn)行了優(yōu)化舍悯,如果發(fā)現(xiàn)
兩條CAS指令不能保證原子性
synchronize發(fā)生異常航棱,如果沒有進(jìn)行try catch 會(huì)釋放鎖睡雇。 monitorExit指令
Lock特點(diǎn)
- 可打斷(lock.lockInterruptibly() 獲取鎖),可重入
- 可以設(shè)置超時(shí)時(shí)間
- 可以設(shè)置為公平鎖
- 支持多個(gè)條件變量
- 支持讀寫鎖
打斷之后是在異常中處理的饮醇,自己可以在打斷的異常中做自己的邏輯處理它抱。
線程的順序執(zhí)行:
- wait和notify實(shí)現(xiàn)(while循環(huán))
- LockSupport.park 和 LockSupport.unpark(t1)
- Join(),future()
并發(fā)編程基礎(chǔ)知識(shí)
高并發(fā)的好處
- 充分利用CPU資源
- 加快響應(yīng)用戶時(shí)間
- 代碼模塊化朴艰,異步化观蓄,簡(jiǎn)單化
多線程注意事項(xiàng)
- 線程之間的安全性
- 線程之間的死鎖
- 線程對(duì)服務(wù)器資源的消耗
線程的啟動(dòng)與中止
啟動(dòng):
- 繼承Thread類,調(diào)用start方法
- 實(shí)現(xiàn)Runnable祠墅,交給Thread運(yùn)行
- 實(shí)現(xiàn)Callable侮穿,通過FutureTask把Callable包裝成Runnable,交給Thread運(yùn)行毁嗦,可以通過FutureTask拿到Callable運(yùn)行后的返回值亲茅。
中止:
- run方法執(zhí)行完成,或者拋出一個(gè)未處理的異常導(dǎo)致線程提前結(jié)束
- 調(diào)用suspend()狗准、resume() 和 stop()這些過期方法克锣,不建議使用,調(diào)用后不會(huì)釋放占有的資源
建議使用中斷式的操作腔长,調(diào)用線程的interrupt()方法對(duì)其進(jìn)行中斷操作袭祟,這個(gè)是協(xié)作式的。該線程通過isInterrupted()方法進(jìn)行判斷是否被中斷饼酿。調(diào)用靜態(tài)方法Thread.interrupted()會(huì)將中斷標(biāo)識(shí)改為false榕酒,也可以作為判斷。
處于死鎖的線程無法被中斷
線程
線程中的方法
run()方法故俐,本質(zhì)是就是一個(gè)成員方法想鹰, 可重復(fù)執(zhí)行,也可被單獨(dú)調(diào)用
start()方法药版,只有執(zhí)行了start()方法辑舷,才真正啟動(dòng)線程,只能執(zhí)行一次槽片。
yield()方法何缓,讓出CPU的執(zhí)行權(quán),但是讓出時(shí)間不可設(shè)定还栓,也不會(huì)釋放鎖資源碌廓。
join()方法,把指定線程A加入到當(dāng)前線程剩盒,知道A線程執(zhí)行完成后谷婆,才會(huì)繼續(xù)執(zhí)行當(dāng)前線程。兩個(gè)交替的線程可以合并為順序執(zhí)行。
sleep()方法纪挎,調(diào)用后期贫,當(dāng)前線程會(huì)休眠,不會(huì)釋放鎖异袄。
setPriority()方法通砍,設(shè)置線程的優(yōu)先級(jí),高優(yōu)先級(jí)的線程分配的時(shí)間片數(shù)量要多于低優(yōu)先級(jí)的線程烤蜕。默認(rèn)優(yōu)先級(jí)為5封孙,范圍為1-10。高優(yōu)先級(jí)的線程并不能保證一定會(huì)比低優(yōu)先級(jí)的線程先執(zhí)行玖绿。因?yàn)榫€程的調(diào)度最終取決于操作系統(tǒng)敛瓷。
守護(hù)線程
Daemon守護(hù)線程是一種支持型線程,主要用作程序中后臺(tái)調(diào)度和支持性工作斑匪。當(dāng)Java虛擬機(jī)中不存在非Daemon線程的時(shí)候呐籽,Java虛擬機(jī)就會(huì)退出。使用Thread.setDaemon(true)將線程設(shè)置為守護(hù)線程蚀瘸。虛擬機(jī)退出時(shí)守護(hù)線程的finally塊不一定會(huì)執(zhí)行狡蝶,所以守護(hù)線程不能依靠finally塊來確保執(zhí)行關(guān)閉或清理資源。垃圾回收線程就是守護(hù)線程贮勃。
等待通知機(jī)制
線程A調(diào)用了對(duì)象o的wait()方法進(jìn)入等待狀態(tài)贪惹,而線程B調(diào)用了對(duì)象o的notify()或者notifyAll()方法,線程A收到通知后寂嘉,從o.wait()方法返回奏瞬,執(zhí)行后續(xù)操作。這幾個(gè)方法都是針對(duì)對(duì)象的泉孩,都是Object中的方法硼端。
等待通知范式:
// 等待方遵循的范式
synchronized(對(duì)象){// 1.獲取對(duì)象的鎖
while(條件不滿足){// 2.條件不滿足,調(diào)用對(duì)象的wait方法寓搬,被通知后仍要檢查條件
對(duì)象.wait();// 會(huì)在這進(jìn)行阻塞
}
對(duì)應(yīng)的邏輯處理 // 3.條件滿足則執(zhí)行對(duì)應(yīng)的邏輯
}
// 通知方范式
synchronized(對(duì)象){// 1.獲得對(duì)象的鎖
改變條件 // 2.改變條件
對(duì)象.notifyAll(); // 3.通知所有等待在線程上的線程
}
wait()珍昨、notify()、notifyAll()方法句喷,線程必須要獲得該對(duì)象的鎖镣典,只能在同步方法或者同步代碼塊中調(diào)用。進(jìn)入wait方法后唾琼,當(dāng)前線程就會(huì)釋放鎖兄春。調(diào)用了notifyAll()后其它線程就會(huì)去競(jìng)爭(zhēng)鎖。但是鎖釋放是在同步代碼塊執(zhí)行完成后釋放锡溯,所以一般notifyAll方法都是在同步代碼塊的最后一行執(zhí)行神郊。
線程的并發(fā)工具類
Fork-Join
分而治之肴裙,設(shè)計(jì)思想是將一個(gè)難以解決的大問題趾唱,分割成一些規(guī)模較小的相同問題涌乳,各個(gè)擊破,分而治之甜癞,最后進(jìn)行join匯總夕晓。快速排序悠咱,歸并排序蒸辆,二分查找,大數(shù)據(jù)中M/R都是分而治之的思想析既。
CountDownLatch
閉鎖躬贡,能夠使一個(gè)線程等待其它線程完成各自的工作后再執(zhí)行。通過一個(gè)計(jì)數(shù)器來實(shí)現(xiàn)眼坏,計(jì)數(shù)器的初始值為初十任務(wù)的數(shù)量拂玻,每完成一個(gè)任務(wù)后,計(jì)數(shù)器減1宰译,當(dāng)計(jì)數(shù)器到達(dá)0時(shí)檐蚜,表示所以任務(wù)已完成。在閉鎖上等待 CountDownLatch.await()方法的線程就可以恢復(fù)執(zhí)行任務(wù)沿侈。
static CountDownLatch countDownLatch = new CountDownLatch(5);
public static void countDownLatchTest() {
for (int i = 0; i < 5; i++) {
int finalI = i;
new Thread(() -> {
System.out.println("子線程運(yùn)行... " + finalI);
countDownLatch.countDown();// 計(jì)數(shù)器減1
}).start();
try {
Thread.sleep(500);
} catch (InterruptedException e) {
e.printStackTrace();
}
}
}
public static void main(String[] args) {
countDownLatchTest();
try {
countDownLatch.await();// 等計(jì)數(shù)器為0之后闯第,停止阻塞,恢復(fù)執(zhí)行
} catch (InterruptedException e) {
e.printStackTrace();
}
System.out.println("主線程運(yùn)行完成...");
}
并非說任務(wù)數(shù)量和線程數(shù)量一點(diǎn)要相等缀拭,一個(gè)線程可以多吃調(diào)用countDownLatch.countDown();進(jìn)行計(jì)數(shù)器減1
CycilcBarrier
可循環(huán)使用的屏障咳短。讓一組線程到達(dá)一個(gè)屏障時(shí)被阻塞,直到最后一個(gè)線程到達(dá)屏障時(shí)蛛淋,屏障才會(huì)開放咙好,所有被屏蔽攔截的線程才會(huì)繼續(xù)執(zhí)行。
CyclicBarrier 默認(rèn)的構(gòu)造方法是 CyclicBarrier(int parties)铣鹏,其參數(shù)表示屏障攔截的線程數(shù)量敷扫,每個(gè)線程調(diào)用 CyclicBarrier.await 方法告訴 CyclicBarrier 我已經(jīng)到達(dá)了屏障,然 后當(dāng)前線程被阻塞诚卸。
CyclicBarrier 還提供一個(gè)更高級(jí)的構(gòu)造函數(shù) CyclicBarrie(r int parties葵第,Runnable barrierAction),用于在線程到達(dá)屏障時(shí)合溺,優(yōu)先執(zhí)行 barrierAction卒密,方便處理更復(fù) 雜的業(yè)務(wù)場(chǎng)景。
Semaphore
信號(hào)量用來控制同時(shí)訪問特定資源的線程數(shù)量棠赛∠妫可以用于做流量控制膛腐。Semaphore 的構(gòu)造方法 Semaphore(int permits)接受一個(gè)整型的數(shù)字, 表示可用的許可證數(shù)量鼎俘。線程使用 Semaphore的 acquire()方法獲取一個(gè)許可證哲身,使用完之后調(diào)用 release()方法歸還許可證。還可以用 tryAcquire()方法嘗試獲取許可證贸伐。
Semaphore 還提供一些其他方法勘天,具體如下。
intavailablePermits():返回此信號(hào)量中當(dāng)前可用的許可證數(shù)捉邢。
intgetQueueLength():返回正在等待獲取許可證的線程數(shù)脯丝。
booleanhasQueuedThreads():是否有線程正在等待獲取許可證。
void reducePermits(int reduction):減少 reduction 個(gè)許可證伏伐,是個(gè)protected 方法宠进。
Collection getQueuedThreads():返回所有等待獲取許可證的線程集合,是個(gè)protected 方法藐翎。
Exchange
Exchanger交換者是一個(gè)用于線程間協(xié)作的工具類材蹬。用于進(jìn)行線程間數(shù)據(jù)交換。提供一個(gè)同步點(diǎn)阱高,在這個(gè)同步點(diǎn)赚导,兩個(gè)線程可以交換彼此的數(shù)據(jù)。通過exchange()方法交換數(shù)據(jù)赤惊,第一個(gè)線程先執(zhí)行exchange方法吼旧,會(huì)等待第二個(gè)線程執(zhí)行exchange方法,兩個(gè)線程都到達(dá)同步點(diǎn)時(shí)未舟,就可以交換數(shù)據(jù)圈暗。
原子操作CAS
如果有兩個(gè)操作A和B,如果從執(zhí)行 A 的線程來看裕膀,當(dāng)另一個(gè)線程執(zhí)行 B 時(shí)员串,要么將 B 全部執(zhí)行完,要么完全不執(zhí)行 B昼扛,那么 A 和 B 對(duì)彼此來說是原子的寸齐。
鎖實(shí)現(xiàn)原子操作存在的問題:被阻塞的線程優(yōu)先級(jí)很高,獲得鎖的線程一直不釋放鎖抄谐,大量線程競(jìng)爭(zhēng)支援渺鹦,導(dǎo)致CPU資源浪費(fèi),死鎖問題蛹含。
CAS基本思路:內(nèi)存地址V上的值和期望的值A(chǔ)相等毅厚,則給其賦予新值B,否則不做任何事浦箱,只返回原值吸耿。
CAS實(shí)現(xiàn)原子操作的3大問題
-
ABA問題
一個(gè)值原來是A祠锣,后面變成了B,又修改成了A咽安,使用CAS進(jìn)行檢查時(shí)發(fā)現(xiàn)其值沒有發(fā)生變化伴网,但是實(shí)際上是已經(jīng)變化過了的。
解決思路就是使用版本號(hào)板乙,在變量面前加上版本號(hào)是偷。
-
循環(huán)時(shí)間長開銷大
自旋CAS如果長時(shí)間不成功,會(huì)給CPU帶來非常大的執(zhí)行開銷募逞。
只能保證一個(gè)共享變量的原子操作
Jdk中原子相關(guān)操作類
- AtomicInteger
- AtomicIntegerArray
- AtomicReference
- AtomicStampedRefrence 利用版本戳形式記錄了每次改變的版本號(hào),避免ABA問題
- AtomicMarkableReference 帶有標(biāo)記位的引用類型
- AtomicIntegerFieldUpdater
- AtomicLongFieldUpdater
- AtomicReferenceFieldUpdater
顯示鎖
synchronized關(guān)鍵字會(huì)隱式的獲取鎖馋评,它將鎖的獲取和釋放固化了放接,就是先獲取再釋放。
顯示鎖提供了隱式鎖很多沒有的功能留特,比如嘗試非阻塞的獲取鎖纠脾,能被中斷的獲取鎖,超時(shí)獲取鎖等蜕青。
Lock的標(biāo)準(zhǔn)用法
lock.lock();// 獲取鎖
try{
...
}finally{
lock.unlock();// 釋放鎖
}
在finally語句塊中執(zhí)行unlock()方法苟蹈,保證最終能夠釋放鎖。
Lock的常用API
公平和非公平鎖
先對(duì)鎖獲取的請(qǐng)求一定先被滿足右核,那么這個(gè)鎖就是公平鎖慧脱。也就是等待時(shí)間最長的線程優(yōu)先獲取鎖。反之就是非公平鎖贺喝。非公平鎖效率比公平鎖高菱鸥。
恢復(fù)一個(gè)被掛起的線程與該線程真正開始執(zhí)行之間存在嚴(yán)重的延遲,有可能在這個(gè)延遲時(shí)間段內(nèi)有另外一個(gè)線程獲取到鎖并且執(zhí)行完成躏鱼,被掛起的線程還沒有被喚醒氮采。正因?yàn)檫@個(gè)情況,非公平鎖比公平鎖效率更高染苛。
ReentrantLock可重入鎖
定義:同一個(gè)線程對(duì)于已經(jīng)獲得到的鎖鹊漠,可以多次繼續(xù)申請(qǐng)到該鎖的使用權(quán)。Synchronized隱式的支持可重入茶行。ReentrantLock實(shí)現(xiàn)Lock接口和序列化接口躯概,構(gòu)造函數(shù)傳入ture表示使用公平鎖。默認(rèn)為非公平鎖拢军。
讀寫鎖ReentrantReadWriteLock
排它鎖:在同一時(shí)刻楞陷,只允許一個(gè)線程進(jìn)行訪問。
讀寫鎖在同一個(gè)時(shí)刻可以允許多個(gè)讀線程訪問茉唉。但是寫線程訪問時(shí)固蛾,所有的讀線程和其它線程均被阻塞结执。
維護(hù)了一個(gè)讀鎖和一個(gè)寫鎖,通過分離讀鎖和寫鎖艾凯,提升并發(fā)性献幔。
Condition接口
Condition 接口提供 了類似 Object 的監(jiān)視器方法,與 Lock 配合可以實(shí)現(xiàn)等待/通知模式趾诗。
使用范式:
Lock lock = new ReentrantLock();
Condition condition = lock.newCondition();
public void conditionWaiter() throws InterruptedException{
lock.lock();
try {
condition.await();
} finally {
lock.unlock();
}
}
public void conditionSignal() {
lock.lock();
try {
condition.signal();
}finally {
lock.unlock();
}
}