线程执行完之后会释放吗_java多线程并发:CAS+AQS+HashMap+volatile+ThreadLocal,乐分享...
CyclicBarrier、CountDownLatch、Semaphore 的用法
CountDownLatch(線程計(jì)數(shù)器 )
CountDownLatch 類位于 java.util.concurrent 包下,利用它可以實(shí)現(xiàn)類似計(jì)數(shù)器的功能。比如有一個(gè)任務(wù) A,它要等待其他 4 個(gè)任務(wù)執(zhí)行完畢之后才能執(zhí)行,此時(shí)就可以利用 CountDownLatch來實(shí)現(xiàn)這種功能了。
final CountDownLatch latch = new CountDownLatch(2);new Thread(){public void run() {
System.out.println("子線程"+Thread.currentThread().getName()+"正在執(zhí)行");
Thread.sleep(3000);
System.out.println("子線程"+Thread.currentThread().getName()+"執(zhí)行完畢");
latch.countDown();
};}.start();
new Thread(){ public void run() {
System.out.println("子線程"+Thread.currentThread().getName()+"正在執(zhí)行");
Thread.sleep(3000);
System.out.println("子線程"+Thread.currentThread().getName()+"執(zhí)行完畢");
latch.countDown();
};}.start();
System.out.println("等待 2 個(gè)子線程執(zhí)行完畢...");
latch.await();
System.out.println("2 個(gè)子線程已經(jīng)執(zhí)行完畢");
System.out.println("繼續(xù)執(zhí)行主線程");
}
CyclicBarrier(回環(huán)柵欄-等待至 barrier 狀態(tài)再全部同時(shí)執(zhí)行)
字面意思回環(huán)柵欄,通過它可以實(shí)現(xiàn)讓一組線程等待至某個(gè)狀態(tài)之后再全部同時(shí)執(zhí)行。叫做回環(huán)是因?yàn)楫?dāng)所有等待線程都被釋放以后,CyclicBarrier 可以被重用。我們暫且把這個(gè)狀態(tài)就叫做barrier,當(dāng)調(diào)用 await()方法之后,線程就處于 barrier 了。
CyclicBarrier 中最重要的方法就是 await 方法,它有 2 個(gè)重載版本:
1. public int await():用來掛起當(dāng)前線程,直至所有線程都到達(dá) barrier 狀態(tài)再同時(shí)執(zhí)行后續(xù)任務(wù);
2. public int await(long timeout, TimeUnit unit):讓這些線程等待至一定的時(shí)間,如果還有線程沒有到達(dá) barrier 狀態(tài)就直接讓到達(dá) barrier 的線程執(zhí)行后續(xù)任務(wù)。
具體使用如下,另外 CyclicBarrier 是可以重用的。
public static void main(String[] args) {int N = 4;
CyclicBarrier barrier = new CyclicBarrier(N);
for(int i=0;inew Writer(barrier).start();
}
static class Writer extends Thread{
private CyclicBarrier cyclicBarrier;
public Writer(CyclicBarrier cyclicBarrier) {
this.cyclicBarrier = cyclicBarrier;
}
@Override
public void run() {
try {
Thread.sleep(5000); //以睡眠來模擬線程需要預(yù)定寫入數(shù)據(jù)操作
System.out.println("線程"+Thread.currentThread().getName()+"寫入數(shù)據(jù)完
畢,等待其他線程寫入完畢");
cyclicBarrier.await();
} catch (InterruptedException e) {
e.printStackTrace();
}catch(BrokenBarrierException e){
e.printStackTrace();
}
System.out.println("所有線程寫入完畢,繼續(xù)處理其他任務(wù),比如數(shù)據(jù)操作");
}
}
Semaphore(信號(hào)量-控制同時(shí)訪問的線程個(gè)數(shù))
Semaphore 翻譯成字面意思為 信號(hào)量,Semaphore 可以控制同時(shí)訪問的線程個(gè)數(shù),通過acquire() 獲取一個(gè)許可,如果沒有就等待,而 release() 釋放一個(gè)許可。
Semaphore 類中比較重要的幾個(gè)方法:
1. public void acquire(): 用來獲取一個(gè)許可,若無許可能夠獲得,則會(huì)一直等待,直到獲得許可。
2. public void acquire(int permits):獲取 permits 個(gè)許可
3. public void release() { } :釋放許可。注意,在釋放許可之前,必須先獲獲得許可。
4. public void release(int permits) { }:釋放 permits 個(gè)許可。
上面 4 個(gè)方法都會(huì)被阻塞,如果想立即得到執(zhí)行結(jié)果,可以使用下面幾個(gè)方法
1. public boolean tryAcquire():嘗試獲取一個(gè)許可,若獲取成功,則立即返回 true,若獲取失敗,則立即返回 false
2. public boolean tryAcquire(long timeout, TimeUnit unit):嘗試獲取一個(gè)許可,若在指定的時(shí)間內(nèi)獲取成功,則立即返回 true,否則則立即返回 false。
3. public boolean tryAcquire(int permits):嘗試獲取 permits 個(gè)許可,若獲取成功,則立即返
回 true,若獲取失敗,則立即返回 false。
4. public boolean tryAcquire(int permits, long timeout, TimeUnit unit): 嘗試獲取 permits個(gè)許可,若在指定的時(shí)間內(nèi)獲取成功,則立即返回 true,否則則立即返回 false。
5. 還可以通過 availablePermits()方法得到可用的許可數(shù)目。
例子:若一個(gè)工廠有 5 臺(tái)機(jī)器,但是有 8 個(gè)工人,一臺(tái)機(jī)器同時(shí)只能被一個(gè)工人使用,只有使用完了,其他工人才能繼續(xù)使用。那么我們就可以通過 Semaphore 來實(shí)現(xiàn):
int N = 8; //工人數(shù)Semaphore semaphore = new Semaphore(5); //機(jī)器數(shù)目
for(int i=0;inew Worker(i,semaphore).start();
}
static class Worker extends Thread{
private int num;
private Semaphore semaphore;
public Worker(int num,Semaphore semaphore){
this.num = num;
this.semaphore = semaphore;
}
@Override
public void run() {
try {
semaphore.acquire();
System.out.println("工人"+this.num+"占用一個(gè)機(jī)器在生產(chǎn)...");
Thread.sleep(2000);
System.out.println("工人"+this.num+"釋放出機(jī)器");
semaphore.release();
} catch (InterruptedException e) {
e.printStackTrace();
}
}
? CountDownLatch 和 CyclicBarrier 都能夠?qū)崿F(xiàn)線程之間的等待,只不過它們側(cè)重點(diǎn)不同;CountDownLatch 一般用于某個(gè)線程 A 等待若干個(gè)其他線程執(zhí)行完任務(wù)之后,它才執(zhí)行;而 CyclicBarrier 一般用于一組線程互相等待至某個(gè)狀態(tài),然后這一組線程再同時(shí)執(zhí)行;另外,CountDownLatch 是不能夠重用的,而 CyclicBarrier 是可以重用的。
? Semaphore 其實(shí)和鎖有點(diǎn)類似,它一般用于控制對(duì)某組資源的訪問權(quán)限。
volatile 關(guān)鍵字的作用(變量可見性、禁止重排序)
Java 語言提供了一種稍弱的同步機(jī)制,即 volatile 變量,用來確保將變量的更新操作通知到其他線程。volatile 變量具備兩種特性,volatile 變量不會(huì)被緩存在寄存器或者對(duì)其他處理器不可見的地方,因此在讀取 volatile 類型的變量時(shí)總會(huì)返回最新寫入的值。
變量可見性
其一是保證該變量對(duì)所有線程可見,這里的可見性指的是當(dāng)一個(gè)線程修改了變量的值,那么新的值對(duì)于其他線程是可以立即獲取的。
禁止重排序
volatile 禁止了指令重排。
比 sychronized 更輕量級(jí)的同步鎖
在訪問 volatile 變量時(shí)不會(huì)執(zhí)行加鎖操作,因此也就不會(huì)使執(zhí)行線程阻塞,因此 volatile 變量是一種比 sychronized 關(guān)鍵字更輕量級(jí)的同步機(jī)制。volatile 適合這種場(chǎng)景:一個(gè)變量被多個(gè)線程共享,線程直接給這個(gè)變量賦值。
當(dāng)對(duì)非 volatile 變量進(jìn)行讀寫的時(shí)候,每個(gè)線程先從內(nèi)存拷貝變量到 CPU 緩存中。如果計(jì)算機(jī)有多個(gè) CPU,每個(gè)線程可能在不同的 CPU 上被處理,這意味著每個(gè)線程可以拷貝到不同的 CPUcache 中。而聲明變量是 volatile 的,JVM 保證了每次讀變量都從內(nèi)存中讀,跳過 CPU cache這一步。
適用場(chǎng)景
值得說明的是對(duì) volatile 變量的單次讀/寫操作可以保證原子性的,如 long 和 double 類型變量,但是并不能保證 i++這種操作的原子性,因?yàn)楸举|(zhì)上 i++是讀、寫兩次操作。在某些場(chǎng)景下可以代替 Synchronized。但是,volatile 的不能完全取代 Synchronized 的位置,只有在一些特殊的場(chǎng)景下,才能適用 volatile。總的來說,必須同時(shí)滿足下面兩個(gè)條件才能保證在并發(fā)環(huán)境的線程安全:
(1)對(duì)變量的寫操作不依賴于當(dāng)前值(比如 i++),或者說是單純的變量賦值(booleanflag = true)。
(2)該變量沒有包含在具有其他變量的不變式中,也就是說,不同的 volatile 變量之間,不能互相依賴。只有在狀態(tài)真正獨(dú)立于程序內(nèi)其他內(nèi)容時(shí)才能使用 volatile。
如何在兩個(gè)線程之間共享數(shù)據(jù)
Java 里面進(jìn)行多線程通信的主要方式就是共享內(nèi)存的方式,共享內(nèi)存主要的關(guān)注點(diǎn)有兩個(gè):可見性和有序性原子性。Java 內(nèi)存模型(JMM)解決了可見性和有序性的問題,而鎖解決了原子性的問題,理想情況下我們希望做到“同步”和“互斥”。有以下常規(guī)實(shí)現(xiàn)方法:
將數(shù)據(jù)抽象成一個(gè)類,并將數(shù)據(jù)的操作作為這個(gè)類的方法
1. 將數(shù)據(jù)抽象成一個(gè)類,并將對(duì)這個(gè)數(shù)據(jù)的操作作為這個(gè)類的方法,這么設(shè)計(jì)可以和容易做到同步,只要在方法上加”synchronized“
public class MyData {private int j=0;
public synchronized void add(){
j++;
System.out.println("線程"+Thread.currentThread().getName()+"j 為:"+j);
}
public synchronized void dec(){
j--;
System.out.println("線程"+Thread.currentThread().getName()+"j 為:"+j);
}
public int getData(){
return j;
}
}
public class AddRunnable implements Runnable{
MyData data;
public AddRunnable(MyData data){
this.data= data;
}public void run() {
data.add();
}
}
public class DecRunnable implements Runnable {
MyData data;
public DecRunnable(MyData data){
this.data = data;
}
public void run() {
data.dec();
}
}
public static void main(String[] args) {
MyData data = new MyData();
Runnable add = new AddRunnable(data);
Runnable dec = new DecRunnable(data);
for(int i=0;i<2;i++){
new Thread(add).start();
new Thread(dec).start();
}
Runnable 對(duì)象作為一個(gè)類的內(nèi)部類
2. 將 Runnable 對(duì)象作為一個(gè)類的內(nèi)部類,共享數(shù)據(jù)作為這個(gè)類的成員變量,每個(gè)線程對(duì)共享數(shù)據(jù)的操作方法也封裝在外部類,以便實(shí)現(xiàn)對(duì)數(shù)據(jù)的各個(gè)操作的同步和互斥,作為內(nèi)部類的各個(gè) Runnable 對(duì)象調(diào)用外部類的這些方法。
public class MyData {private int j=0;
public synchronized void add(){
j++;
System.out.println("線程"+Thread.currentThread().getName()+"j 為:"+j);
}
public synchronized void dec(){
j--;
System.out.println("線程"+Thread.currentThread().getName()+"j 為:"+j);
}
public int getData(){
return j;
}
}
public class TestThread {
public static void main(String[] args) {
final MyData data = new MyData();
for(int i=0;i<2;i++){
new Thread(new Runnable(){
public void run() {
data.add();
}
}).start();
new Thread(new Runnable(){
public void run() {
data.dec();
}
}).start();
}
}
}
ThreadLocal 作用(線程本地存儲(chǔ))
ThreadLocal,很多地方叫做線程本地變量,也有些地方叫做線程本地存儲(chǔ),ThreadLocal 的作用是提供線程內(nèi)的局部變量,這種變量在線程的生命周期內(nèi)起作用,減少同一個(gè)線程內(nèi)多個(gè)函數(shù)或者組件之間一些公共變量的傳遞的復(fù)雜度。
ThreadLocalMap(線程的一個(gè)屬性)
1. 每個(gè)線程中都有一個(gè)自己的 ThreadLocalMap 類對(duì)象,可以將線程自己的對(duì)象保持到其中,各管各的,線程可以正確的訪問到自己的對(duì)象。
2. 將一個(gè)共用的 ThreadLocal 靜態(tài)實(shí)例作為 key,將不同對(duì)象的引用保存到不同線程的ThreadLocalMap 中,然后在線程執(zhí)行的各處通過這個(gè)靜態(tài) ThreadLocal 實(shí)例的 get()方法取得自己線程保存的那個(gè)對(duì)象,避免了將這個(gè)對(duì)象作為參數(shù)傳遞的麻煩。
3. ThreadLocalMap 其實(shí)就是線程里面的一個(gè)屬性,它在 Thread 類中定義
ThreadLocal.ThreadLocalMap threadLocals = null;
使用場(chǎng)景
最常見的 ThreadLocal 使用場(chǎng)景為 用來解決 數(shù)據(jù)庫(kù)連接、Session 管理等。
private static final ThreadLocal threadSession = new ThreadLocal();public static Session getSession() throws InfrastructureException {
Session s = (Session) threadSession.get();
try {
if (s == null) {
s = getSessionFactory().openSession();
threadSession.set(s);
}
} catch (HibernateException ex) {
throw new InfrastructureException(ex);
}
return s;
}
synchronized 和 ReentrantLock 的區(qū)別
兩者的共同點(diǎn):
1. 都是用來協(xié)調(diào)多線程對(duì)共享對(duì)象、變量的訪問
2. 都是可重入鎖,同一線程可以多次獲得同一個(gè)鎖
3. 都保證了可見性和互斥性
兩者的不同點(diǎn):
1. ReentrantLock 顯示的獲得、釋放鎖,synchronized 隱式獲得釋放鎖
2. ReentrantLock 可響應(yīng)中斷、可輪回,synchronized 是不可以響應(yīng)中斷的,為處理鎖的不可用性提供了更高的靈活性
3. ReentrantLock 是 API 級(jí)別的,synchronized 是 JVM 級(jí)別的
4. ReentrantLock 可以實(shí)現(xiàn)公平鎖
5. ReentrantLock 通過 Condition 可以綁定多個(gè)條件
6. 底層實(shí)現(xiàn)不一樣, synchronized 是同步阻塞,使用的是悲觀并發(fā)策略,lock 是同步非阻塞,采用的是樂觀并發(fā)策略
7. Lock 是一個(gè)接口,而 synchronized 是 Java 中的關(guān)鍵字,synchronized 是內(nèi)置的語言實(shí)現(xiàn)。
8. synchronized 在發(fā)生異常時(shí),會(huì)自動(dòng)釋放線程占有的鎖,因此不會(huì)導(dǎo)致死鎖現(xiàn)象發(fā)生;
而 Lock 在發(fā)生異常時(shí),如果沒有主動(dòng)通過 unLock()去釋放鎖,則很可能造成死鎖現(xiàn)象,因此使用 Lock 時(shí)需要在 finally 塊中釋放鎖。
9. Lock 可以讓等待鎖的線程響應(yīng)中斷,而 synchronized 卻不行,使用 synchronized 時(shí),等待的線程會(huì)一直等待下去,不能夠響應(yīng)中斷。
10. 通過 Lock 可以知道有沒有成功獲取鎖,而 synchronized 卻無法辦到。
11. Lock 可以提高多個(gè)線程進(jìn)行讀操作的效率,既就是實(shí)現(xiàn)讀寫鎖等。
ConcurrentHashMap 并發(fā)
減小鎖粒度
減小鎖粒度是指縮小鎖定對(duì)象的范圍,從而減小鎖沖突的可能性,從而提高系統(tǒng)的并發(fā)能力。減小鎖粒度是一種削弱多線程鎖競(jìng)爭(zhēng)的有效手段,這種技術(shù)典型的應(yīng)用是 ConcurrentHashMap(高性能的 HashMap)類的實(shí)現(xiàn)。對(duì)于 HashMap 而言,最重要的兩個(gè)方法是 get 與 set 方法,如果我們對(duì)整個(gè) HashMap 加鎖,可以得到線程安全的對(duì)象,但是加鎖力度太大。Segment 的大小也被稱為 ConcurrentHashMap 的并發(fā)度。
ConcurrentHashMap 分段鎖
ConcurrentHashMap,它內(nèi)部細(xì)分了若干個(gè)小的 HashMap,稱之為段(Segment)。默認(rèn)情況下一個(gè) ConcurrentHashMap 被進(jìn)一步細(xì)分為 16 個(gè)段,既就是鎖的并發(fā)度。
如果需要在 ConcurrentHashMap 中添加一個(gè)新的表項(xiàng),并不是將整個(gè) HashMap 加鎖,而是首先根據(jù) hashcode 得到該表項(xiàng)應(yīng)該存放在哪個(gè)段中,然后對(duì)該段加鎖,并完成 put 操作。在多線程環(huán)境中,如果多個(gè)線程同時(shí)進(jìn)行 put操作,只要被加入的表項(xiàng)不存放在同一個(gè)段中,則線程間可以做到真正的并行。
ConcurrentHashMap 是由 Segment 數(shù)組結(jié)構(gòu)和 HashEntry 數(shù)組結(jié)構(gòu)組成
ConcurrentHashMap 是由 Segment 數(shù)組結(jié)構(gòu)和 HashEntry 數(shù)組結(jié)構(gòu)組成。Segment 是一種可重入鎖 ReentrantLock,在 ConcurrentHashMap 里扮演鎖的角色,HashEntry 則用于存儲(chǔ)鍵值對(duì)數(shù)據(jù)。一個(gè) ConcurrentHashMap 里包含一個(gè) Segment 數(shù)組,Segment 的結(jié)構(gòu)和 HashMap類似,是一種數(shù)組和鏈表結(jié)構(gòu), 一個(gè) Segment 里包含一個(gè) HashEntry 數(shù)組,每個(gè) HashEntry 是一個(gè)鏈表結(jié)構(gòu)的元素, 每個(gè) Segment 守護(hù)一個(gè) HashEntry 數(shù)組里的元素,當(dāng)對(duì) HashEntry 數(shù)組的數(shù)據(jù)進(jìn)行修改時(shí),必須首先獲得它對(duì)應(yīng)的 Segment 鎖。
Java 中用到的線程調(diào)度
搶占式調(diào)度:
搶占式調(diào)度指的是每條線程執(zhí)行的時(shí)間、線程的切換都由系統(tǒng)控制,系統(tǒng)控制指的是在系統(tǒng)某種運(yùn)行機(jī)制下,可能每條線程都分同樣的執(zhí)行時(shí)間片,也可能是某些線程執(zhí)行的時(shí)間片較長(zhǎng),甚至某些線程得不到執(zhí)行的時(shí)間片。在這種機(jī)制下,一個(gè)線程的堵塞不會(huì)導(dǎo)致整個(gè)進(jìn)程堵塞。
協(xié)同式調(diào)度:
協(xié)同式調(diào)度指某一線程執(zhí)行完后主動(dòng)通知系統(tǒng)切換到另一線程上執(zhí)行,這種模式就像接力賽一樣,一個(gè)人跑完自己的路程就把接力棒交接給下一個(gè)人,下個(gè)人繼續(xù)往下跑。線程的執(zhí)行時(shí)間由線程本身控制,線程切換可以預(yù)知,不存在多線程同步問題,但它有一個(gè)致命弱點(diǎn):如果一個(gè)線程編寫有問題,運(yùn)行到一半就一直堵塞,那么可能導(dǎo)致整個(gè)系統(tǒng)崩潰。
JVM 的線程調(diào)度實(shí)現(xiàn)(搶占式調(diào)度)
java 使用的線程調(diào)使用搶占式調(diào)度,Java 中線程會(huì)按優(yōu)先級(jí)分配 CPU 時(shí)間片運(yùn)行,且優(yōu)先級(jí)越高越優(yōu)先執(zhí)行,但優(yōu)先級(jí)高并不代表能獨(dú)自占用執(zhí)行時(shí)間片,可能是優(yōu)先級(jí)高得到越多的執(zhí)行時(shí)間片,反之,優(yōu)先級(jí)低的分到的執(zhí)行時(shí)間少但不會(huì)分配不到執(zhí)行時(shí)間。
線程讓出 cpu 的情況:
1. 當(dāng)前運(yùn)行線程主動(dòng)放棄 CPU,JVM 暫時(shí)放棄 CPU 操作(基于時(shí)間片輪轉(zhuǎn)調(diào)度的 JVM 操作系統(tǒng)不會(huì)讓線程永久放棄 CPU,或者說放棄本次時(shí)間片的執(zhí)行權(quán)),例如調(diào)用 yield()方法。
2. 當(dāng)前運(yùn)行線程因?yàn)槟承┰蜻M(jìn)入阻塞狀態(tài),例如阻塞在 I/O 上。
3. 當(dāng)前運(yùn)行線程結(jié)束,即運(yùn)行完 run()方法里面的任務(wù)。
進(jìn)程調(diào)度算法
優(yōu)先調(diào)度算法
1. 先來先服務(wù)調(diào)度算法(FCFS)
當(dāng)在作業(yè)調(diào)度中采用該算法時(shí),每次調(diào)度都是從后備作業(yè)隊(duì)列中選擇一個(gè)或多個(gè)最先進(jìn)入該隊(duì)列的作業(yè),將它們調(diào)入內(nèi)存,為它們分配資源、創(chuàng)建進(jìn)程,然后放入就緒隊(duì)列。在進(jìn)程調(diào)度中采用 FCFS 算法時(shí),則每次調(diào)度是從就緒隊(duì)列中選擇一個(gè)最先進(jìn)入該隊(duì)列的進(jìn)程,為之分配處理機(jī),使之投入運(yùn)行。該進(jìn)程一直運(yùn)行到完成或發(fā)生某事件而阻塞后才放棄處理機(jī),特點(diǎn)是:算法比較簡(jiǎn)單,可以實(shí)現(xiàn)基本上的公平。
2. 短作業(yè)(進(jìn)程)優(yōu)先調(diào)度算法
短作業(yè)優(yōu)先(SJF)的調(diào)度算法是從后備隊(duì)列中選擇一個(gè)或若干個(gè)估計(jì)運(yùn)行時(shí)間最短的作業(yè),將它們調(diào)入內(nèi)存運(yùn)行。而短進(jìn)程優(yōu)先(SPF)調(diào)度算法則是從就緒隊(duì)列中選出一個(gè)估計(jì)運(yùn)行時(shí)間最短的進(jìn)程,將處理機(jī)分配給它,使它立即執(zhí)行并一直執(zhí)行到完成,或發(fā)生某事件而被阻塞放棄處理機(jī)時(shí)再重新調(diào)度。該算法未照顧緊迫型作業(yè)。
高優(yōu)先權(quán)優(yōu)先調(diào)度算法
為了照顧緊迫型作業(yè),使之在進(jìn)入系統(tǒng)后便獲得優(yōu)先處理,引入了最高優(yōu)先權(quán)優(yōu)先(FPF)調(diào)度算法。當(dāng)把該算法用于作業(yè)調(diào)度時(shí),系統(tǒng)將從后備隊(duì)列中選擇若干個(gè)優(yōu)先權(quán)最高的作業(yè)裝入內(nèi)存。
當(dāng)用于進(jìn)程調(diào)度時(shí),該算法是把處理機(jī)分配給就緒隊(duì)列中優(yōu)先權(quán)最高的進(jìn)程。
1. 非搶占式優(yōu)先權(quán)算法
在這種方式下,系統(tǒng)一旦把處理機(jī)分配給就緒隊(duì)列中優(yōu)先權(quán)最高的進(jìn)程后,該進(jìn)程便一直執(zhí)行下去,直至完成;或因發(fā)生某事件使該進(jìn)程放棄處理機(jī)時(shí)。這種調(diào)度算法主要用于批處理系統(tǒng)中;也可用于某些對(duì)實(shí)時(shí)性要求不嚴(yán)的實(shí)時(shí)系統(tǒng)中。
2. 搶占式優(yōu)先權(quán)調(diào)度算法
在這種方式下,系統(tǒng)同樣是把處理機(jī)分配給優(yōu)先權(quán)最高的進(jìn)程,使之執(zhí)行。但在其執(zhí)行期間,只要又出現(xiàn)了另一個(gè)其優(yōu)先權(quán)更高的進(jìn)程,進(jìn)程調(diào)度程序就立即停止當(dāng)前進(jìn)程(原優(yōu)先權(quán)最高的進(jìn)程)的執(zhí)行,重新將處理機(jī)分配給新到的優(yōu)先權(quán)最高的進(jìn)程。顯然,這種搶占式的優(yōu)先權(quán)調(diào)度算法能更好地滿足緊迫作業(yè)的要求,故而常用于要求比較嚴(yán)格的實(shí)時(shí)系統(tǒng)中,以及對(duì)性能要求較高的批處理和分時(shí)系統(tǒng)中。
2.高響應(yīng)比優(yōu)先調(diào)度算法
在批處理系統(tǒng)中,短作業(yè)優(yōu)先算法是一種比較好的算法,其主要的不足之處是長(zhǎng)作業(yè)的運(yùn)行得不到保證。如果我們能為每個(gè)作業(yè)引入前面所述的動(dòng)態(tài)優(yōu)先權(quán),并使作業(yè)的優(yōu)先級(jí)隨著等待時(shí)間的增加而以速率 a 提高,則長(zhǎng)作業(yè)在等待一定的時(shí)間后,必然有機(jī)會(huì)分配到處理機(jī)。該優(yōu)先權(quán)的變化規(guī)律可描述為:
(1) 如果作業(yè)的等待時(shí)間相同,則要求服務(wù)的時(shí)間愈短,其優(yōu)先權(quán)愈高,因而該算法有利于短作業(yè)。
(2) 當(dāng)要求服務(wù)的時(shí)間相同時(shí),作業(yè)的優(yōu)先權(quán)決定于其等待時(shí)間,等待時(shí)間愈長(zhǎng),其優(yōu)先權(quán)愈高,因而實(shí)現(xiàn)的是先來先服務(wù)
(3) 對(duì)于長(zhǎng)作業(yè),作業(yè)的優(yōu)先級(jí)可以隨等待時(shí)間的增加而提高,當(dāng)其等待時(shí)間足夠長(zhǎng)時(shí),其優(yōu)先級(jí)便可升到很高,從而也可獲得處理機(jī)。簡(jiǎn)言之,該算法既照顧了短作業(yè),又考慮了作業(yè)到達(dá)的先后次序,不會(huì)使長(zhǎng)作業(yè)長(zhǎng)期得不到服務(wù)。因此,該算法實(shí)現(xiàn)了一種較好的折衷。當(dāng)然,在利用該算法時(shí),每要進(jìn)行調(diào)度之前,都須先做響應(yīng)比的計(jì)算,這會(huì)增加系統(tǒng)開銷。
基于時(shí)間片的輪轉(zhuǎn)調(diào)度算法
1. 時(shí)間片輪轉(zhuǎn)法
在早期的時(shí)間片輪轉(zhuǎn)法中,系統(tǒng)將所有的就緒進(jìn)程按先來先服務(wù)的原則排成一個(gè)隊(duì)列,每次調(diào)度時(shí),把 CPU 分配給隊(duì)首進(jìn)程,并令其執(zhí)行一個(gè)時(shí)間片。時(shí)間片的大小從幾 ms 到幾百 ms。當(dāng)執(zhí)行的時(shí)間片用完時(shí),由一個(gè)計(jì)時(shí)器發(fā)出時(shí)鐘中斷請(qǐng)求,調(diào)度程序便據(jù)此信號(hào)來停止該進(jìn)程的執(zhí)行,并將它送往就緒隊(duì)列的末尾;然后,再把處理機(jī)分配給就緒隊(duì)列中新的隊(duì)首進(jìn)程,同時(shí)也讓它執(zhí)行一個(gè)時(shí)間片。這樣就可以保證就緒隊(duì)列中的所有進(jìn)程在一給定的時(shí)間內(nèi)均能獲得一時(shí)間片的處理機(jī)執(zhí)行時(shí)間。
2. 多級(jí)反饋隊(duì)列調(diào)度算法
(1) 應(yīng)設(shè)置多個(gè)就緒隊(duì)列,并為各個(gè)隊(duì)列賦予不同的優(yōu)先級(jí)。第一個(gè)隊(duì)列的優(yōu)先級(jí)最高,第二個(gè)隊(duì)列次之,其余各隊(duì)列的優(yōu)先權(quán)逐個(gè)降低。該算法賦予各個(gè)隊(duì)列中進(jìn)程執(zhí)行時(shí)間片的大小也各不相同,在優(yōu)先權(quán)愈高的隊(duì)列中,為每個(gè)進(jìn)程所規(guī)定的執(zhí)行時(shí)間片就愈小。例如,第二個(gè)隊(duì)列的時(shí)間片要比第一個(gè)隊(duì)列的時(shí)間片長(zhǎng)一倍,……,第 i+1 個(gè)隊(duì)列的時(shí)間片要比第 i 個(gè)隊(duì)列的時(shí)間片長(zhǎng)一倍。
(2) 當(dāng)一個(gè)新進(jìn)程進(jìn)入內(nèi)存后,首先將它放入第一隊(duì)列的末尾,按 FCFS 原則排隊(duì)等待調(diào)度。當(dāng)輪到該進(jìn)程執(zhí)行時(shí),如它能在該時(shí)間片內(nèi)完成,便可準(zhǔn)備撤離系統(tǒng);如果它在一個(gè)時(shí)間片結(jié)束時(shí)尚未完成,調(diào)度程序便將該進(jìn)程轉(zhuǎn)入第二隊(duì)列的末尾,再同樣地按 FCFS 原則等待調(diào)度執(zhí)行;如果它在第二隊(duì)列中運(yùn)行一個(gè)時(shí)間片后仍未完成,再依次將它放入第三隊(duì)列,……,如此下去,當(dāng)一個(gè)長(zhǎng)作業(yè)(進(jìn)程)從第一隊(duì)列依次降到第 n 隊(duì)列后,在第 n 隊(duì)列便采取按時(shí)間片輪轉(zhuǎn)的方式運(yùn)行。
(3) 僅當(dāng)?shù)谝魂?duì)列空閑時(shí),調(diào)度程序才調(diào)度第二隊(duì)列中的進(jìn)程運(yùn)行;僅當(dāng)?shù)?1~(i-1)隊(duì)列均空時(shí),才會(huì)調(diào)度第 i 隊(duì)列中的進(jìn)程運(yùn)行。如果處理機(jī)正在第 i 隊(duì)列中為某進(jìn)程服務(wù)時(shí),又有新進(jìn)程進(jìn)入優(yōu)先權(quán)較高的隊(duì)列(第 1~(i-1)中的任何一個(gè)隊(duì)列),則此時(shí)新進(jìn)程將搶占正在運(yùn)行進(jìn)程的處理機(jī),即由調(diào)度程序把正在運(yùn)行的進(jìn)程放回到第 i 隊(duì)列的末尾,把處理機(jī)分配給新到的高優(yōu)先權(quán)進(jìn)程。
在多級(jí)反饋隊(duì)列調(diào)度算法中,如果規(guī)定第一個(gè)隊(duì)列的時(shí)間片略大于多數(shù)人機(jī)交互所需之處理時(shí)間時(shí),便能夠較好的滿足各種類型用戶的需要。
什么是 CAS(比較并交換-樂觀鎖機(jī)制-鎖自旋)
概念及特性
CAS(Compare And Swap/Set)比較并交換,CAS 算法的過程是這樣:它包含 3 個(gè)參數(shù)CAS(V,E,N)。V 表示要更新的變量(內(nèi)存值),E 表示預(yù)期值(舊的),N 表示新值。當(dāng)且僅當(dāng) V 值等于 E 值時(shí),才會(huì)將 V 的值設(shè)為 N,如果 V 值和 E 值不同,則說明已經(jīng)有其他線程做了更新,則當(dāng)
前線程什么都不做。最后,CAS 返回當(dāng)前 V 的真實(shí)值。
CAS 操作是抱著樂觀的態(tài)度進(jìn)行的(樂觀鎖),它總是認(rèn)為自己可以成功完成操作。當(dāng)多個(gè)線程同時(shí)使用 CAS 操作一個(gè)變量時(shí),只有一個(gè)會(huì)勝出,并成功更新,其余均會(huì)失敗。失敗的線程不會(huì)被掛起,僅是被告知失敗,并且允許再次嘗試,當(dāng)然也允許失敗的線程放棄操作。基于這樣的原理,CAS 操作即使沒有鎖,也可以發(fā)現(xiàn)其他線程對(duì)當(dāng)前線程的干擾,并進(jìn)行恰當(dāng)?shù)奶幚怼?/p>
原子包?
java.util.concurrent.atomic(鎖自旋)
JDK1.5 的原子包:
java.util.concurrent.atomic 這個(gè)包里面提供了一組原子類。其基本的特性就是在多線程環(huán)境下,當(dāng)有多個(gè)線程同時(shí)執(zhí)行這些類的實(shí)例包含的方法時(shí),具有排他性,即當(dāng)某個(gè)線程進(jìn)入方法,執(zhí)行其中的指令時(shí),不會(huì)被其他線程打斷,而別的線程就像自旋鎖一樣,一直等到該方法執(zhí)行完成,才由 JVM 從等待隊(duì)列中選擇一個(gè)另一個(gè)線程進(jìn)入,這只是一種邏輯上的理解。相對(duì)于對(duì)于 synchronized 這種阻塞算法,CAS 是非阻塞算法的一種常見實(shí)現(xiàn)。由于一般 CPU 切換時(shí)間比 CPU 指令集操作更加長(zhǎng), 所以 J.U.C 在性能上有了很大的提升。如下代碼:
private volatile int value;
public final int get() {
return value;
}
public final int getAndIncrement() {
for (;;) { //CAS 自旋,一直嘗試,直達(dá)成功
int current = get();
int next = current + 1;
if (compareAndSet(current, next))
return current;
}
}
public final boolean compareAndSet(int expect, int update) {
return unsafe.compareAndSwapInt(this, valueOffset, expect, update);
}
}
getAndIncrement 采用了 CAS 操作,每次從內(nèi)存中讀取數(shù)據(jù)然后將此數(shù)據(jù)和+1 后的結(jié)果進(jìn)行CAS 操作,如果成功就返回結(jié)果,否則重試直到成功為止。而 compareAndSet 利用 JNI 來完成CPU 指令的操作。
ABA 問題
CAS 會(huì)導(dǎo)致“ABA 問題”。CAS 算法實(shí)現(xiàn)一個(gè)重要前提需要取出內(nèi)存中某時(shí)刻的數(shù)據(jù),而在下時(shí)刻比較并替換,那么在這個(gè)時(shí)間差類會(huì)導(dǎo)致數(shù)據(jù)的變化。
比如說一個(gè)線程 one 從內(nèi)存位置 V 中取出 A,這時(shí)候另一個(gè)線程 two 也從內(nèi)存中取出 A,并且two 進(jìn)行了一些操作變成了 B,然后 two 又將 V 位置的數(shù)據(jù)變成 A,這時(shí)候線程 one 進(jìn)行 CAS 操作發(fā)現(xiàn)內(nèi)存中仍然是 A,然后 one 操作成功。盡管線程 one 的 CAS 操作成功,但是不代表這個(gè)過程就是沒有問題的。
部分樂觀鎖的實(shí)現(xiàn)是通過版本號(hào)(version)的方式來解決 ABA 問題,樂觀鎖每次在執(zhí)行數(shù)據(jù)的修改操作時(shí),都會(huì)帶上一個(gè)版本號(hào),一旦版本號(hào)和數(shù)據(jù)的版本號(hào)一致就可以執(zhí)行修改操作并對(duì)版本號(hào)執(zhí)行+1 操作,否則就執(zhí)行失敗。因?yàn)槊看尾僮鞯陌姹咎?hào)都會(huì)隨之增加,所以不會(huì)出現(xiàn) ABA 問題,因?yàn)榘姹咎?hào)只會(huì)增加不會(huì)減少。
什么是 AQS(抽象的隊(duì)列同步器)
AbstractQueuedSynchronizer 類如其名,抽象的隊(duì)列式的同步器,AQS 定義了一套多線程訪問共享資源的同步器框架,許多同步類實(shí)現(xiàn)都依賴于它,如常用的
ReentrantLock/Semaphore/CountDownLatch。
它維護(hù)了一個(gè) volatile int state(代表共享資源)和一個(gè) FIFO 線程等待隊(duì)列(多線程爭(zhēng)用資源被阻塞時(shí)會(huì)進(jìn)入此隊(duì)列)。這里 volatile 是核心關(guān)鍵詞,具體 volatile 的語義,在此不述。state 的訪問方式有三種:
getState()
setState()
compareAndSetState()
AQS 定義兩種資源共享方式
Exclusive 獨(dú)占資源-ReentrantLock
Exclusive(獨(dú)占,只有一個(gè)線程能執(zhí)行,如 ReentrantLock)
Share 共享資源-Semaphore/CountDownLatch
Share(共享,多個(gè)線程可同時(shí)執(zhí)行,如 Semaphore/CountDownLatch)。
AQS 只是一個(gè)框架,具體資源的獲取/釋放方式交由自定義同步器去實(shí)現(xiàn),AQS 這里只定義了一個(gè)接口,具體資源的獲取交由自定義同步器去實(shí)現(xiàn)了(通過 state 的 get/set/CAS)之所以沒有定義成abstract ,是 因 為獨(dú) 占模 式 下 只 用實(shí)現(xiàn) tryAcquire-tryRelease ,而 共享 模 式 下 只用 實(shí) 現(xiàn)
tryAcquireShared-tryReleaseShared。如果都定義成abstract,那么每個(gè)模式也要去實(shí)現(xiàn)另一模式下的接口。不同的自定義同步器爭(zhēng)用共享資源的方式也不同。自定義同步器在實(shí)現(xiàn)時(shí)只需要實(shí)現(xiàn)共享資源 state 的獲取與釋放方式即可,至于具體線程等待隊(duì)列的維護(hù)(如獲取資源失敗入隊(duì)/喚醒出隊(duì)等),AQS 已經(jīng)在頂層實(shí)現(xiàn)好了。自定義同步器實(shí)現(xiàn)時(shí)主要實(shí)現(xiàn)以下幾種方法:
1.isHeldExclusively():該線程是否正在獨(dú)占資源。只有用到 condition 才需要去實(shí)現(xiàn)它。
2.tryAcquire(int):獨(dú)占方式。嘗試獲取資源,成功則返回 true,失敗則返回 false。
3.tryRelease(int):獨(dú)占方式。嘗試釋放資源,成功則返回 true,失敗則返回 false。
4.tryAcquireShared(int):共享方式。嘗試獲取資源。負(fù)數(shù)表示失敗;0 表示成功,但沒有剩余可用資源;正數(shù)表示成功,且有剩余資源。
5.tryReleaseShared(int):共享方式。嘗試釋放資源,如果釋放后允許喚醒后續(xù)等待節(jié)點(diǎn)返回true,否則返回 false。
同步器的實(shí)現(xiàn)是 ABS 核心(state 資源狀態(tài)計(jì)數(shù))
同步器的實(shí)現(xiàn)是 ABS 核心,以 ReentrantLock 為例,state 初始化為 0,表示未鎖定狀態(tài)。A 線程lock()時(shí),會(huì)調(diào)用 tryAcquire()獨(dú)占該鎖并將 state+1。此后,其他線程在 tryAcquire()時(shí)就會(huì)失敗,直到 A 線程 unlock()到 state=0(即釋放鎖)為止,其它線程才有機(jī)會(huì)獲取該鎖。當(dāng)然,釋放鎖之前,A 線程自己是可以重復(fù)獲取此鎖的(state 會(huì)累加),這就是可重入的概念。但要注意,獲取多少次就要釋放多少次,這樣才能保證 state 是能回到零態(tài)的。
以 CountDownLatch 以例,任務(wù)分為 N 個(gè)子線程去執(zhí)行,state 也初始化為 N(注意 N 要與線程個(gè)數(shù)一致)。這 N 個(gè)子線程是并行執(zhí)行的,每個(gè)子線程執(zhí)行完后 countDown()一次,state會(huì) CAS 減 1。等到所有子線程都執(zhí)行完后(即 state=0),會(huì) unpark()主調(diào)用線程,然后主調(diào)用線程就會(huì)從 await()函數(shù)返回,繼續(xù)后余動(dòng)作。
ReentrantReadWriteLock 實(shí)現(xiàn)獨(dú)占和共享兩種方式
一般來說,自定義同步器要么是獨(dú)占方法,要么是共享方式,他們也只需實(shí)現(xiàn) tryAcquiretryRelease、
tryAcquireShared-tryReleaseShared 中的一種即可。但 AQS 也支持自定義同步器同時(shí)實(shí)現(xiàn)獨(dú)占和共享兩種方式,如 ReentrantReadWriteLock。
多多轉(zhuǎn)發(fā)關(guān)注不迷路~~~~~
創(chuàng)作挑戰(zhàn)賽新人創(chuàng)作獎(jiǎng)勵(lì)來咯,堅(jiān)持創(chuàng)作打卡瓜分現(xiàn)金大獎(jiǎng)總結(jié)
以上是生活随笔為你收集整理的线程执行完之后会释放吗_java多线程并发:CAS+AQS+HashMap+volatile+ThreadLocal,乐分享...的全部?jī)?nèi)容,希望文章能夠幫你解決所遇到的問題。
- 上一篇: searchview 点击后被覆盖_03
- 下一篇: ++ 多核cpu 并行_一文读懂什么是多