并發(fā)編程的優(yōu)點(diǎn)和缺點(diǎn)
優(yōu)點(diǎn):
提升性能
將多核CPU的計(jì)算能力發(fā)揮到機(jī)制,性能得到提升
業(yè)務(wù)適用
并行計(jì)算會(huì)比串行計(jì)算更適應(yīng)業(yè)務(wù)需求纷捞,而并發(fā)編程更適用于該模型
缺點(diǎn):
頻繁上線文切換
前提是:線程數(shù)量 > CPU核數(shù)
線程安全
數(shù)據(jù)的可見性、原子性、有序性
無(wú)鎖并發(fā)編程
Runnable和Callable的區(qū)別
- Runnable從JDK1.1就有了,而Callble是JDK1.5之后增加的
- Callable執(zhí)行方法是call()萤皂,Runnable執(zhí)行方法是run()
- Callable任務(wù)執(zhí)行后可返回值,而Runnable任務(wù)是不能返回值(void)
- call方法可以拋出異常匣椰,run方法不可以
- 運(yùn)行Callable任務(wù)可拿到Future對(duì)象裆熙,即異步計(jì)算結(jié)果:
- 它提供了檢查計(jì)算是否完成的方法,以等待計(jì)算的完成禽笑,并檢索計(jì)算的結(jié)果
- 通過(guò)Future對(duì)象可以了解任務(wù)執(zhí)行情況入录,可取消任務(wù)的執(zhí)行,還可以獲取執(zhí)行結(jié)果
- 使用線程池方式運(yùn)行:
- Runnable用ExecutorService的execute方法
- Callable使用submit方法
為什么使用鎖
并發(fā)操作同一資源
資源競(jìng)爭(zhēng)佳镜、保護(hù)
資源胡釵
并發(fā)編程的產(chǎn)生
Java中鎖的種類
- synchronized
- synchronized關(guān)鍵字僚稿,語(yǔ)義層面的定義及使用
- 隱式地獲取鎖,將鎖的獲取和釋放固化蟀伸,即先獲取在釋放當(dāng)前鎖
- Lock(JDK1.5)
- 對(duì)比:
- 非阻塞方式獲取鎖
- 獲取鎖喉可被中斷
- 獲取鎖設(shè)置超時(shí)機(jī)制
synchronize的用法
Java對(duì)象的組成
synchronize 鎖升級(jí)
偏向鎖即有一個(gè)線程A訪問(wèn)帶有鎖的同步代碼塊時(shí)蚀同,會(huì)在對(duì)象頭的mark word記錄線程的ID缅刽,當(dāng)有線程訪問(wèn)同步塊時(shí),會(huì)比較mark word中的線程id蠢络,和當(dāng)前線程是否一致衰猛,如果還是線程A則直接獲取到鎖,當(dāng)有一個(gè)B訪問(wèn)時(shí)刹孔,會(huì)嘗試CAS將對(duì)象頭中的mark wor替換為指向鎖記錄指針啡省,如果替換成功,則當(dāng)前線程獲得到鎖髓霞,如果替換失敗則說(shuō)明有其他線程在競(jìng)爭(zhēng)鎖卦睹,當(dāng)前線程使用自旋來(lái)獲取鎖,當(dāng)自旋次數(shù)達(dá)到一定次數(shù)時(shí)方库,就會(huì)升級(jí)成為重量級(jí)鎖分预,由操作系統(tǒng)Mutexlock實(shí)現(xiàn)線程間的切換
輕量鎖就是偏向鎖+自旋
? 自旋的次數(shù)可以用個(gè)jvm參數(shù)設(shè)置,但是在jdk1.7之后薪捍,就不建議修改這個(gè)參數(shù)了,因?yàn)槌绦蜻M(jìn)行了優(yōu)化配喳,鎖自動(dòng)優(yōu)化酪穿,鎖彈性升級(jí),它自動(dòng)判斷大部分情況需要多少自旋次數(shù)能解決問(wèn)題晴裹,不斷升級(jí)自旋次數(shù)
Lock
- Lock概念
- 是一個(gè)接口被济,它定義和規(guī)范了"獲取和釋放鎖",Lock接口的實(shí)現(xiàn)基本都是通過(guò)聚合了一個(gè)同步器的子類來(lái)完成線程訪問(wèn)控制的
- Lock接口方法
- lock() 獲取鎖
- lockInterruptibly() 可中斷地進(jìn)行獲取鎖
- tryLock() 非阻塞地嘗試獲取鎖
- tryLock(long time, TimeUnit unit) 有超時(shí)時(shí)間獲取鎖
- unlock() 釋放鎖
- Condition newCondition獲取等待通知組件涧团,該組件與當(dāng)前鎖綁定只磷,只有當(dāng)前線程獲取鎖才可以使用組件await(),await()調(diào)用后釋放鎖
import java.util.concurrent.TimeUnit;
import java.util.concurrent.locks.Condition;
import java.util.concurrent.locks.Lock;
public class TestLock implements Lock {
// 獲取鎖
public void lock() {
}
// 可中斷地進(jìn)行獲取鎖
public void lockInterruptibly() throws InterruptedException {
}
// 非阻塞地嘗試獲取鎖
public boolean tryLock() {
return false;
}
// 有超時(shí)時(shí)間獲取鎖
public boolean tryLock(long time, TimeUnit unit) throws InterruptedException {
return false;
}
// 釋放鎖
public void unlock() {
}
// 獲取等待通知組件泌绣,該組件與當(dāng)前鎖綁定钮追,只有當(dāng)前線程獲取鎖才可以使用組件await(),await()調(diào)用后釋放鎖
public Condition newCondition() {
return null;
}
}
Condition
**await(): **造成當(dāng)前線程在接收到信號(hào)或被中斷之前一直處于等待狀態(tài)阿迈。
**await(long time, TimeUnit unit): **造成當(dāng)前線程在接收到信號(hào)元媚、被中斷或達(dá)到指定等待時(shí)間之前一直處于等待狀態(tài)。
**long awaitNanos(long nanosTimeout): **造成當(dāng)前線程在接收到信號(hào)苗沧、被中斷或達(dá)到指定等待時(shí)間之前一直處于等待狀態(tài)刊棕。返回值表示剩余時(shí)間,如果在nanosTimeout之前喚醒待逞,那么返回值= nanosTimeout - 消耗時(shí)間甥角,如果返回值 <= 0,則可以認(rèn)定它已經(jīng)超時(shí)了识樱。
-
**awaitUninterruptibly(): **造成當(dāng)前線程在接收到信號(hào)之前一直處于等待狀態(tài)嗤无。
- 注意:該方法對(duì)中斷不敏感震束。
**boolean awaitUntil(Date deadline): **造成當(dāng)前線程在接收到信號(hào)、被中斷或到達(dá)指定最后期限之前一直處于等待狀態(tài)翁巍。如果沒有到指定時(shí)間就被通知驴一,則返回true,否則表示到了指定時(shí)間灶壶,返回false肝断。
**void signal(): **喚醒一個(gè)等待線程。該線程從等待放放返回前必須獲得與Condition相關(guān)的鎖驰凛。
**void signalAll(): **喚醒所有等待線程胸懈,能夠從等待放放返回的線程,必須獲取與Condition相關(guān)的鎖
import java.util.concurrent.locks.Condition;
import java.util.concurrent.locks.ReentrantLock;
public class TestCondition {
public static void main(String[] args) {
final ReentrantLock reentrantLock = new ReentrantLock();
final Condition condition = reentrantLock.newCondition();
new Thread(() -> {
reentrantLock.lock();
System.out.println(Thread.currentThread().getName() + "拿到了鎖");
System.out.println(Thread.currentThread().getName() + "等待信號(hào)");
try {
condition.await();
} catch (InterruptedException e) {
e.printStackTrace();
}
System.out.println(Thread.currentThread().getName() + "拿到信號(hào)");
reentrantLock.unlock();
}, "線程1").start();
new Thread(() -> {
reentrantLock.lock();
System.out.println(Thread.currentThread().getName() + "拿到了鎖");
try {
Thread.sleep(1000);
} catch (InterruptedException e) {
e.printStackTrace();
}
System.out.println(Thread.currentThread().getName() + "發(fā)出信號(hào)");
condition.signalAll();
reentrantLock.unlock();
}, "線程2").start();
}
}
AQS 隊(duì)列同步器
- AQS:AbstractQueuedSynchronizer 隊(duì)列同步器
- 用來(lái)構(gòu)建鎖或者其他同步組件的基礎(chǔ)框架
- 用int成員變量表示同步狀態(tài)
- 通過(guò)內(nèi)置的FIFO(先進(jìn)先出)隊(duì)列來(lái)完成資源獲取線程的排隊(duì)工作
- 大部分同步需求的基礎(chǔ)
- 同步狀態(tài):
- getState()
- setSate(int newState)
- compareAndSetState(int expect, int update)
- 鎖是面向使用者的恰响,它定義了使用者與鎖交互的接口(比如可以允許兩個(gè)線程并行訪問(wèn))趣钱,隱藏了實(shí)現(xiàn)細(xì)節(jié)
- 同步器(AQS)面向的是鎖的創(chuàng)造者OR開發(fā)者,它提供了便捷的工具和方法胚宦,將同步狀態(tài)首有,線程排隊(duì),等待及喚醒等底層操作封裝起來(lái)枢劝,讓開發(fā)者使用更便捷
- 鎖和同步器很多地隔離了使用者和實(shí)現(xiàn)者所需關(guān)注的范圍及各自關(guān)注的邏輯細(xì)節(jié)
volatile
- 保證不同線程對(duì)統(tǒng)一變量操作時(shí)的可見性
- 禁止指令重排序
MESI協(xié)議
- 將當(dāng)前處理器緩存行的數(shù)據(jù)寫會(huì)系統(tǒng)內(nèi)存
- 這個(gè)寫回內(nèi)存的操作會(huì)使其他CPU里緩存了該內(nèi)存地址的數(shù)據(jù)無(wú)效
- volatile下的MESI:
- Lock前綴的指令會(huì)引起處理器緩存寫回內(nèi)存
- 一個(gè)處理器的緩存回寫到內(nèi)存會(huì)導(dǎo)致其他處理器的緩存失效
- 當(dāng)處理器發(fā)現(xiàn)本地緩存失效后井联,就會(huì)從內(nèi)存中重讀該變量數(shù)據(jù),既可以獲取當(dāng)前最新值
AQS實(shí)現(xiàn)原理
- 同步器依賴內(nèi)部的隊(duì)列完成同步狀態(tài)的管理您旁,當(dāng)線程獲取同步狀態(tài)失敗時(shí)烙常,同步器會(huì)將當(dāng)前線程與等待狀態(tài)等信息構(gòu)造成為一個(gè)節(jié)點(diǎn)(Node)將其放入同步隊(duì)列,同時(shí)會(huì)阻塞當(dāng)前線程鹤盒。同步狀態(tài)釋放時(shí)蚕脏,喚醒首節(jié)點(diǎn)中的線程,使其再次嘗試獲取同步狀態(tài)侦锯,
- 主要包括:
- 同步隊(duì)列
- 獨(dú)占式同步狀態(tài)獲取與釋放
- 共享式同步狀態(tài)獲取與釋放
- 超時(shí)獲取同步狀態(tài)等同步器的核心數(shù)據(jù)結(jié)構(gòu)與模板方法
同步隊(duì)列
-
同步隊(duì)列中的節(jié)點(diǎn)(Node):
- 保存獲取同步狀態(tài)失敗線程的引用驼鞭,等待狀態(tài)以及前置和后置節(jié)點(diǎn)
-
包含的字段:
- prev 前置節(jié)點(diǎn)
- next 后置節(jié)點(diǎn)
- thread 獲取同步狀態(tài)的線程
- waitStatus 等待狀態(tài)
- nextWaiter 等待隊(duì)列中的后置節(jié)點(diǎn)