//測(cè)試i--與println()聯(lián)合使用時(shí)可能出現(xiàn)異常
public class Test {
public static void main(String[] args) {
// 創(chuàng)建一個(gè)MyThread對(duì)象,并將該對(duì)象分別加載到五個(gè)線程中并分別給線程命名
MyThread myThread = new MyThread();
Thread t1 = new Thread(myThread);
Thread t2 = new Thread(myThread);
Thread t3 = new Thread(myThread);
Thread t4 = new Thread(myThread);
Thread t5 = new Thread(myThread);
// 啟動(dòng)五個(gè)線程,myThread中的 i=5 會(huì)由5個(gè)線程共享,5個(gè)線程分別執(zhí)行 run()
t1.start();
t2.start();
t3.start();
t4.start();
t5.start();
}
}
運(yùn)行結(jié)果:
i=5 threadName=Thread-1
i=2 threadName=Thread-5
i=5 threadName=Thread-2
i=4 threadName=Thread-3
i=3 threadName=Thread-4
原因: ()方法與i--聯(lián)合使用時(shí)“有可能”出現(xiàn)異常,雖然()方法在內(nèi)部是同步的,但i--的操作卻是在進(jìn)入()之前發(fā)生的,所以有發(fā)生非線程安全問題的概率。
() 的源碼:
public void println(String x) {
synchronized (this) {
print(x);
newLine();
}
}
解決方法:()與i--聯(lián)合使用時(shí)可能會(huì)出現(xiàn)非線程安全問題,解決方法仍是使用同步方法,即在需要同步的方法(比如上文中的run方法)前加上關(guān)鍵字。
6、如何知道代碼段被哪個(gè)線程調(diào)用?
System.out.println(Thread.currentThread().getName());
7、線程活動(dòng)狀態(tài)
public class XKThread extends Thread {
@Override
public void run() {
System.out.println("run run run is " + this.isAlive() );
}
public static void main(String[] args) {
XKThread xk = new XKThread();
System.out.println("begin ——— " + xk.isAlive());
xk.start();
System.out.println("end ————— " + xk.isAlive());
}
}
8、sleep()方法
方法sleep()的作用是在指定的毫秒數(shù)內(nèi)讓當(dāng)前的正在執(zhí)行的線程休眠(暫停執(zhí)行)。
9、如何優(yōu)雅地設(shè)置睡眠時(shí)間?
jdk1.5后,引入了一個(gè)枚舉 ,對(duì)sleep方法提供了很好的封裝。
比如要表達(dá)2小時(shí)22分55秒899毫秒。
Thread.sleep(8575899L);
TimeUnit.HOURS.sleep(2);
TimeUnit.MINUTES.sleep(22);
TimeUnit.SECONDS.sleep(55);
TimeUnit.MILLISECONDS.sleep(899);
可以看到表達(dá)的含義更清晰,更優(yōu)雅。
10、停止線程
run()方法執(zhí)行完成,自然終止。
stop()方法,()以及()都是過期作廢方法,使用它們結(jié)果不可預(yù)期。
大多數(shù)停止一個(gè)線程的操作使用.()等于說給線程打一個(gè)停止的標(biāo)記,此方法不會(huì)去終止一個(gè)正在運(yùn)行的線程,需要加入一個(gè)判斷才能可以完成線程的停止。
11、和的區(qū)別
:判斷當(dāng)前線程是否已經(jīng)中斷,會(huì)清除狀態(tài)。
:判斷線程是否已經(jīng)中斷,不會(huì)清除狀態(tài)。
12、yield
放棄當(dāng)前CPU資源,將它讓給其他的任務(wù)占用CPU執(zhí)行時(shí)間。但放棄的時(shí)間不確定,有可能剛剛放棄,馬上又獲得CPU時(shí)間片。
測(cè)試代碼:(cpu獨(dú)占時(shí)間片)
public class XKThread extends Thread {
@Override
public void run() {
long beginTime = System.currentTimeMillis();
int count = 0;
for (int i = 0; i < 50000000; i++) {
count = count + (i + 1);
}
long endTime = System.currentTimeMillis();
System.out.println("?用時(shí) = " + (endTime - beginTime) + " 毫秒! ");
}
public static void main(String[] args) {
XKThread xkThread = new XKThread();
xkThread.start();
}
}
輸出結(jié)果:
用時(shí) = 20 毫秒!
加入yield再來測(cè)試。(CPU讓給其他資源導(dǎo)致速度變慢)
public class XKThread extends Thread {
@Override
public void run() {
long beginTime = System.currentTimeMillis();
int count = 0;
for (int i = 0; i < 50000000; i++) {
Thread.yield();//加入yield
count = count + (i + 1);
}
long endTime = System.currentTimeMillis();
System.out.println("?用時(shí) = " + (endTime - beginTime) + " 毫秒! ");
}
public static void main(String[] args) {
XKThread xkThread = new XKThread();
xkThread.start();
}
}
結(jié)果:
用時(shí) = 38424 毫秒!
13、線程的優(yōu)先級(jí)
在操作系統(tǒng)中,線程可以劃分優(yōu)先級(jí),優(yōu)先級(jí)較高的線程得到cpu資源比較多,也就是cpu優(yōu)先執(zhí)行優(yōu)先級(jí)較高的線程對(duì)象中的任務(wù),但是不能保證一定優(yōu)先級(jí)高,就先執(zhí)行。
Java的優(yōu)先級(jí)分為1~10個(gè)等級(jí),數(shù)字越大優(yōu)先級(jí)越高,默認(rèn)優(yōu)先級(jí)大小為5。超出范圍則拋出:java.lang.tion。
14、優(yōu)先級(jí)繼承特性
線程的優(yōu)先級(jí)具有繼承性,比如a線程啟動(dòng)b線程,b線程與a優(yōu)先級(jí)是一樣的。
15、誰跑的更快?
設(shè)置優(yōu)先級(jí)高低兩個(gè)線程,累加數(shù)字,看誰跑的快,上代碼。
public class Run extends Thread{
public static void main(String[] args) {
try {
ThreadLow low = new ThreadLow();
low.setPriority(2);
low.start();
ThreadHigh high = new ThreadHigh();
high.setPriority(8);
high.start();
Thread.sleep(2000);
low.stop();
high.stop();
System.out.println("low = " + low.getCount());
System.out.println("high = " + high.getCount());
} catch (InterruptedException e) {
e.printStackTrace();
}
}
}
class ThreadHigh extends Thread {
private int count = 0;
public int getCount() {
return count;
}
@Override
public void run() {
while (true) {
count++;
}
}
}
class ThreadLow extends Thread {
private int count = 0;
public int getCount() {
return count;
}
@Override
public void run() {
while (true) {
count++;
}
}
}
結(jié)果:
low = 1193854568
high = 1204372373
16、線程種類
Java線程有兩種,一種是用戶線程,一種是守護(hù)線程()。
17、守護(hù)線程 17.1 守護(hù)線程的特點(diǎn)
守護(hù)線程是一個(gè)比較特殊的線程,主要被用做程序中后臺(tái)調(diào)度以及支持性工作。當(dāng)Java虛擬機(jī)中不存在非守護(hù)線程時(shí),守護(hù)線程才會(huì)隨著JVM一同結(jié)束工作。
17.2 Java中典型的守護(hù)線程
GC(垃圾回收器)
17.3 如何設(shè)置守護(hù)線程
.(true)
PS:屬性需要在啟動(dòng)線程之前設(shè)置,不能再啟動(dòng)后設(shè)置。
17.4 Java虛擬機(jī)退出時(shí)線程中的塊一定會(huì)執(zhí)行?
Java虛擬機(jī)退出時(shí),線程中的塊并不一定會(huì)執(zhí)行。
代碼示例:
public class XKDaemon {
public static void main(String[] args) {
Thread thread = new Thread(new DaemonRunner(),"xkDaemonRunner");
thread.setDaemon(true);
thread.start();
}
static class DaemonRunner implements Runnable {
@Override
public void run() {
try {
SleepUtils.sleep(10);
} finally {
System.out.println("Java?小咖秀 daemonThread finally run …");
}
}
}
}
結(jié)果:
沒有任何的輸出,說明沒有執(zhí)行。
18、設(shè)置線程上下文類加載器
獲取線程上下文類加載器
public ClassLoader getContextClassLoader()
設(shè)置線程類加載器(可以打破Java類加載器的父類委托機(jī)制)
public void setContextClassLoader(ClassLoader cl)
19、join
join是指把指定的線程加入到當(dāng)前線程,比如join某個(gè)線程a,會(huì)讓當(dāng)前線程b進(jìn)入等待,直到a的生命周期結(jié)束,此期間b線程是處于狀態(tài)。
20、 20.1 什么是?
關(guān)鍵字可以實(shí)現(xiàn)一個(gè)簡(jiǎn)單的策略來防止線程干擾和內(nèi)存一致性錯(cuò)誤,如果一個(gè)對(duì)象是對(duì)多個(gè)線程可見的,那么對(duì)該對(duì)象的所有讀寫都將通過同步的方式來進(jìn)行。
20.2、包括哪兩個(gè)jvm重要的指令?
enter和 exit
20.3 關(guān)鍵字用法?
可以用于對(duì)代碼塊或方法的修飾
20.4 鎖的是什么? 被修飾情況被鎖對(duì)象
普通同步方法
當(dāng)前實(shí)例對(duì)象
靜態(tài)同步方法
當(dāng)前類的Class對(duì)象
同步方法塊
括號(hào)里配置的對(duì)象
21、Java對(duì)象的組成 Java對(duì)象在內(nèi)存中的保存格式
內(nèi)容長(zhǎng)度(32/64位JVM)說明
對(duì)象頭
Mark Word
32/64bit
存儲(chǔ)對(duì)象的或者鎖信息等。當(dāng)這個(gè)對(duì)象被關(guān)鍵字當(dāng)成同步鎖時(shí),圍繞這個(gè)鎖的一系列操作都和Mark Word有關(guān)。
指向類的指針
32/64bit
即指向當(dāng)前對(duì)象的類的元數(shù)據(jù)的指針,虛擬機(jī)通過這個(gè)指針來確定這個(gè)對(duì)象是哪個(gè)類的實(shí)例。并不是所有的虛擬機(jī)實(shí)現(xiàn)都必須在對(duì)象數(shù)據(jù)上保留類型指針,換句話說查找對(duì)象的元數(shù)據(jù)信息并不一定要經(jīng)過對(duì)象本身。
數(shù)組長(zhǎng)度
32/32bit
只有數(shù)組對(duì)象保存了這部分?jǐn)?shù)據(jù)
實(shí)例數(shù)據(jù)
實(shí)例數(shù)據(jù)部分是對(duì)象真正存儲(chǔ)的有效信息,也就是我們?cè)诔绦虼a里面所定義的各種類型的字段內(nèi)容,無論是從父類繼承下來的,還是在子類中定義的都需要記錄下來。 這部分的存儲(chǔ)順序會(huì)受到虛擬機(jī)分配策略參數(shù)(e)和字段在Java源碼中定義順序的影響。
對(duì)齊填充字節(jié)
因?yàn)镴VM要求Java的對(duì)象占的內(nèi)存大小應(yīng)該是8bit的倍數(shù),所以后面有幾個(gè)字節(jié)用于把對(duì)象的大小補(bǔ)齊至8bit的倍數(shù),沒有特別的功能。
用的鎖是存在Java對(duì)象頭里的。對(duì)象如果是數(shù)組類型,虛擬機(jī)用3個(gè)字寬存儲(chǔ)對(duì)象頭,如果對(duì)象是非數(shù)組類型,用2字寬存儲(chǔ)對(duì)象頭。
Tips:32位虛擬機(jī)中一個(gè)字寬等于4字節(jié)。
32位JVM的Mark Word默認(rèn)存儲(chǔ)結(jié)構(gòu):
鎖狀態(tài)是否偏向鎖2bit鎖標(biāo)志位
無鎖狀態(tài)
對(duì)象的
對(duì)象分代年齡
0
01
21.1 Mark Word的狀態(tài)變化
Mark Word存儲(chǔ)的數(shù)據(jù)會(huì)隨著鎖標(biāo)志位的變化而變化。在32位JVM中長(zhǎng)度是這么存的:
鎖狀態(tài)
是否是偏向鎖鎖標(biāo)志位
無鎖
對(duì)象的
對(duì)象分代年齡
0
01
偏向鎖
線程ID
Epoch
對(duì)象分代年齡
1
01
輕量級(jí)鎖
指向棧中鎖記錄的指針
00
重量級(jí)鎖
指向互斥量(重量級(jí)鎖)的指針
10
GC標(biāo)記
空
11
64位JVM下,Mark Word是64位大小的。
鎖狀態(tài)
分代年齡
偏向鎖
標(biāo)志位
無鎖
0
01
偏向鎖
(54bit) Epoch(2bit)
1
01
JVM一般是這樣使用鎖和Mark Word的:
無鎖狀態(tài)時(shí),這就是一個(gè)普通的對(duì)象,Mark Word記錄對(duì)象的,鎖標(biāo)志位是01,是否偏向鎖那一位是0。當(dāng)對(duì)象被當(dāng)做同步鎖并有一個(gè)線程A搶到了鎖時(shí),鎖標(biāo)志位還是01,但是否偏向鎖那一位改成1,前23bit記錄搶到鎖的線程ID,表示進(jìn)入偏向鎖狀態(tài)。當(dāng)線程A再次試圖來獲得鎖時(shí),JVM發(fā)現(xiàn)同步鎖對(duì)象的標(biāo)志位是01,是否偏向鎖是1,也就是偏向鎖狀態(tài),Mark Word中記錄的線程ID就是線程A自己的ID,表示線程A已經(jīng)獲得了這個(gè)偏向鎖,可以執(zhí)行同步鎖的代碼。當(dāng)線程B試圖獲得這個(gè)鎖時(shí),JVM發(fā)現(xiàn)同步鎖處于偏向狀態(tài),但是Mark Word中的線程id記錄的不是B,那么線程B會(huì)先用CAS操作試圖獲得鎖,這里的獲得鎖操作是有可能成功的,因?yàn)榫€程A一般不會(huì)自動(dòng)釋放偏向鎖。如果搶鎖成功,就把Mark Word里的線程ID改為線程B的ID,代表線程B獲得了這個(gè)偏向鎖,可以執(zhí)行同步鎖代碼。如果搶鎖失敗,則繼續(xù)執(zhí)行步驟5。偏向鎖狀態(tài)搶鎖失敗,代表當(dāng)前鎖有一定的競(jìng)爭(zhēng),偏向鎖將升級(jí)為輕量級(jí)鎖。JVM會(huì)在當(dāng)前線程的線程棧中開辟一塊單獨(dú)的空間,里面保存指向?qū)ο箧iMark Word的指針,同時(shí)在對(duì)象鎖Mark Word中保存指向這片空間的指針。上述兩個(gè)保存操作都是CAS操作,如果保存成功,代表線程搶到了同步鎖,就把Mark Word中的鎖標(biāo)志位改成00,可以執(zhí)行同步鎖代碼。如果保存失敗,表示搶鎖失敗,競(jìng)爭(zhēng)太激烈,繼續(xù)執(zhí)行步驟6。輕量級(jí)鎖搶鎖失敗,JVM會(huì)使用自旋鎖,自旋鎖不是一個(gè)鎖狀態(tài),只是代表不斷的重試,嘗試搶鎖。從JDK1.7開始,自旋鎖默認(rèn)啟用,自旋次數(shù)由JVM決定。如果搶鎖成功則執(zhí)行同步鎖代碼,如果失敗則繼續(xù)執(zhí)行步驟7。自旋鎖重試之后如果搶鎖依然失敗,同步鎖會(huì)升級(jí)至重量級(jí)鎖,鎖標(biāo)志位改為10。在這個(gè)狀態(tài)下,未搶到鎖的線程都會(huì)被阻塞。 22、Java鎖的種類 22.1 鎖的升降級(jí)規(guī)則
Java SE1.6為了提高鎖的性能。引入了偏向鎖和輕量級(jí)鎖。
Java SE1.6中鎖有4種狀態(tài)。級(jí)別從低到高依次是:無鎖狀態(tài)、偏向鎖狀態(tài)、輕量級(jí)鎖狀態(tài)、重量級(jí)鎖狀態(tài)。
鎖只能升級(jí)不能降級(jí)。
22.2 偏向鎖
大多數(shù)情況,鎖不僅不存在多線程競(jìng)爭(zhēng),而且總由同一線程多次獲得。當(dāng)一個(gè)線程訪問同步塊并獲取鎖時(shí),會(huì)在對(duì)象頭和棧幀中記錄存儲(chǔ)鎖偏向的線程ID,以后該線程在進(jìn)入和退出同步塊時(shí)不需要進(jìn)行cas操作來加鎖和解鎖,只需測(cè)試一下對(duì)象頭Mark Word里是否存儲(chǔ)著指向當(dāng)前線程的偏向鎖。如果測(cè)試成功,表示線程已經(jīng)獲得了鎖,如果失敗,則需要測(cè)試下Mark Word中偏向鎖的標(biāo)示是否已經(jīng)設(shè)置成1(表示當(dāng)前是偏向鎖),如果沒有設(shè)置,則使用cas競(jìng)爭(zhēng)鎖,如果設(shè)置了,則嘗試使用cas將對(duì)象頭的偏向鎖只限當(dāng)前線程。
關(guān)閉偏向鎖延遲
java6 和 7 中默認(rèn)啟用偏向鎖延遲,但是會(huì)在程序啟動(dòng)幾秒后才激活,如果需要關(guān)閉延遲,使用-XX:Delay=0。
如何關(guān)閉偏向鎖
JVM參數(shù)關(guān)閉偏向鎖:-XX:-=false,那么程序默認(rèn)會(huì)進(jìn)入輕量級(jí)鎖狀態(tài)。
Tips:如果你可以確定程序的所有鎖通常情況處于競(jìng)態(tài),則可以選擇關(guān)閉。
22.3 輕量級(jí)鎖
線程在執(zhí)行同步塊,JVM會(huì)在當(dāng)前線程的棧幀中創(chuàng)建用于儲(chǔ)存鎖記錄的空間。并將對(duì)象頭中的Mark
Word復(fù)制到鎖記錄中。然后線程嘗試使用 cas 將對(duì)象頭中的替換為自己鎖記錄的指針。如果成功,當(dāng)前線程獲得鎖,如果失敗,表示其他線程競(jìng)爭(zhēng)鎖,當(dāng)前線程便嘗試使用自旋來獲取鎖。
輕量級(jí)鎖的解鎖
輕量級(jí)鎖解鎖時(shí),會(huì)使原子操作cas將 Mark Word替換回對(duì)象頭,如果成功則表示沒有競(jìng)爭(zhēng)發(fā)生,如果失敗,表示存在競(jìng)爭(zhēng),此時(shí)鎖就會(huì)膨脹為重量級(jí)鎖。
22.4 鎖的優(yōu)缺點(diǎn)對(duì)比 鎖優(yōu)點(diǎn)缺點(diǎn)適用場(chǎng)景
偏向鎖
加鎖和解鎖都不需要額外的消耗,和執(zhí)行非同步方法相比僅存在納秒級(jí)的差距
如果線程間存在鎖競(jìng)爭(zhēng),會(huì)帶來額外的鎖撤銷的消耗
只有一個(gè)線程訪問同步塊
輕量級(jí)鎖
競(jìng)爭(zhēng)的線程不會(huì)阻塞,提高了程序的響應(yīng)速度
如果始終得不到鎖競(jìng)爭(zhēng)的線程,使用自旋會(huì)消耗CPU
追求響應(yīng)時(shí)間、同步塊執(zhí)行速度非常塊
重量級(jí)鎖
線程競(jìng)爭(zhēng)不使用自旋,不會(huì)消耗CPU
線程阻塞word沒有響應(yīng)保存 等待,響應(yīng)時(shí)間慢
追求吞吐量、同步塊執(zhí)行時(shí)間較長(zhǎng)
23、什么是原子操作
不可被中斷的一個(gè)或者一系列操作。
23.1 Java如何實(shí)現(xiàn)原子操作
Java中通過鎖和循環(huán) CAS 的方式來實(shí)現(xiàn)原子操作,JVM的CAS操作利用了處理器提供的指令來實(shí)現(xiàn)的。自旋CAS實(shí)現(xiàn)的基本思路就是循環(huán)進(jìn)行CAS操作直到成功為止。
23.2 CAS實(shí)現(xiàn)原子操作的3大問題 什么是ABA問題
問題:因?yàn)閏as需要在操作值的時(shí)候,檢查值有沒有變化,如果沒有變化則更新。如果一個(gè)值原來是A,變成了B,又變成了A,那么使用cas進(jìn)行檢測(cè)時(shí)會(huì)發(fā)現(xiàn)值沒有發(fā)生變化,其實(shí)是變過的。
本質(zhì):ABA問題的根本在于cas在修改變量的時(shí)候,無法記錄變量的狀態(tài),比如修改的次數(shù),否修改過這個(gè)變量。這樣就很容易在一個(gè)線程將A修改成B時(shí),另一個(gè)線程又會(huì)把B修改成A,造成cas多次執(zhí)行的問題。
解決:添加版本號(hào),每次更新的時(shí)候追加版本號(hào),A-B-A一>1A-2B-3A。
從JDK1.5開始,包提供了一個(gè)類ce來解決ABA的問題。
CAS循環(huán)時(shí)間長(zhǎng)占用資源大問題
如果 JVM 能支持處理器提供的pause指令,那么效率會(huì)有一定的提升。
第一、它可以延遲流水線執(zhí)行指令(de-),使cpu不會(huì)消耗過多的執(zhí)行資源,延遲的時(shí)間取決于具體實(shí)現(xiàn)的版本,有些處理器延遲時(shí)間是0。
第二、它可以避免在退出循環(huán)的時(shí)候因?yàn)閮?nèi)存順序沖突而引起的CPU流水線被清空,從而提高CPU執(zhí)行效率。
CAS只能保證一個(gè)共享變量原子操作
1、對(duì)多個(gè)共享變量操作時(shí),可以用鎖。
2、可以把多個(gè)共享變量合并成一個(gè)共享變量來操作。比如 x=1,k=a,合并成xk=1a,然后用cas操作xk。
Tips:java1.5開始,JDK提供了類來保證引用對(duì)象之間的原子性,就可以把多個(gè)變量放在一個(gè)對(duì)象來進(jìn)行cas操作。
24、關(guān)鍵字
是輕量級(jí)的,它在多處理器開發(fā)中保證了共享變量的“可見性“。
Java語言規(guī)范第3版對(duì) 定義如下,Java允許線程訪問共享變量,為了保證共享變量能準(zhǔn)確和一致的更新,線程應(yīng)該確保排它鎖單獨(dú)獲得這個(gè)變量。如果一個(gè)字段被聲明為 ,Java線程內(nèi)存模型所有線程看到這個(gè)變量的值是一致的。
25、wait
方法wait()的作用是使當(dāng)前執(zhí)行代碼的線程進(jìn)行等待,wait()是類通用的方法,該方法用來將當(dāng)前線程置入“預(yù)執(zhí)行隊(duì)列”中,并在wait()所在的代碼處停止執(zhí)行,直到接到通知或中斷為止。
在調(diào)用wait之前,線程需要獲得該對(duì)象的對(duì)象級(jí)別的鎖。代碼體現(xiàn)上,即只能是同步方法或同步代碼塊內(nèi)。調(diào)用wait()后當(dāng)前線程釋放鎖。
26、
()也是類的通用方法,也要在同步方法或同步代碼塊內(nèi)調(diào)用,該方法用來喚醒一個(gè)線程,如果有多個(gè)線程等待,則隨機(jī)挑選出其中一個(gè)處于wait狀態(tài)的線程,對(duì)其發(fā)出通知,并讓它等待獲取該對(duì)象的對(duì)象鎖。
27、/
等于說將等待隊(duì)列中的一個(gè)線程移動(dòng)到同步隊(duì)列中,而是將等待隊(duì)列中的所有線程全部移動(dòng)到同步隊(duì)列中。
28、等待/通知機(jī)制
一個(gè)線程修改了一個(gè)對(duì)象的值,而另一個(gè)線程感知到了變化,然后進(jìn)行相應(yīng)的操作。
28.1 等待/通知經(jīng)典范式
等待wait:
synchronized(obj) {
while(條件不不滿?) {
obj.wait();
}
執(zhí)行對(duì)應(yīng)邏輯
}
通知:
synchronized(obj) {
改變條件
obj.notifyAll();
}
29、
主要解決每一個(gè)線程想綁定自己的值,存放線程的私有數(shù)據(jù)。
29.1 使用
獲取當(dāng)前的線程的值通過get(),默認(rèn)值為空,設(shè)置set(T)方式來設(shè)置值。
public class XKThreadLocal {
public static ThreadLocal threadLocal = new ThreadLocal();
public static void main(String[] args) {
if (threadLocal.get() == null) {
System.out.println("未設(shè)置過值");
threadLocal.set("Java?小咖秀");
}
System.out.println(threadLocal.get());
}
}
輸出:
未設(shè)置過值
Java?小咖秀
29.2 解決get()返回null問題
通過繼承重寫()方法即可。
代碼實(shí)現(xiàn):
public class ThreadLocalExt extends ThreadLocal{
static ThreadLocalExt threadLocalExt = new ThreadLocalExt();
@Override
protected Object initialValue() {
return "Java小咖秀";
}
public static void main(String[] args) {
System.out.println(threadLocalExt.get());
}
}
輸出:
Java小咖秀
30、Lock接口
鎖可以防止多個(gè)線程同時(shí)共享資源。Java5前,程序是靠實(shí)現(xiàn)鎖功能。Java5之后,并發(fā)包新增Lock接口來實(shí)現(xiàn)鎖功能。
30.1 Lock接口提供不具備的主要特性 特性描述
嘗試非阻塞地獲取鎖
當(dāng)前線程嘗試獲取鎖,如果這一時(shí)刻沒有被其他線程獲取到,則成功獲取并持有鎖
能被中斷地獲取鎖
與不同,獲取到鎖的線程能夠響應(yīng)中斷。當(dāng)獲取到鎖的線程被中斷時(shí),中斷異常將會(huì)被拋出,同時(shí)鎖會(huì)被釋放
超時(shí)獲取鎖
在指定的截止時(shí)間之前獲取鎖。如果截止時(shí)間到了仍舊無法獲取鎖,則返回。
30.2 重入鎖
支持重進(jìn)入的鎖,它表示該鎖能夠支持一個(gè)線程對(duì)資源的重復(fù)加鎖。除此之外,該鎖的還支持獲取鎖時(shí)的公平和非公平性選擇。
30.3 重進(jìn)入是什么意思?
重進(jìn)入是指任意線程在獲取到鎖之后能夠再次獲鎖而不被鎖阻塞。
該特性主要解決以下兩個(gè)問題:
鎖需要去識(shí)別獲取鎖的線程是否為當(dāng)前占據(jù)鎖的線程,如果是則再次成功獲取。所得鎖最終釋放。線程重復(fù)n次是獲取了鎖,隨后在第n次釋放該鎖后,其他線程能夠獲取到該鎖。 30.4 默認(rèn)鎖?
默認(rèn)非公平鎖。
代碼為證:
final boolean nonfairTryAcquire(int acquires) {
final Thread current = Thread.currentThread();
int c = getState();
if (c == 0) {
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;
}
31、公平鎖和非公平鎖的區(qū)別
公平性與否是針對(duì)獲取鎖來說的,如果一個(gè)鎖是公平的,那么鎖的獲取順序就應(yīng)該符合請(qǐng)求的絕對(duì)時(shí)間順序,也就是FIFO。
32、讀寫鎖
讀寫鎖允許同一時(shí)刻多個(gè)讀線程訪問,但是寫線程和其他寫線程均被阻塞。讀寫鎖維護(hù)一個(gè)讀鎖一個(gè)寫鎖,讀寫分離,并發(fā)性得到了提升。
Java中提供讀寫鎖的實(shí)現(xiàn)類是ck。
33、工具
定義了一組公共靜態(tài)方法,提供了最基本的線程阻塞和喚醒功能。
方法名稱描述
void park()
阻塞當(dāng)前線程,如果用 ( )方法或者當(dāng)前線程被中斷,才能從park()方法返回
void (long nanos)
阻塞當(dāng)前線程,最長(zhǎng)不超過 nanos 納秒,返回條件在 park()的基礎(chǔ)上增加了超時(shí)返回
void (long )
阻當(dāng)前線程.直到時(shí)間(從1970年開始到時(shí)間的毫秒數(shù))
void ( )
喚醒處于阻塞狀態(tài)的線程
34、接口
提供了類似監(jiān)視器方法,與Lock配合使用實(shí)現(xiàn)等待/通知模式。
34.1 使用
代碼示例:
public class XKCondition {
Lock lock = new ReentrantLock();
Condition cd = lock.newCondition();
public void await() throws InterruptedException {
lock.lock();
try {
cd.await();//相當(dāng)于Object 方法中的wait()
} finally {
lock.unlock();
}
}
public void signal() {
lock.lock();
try {
cd.signal(); //相當(dāng)于Object 方法中的notify()
} finally {
lock.unlock();
}
}
}
35、Java并發(fā)容器 并發(fā)容器描述
并發(fā)安全版,Java7中采用分段鎖技術(shù)來提高并發(fā)效率,默認(rèn)分16段。Java8放棄了分段鎖,采用CAS,同時(shí)當(dāng)哈希沖突時(shí),當(dāng)鏈表的長(zhǎng)度到8時(shí),會(huì)轉(zhuǎn)化成紅黑樹。
e
基于鏈接節(jié)點(diǎn)的無界線程安全隊(duì)列,它采用先進(jìn)先出的規(guī)則對(duì)節(jié)點(diǎn)進(jìn)行排序,當(dāng)我們添加一個(gè)元素的時(shí)候,它會(huì)添加到隊(duì)列的尾部,當(dāng)我們獲取一個(gè)元素時(shí),它會(huì)返回隊(duì)列頭部的元素。它采用cas算法來實(shí)現(xiàn)。
e
p
t
-------------------------------
---------------------------------------------------------------------------
一個(gè)由數(shù)據(jù)支持的有界阻塞隊(duì)列,此隊(duì)列按照FIFO原則對(duì)元素進(jìn)行排序。隊(duì)列頭部在隊(duì)列中存在的時(shí)間最長(zhǎng),隊(duì)列尾部存在時(shí)間最短。
雙向隊(duì)列
e
一個(gè)支持優(yōu)先級(jí)排序的無界阻塞隊(duì)列,但它不會(huì)阻塞數(shù)據(jù)生產(chǎn)者,而只會(huì)在沒有可消費(fèi)的數(shù)據(jù)時(shí),阻塞數(shù)據(jù)的消費(fèi)者。
-----------------------
--------------------------------------------------------------------------
是一個(gè)支持延時(shí)獲取元素的使用優(yōu)先級(jí)隊(duì)列的實(shí)現(xiàn)的無界阻塞隊(duì)列。隊(duì)列中的元素必須實(shí)現(xiàn) 接口和 接口,在創(chuàng)建元素時(shí)可以指定多久才能從隊(duì)列中獲取當(dāng)前元素。
36、阻塞隊(duì)列 36.1 什么是阻塞隊(duì)列?
隊(duì)列比較好理解,數(shù)據(jù)結(jié)構(gòu)中我們都接觸過,先進(jìn)先出的一種數(shù)據(jù)結(jié)構(gòu),那什么是阻塞隊(duì)列呢?從名字可以看出阻塞隊(duì)列其實(shí)也就是隊(duì)列的一種特殊情況。它具有如下特點(diǎn):
當(dāng)阻塞隊(duì)列滿了,往隊(duì)列添加元素的操作將會(huì)被阻塞。當(dāng)阻塞隊(duì)列為空時(shí),從隊(duì)列中獲取元素的操作將會(huì)被阻塞。 36.2 阻塞隊(duì)列常用的應(yīng)用場(chǎng)景?
常用于生產(chǎn)者和消費(fèi)者場(chǎng)景,生產(chǎn)者是往隊(duì)列里添加元素的線程,消費(fèi)者是從隊(duì)列里取元素的線程。阻塞隊(duì)列正好是生產(chǎn)者存放、消費(fèi)者來獲取的容器。
36.3 Java里的阻塞的隊(duì)列 隊(duì)列描述
數(shù)組結(jié)構(gòu)組成的 、有界阻塞隊(duì)列
鏈表結(jié)構(gòu)組成的 、有界阻塞隊(duì)列
e
支持優(yōu)先級(jí)排序 、無界阻塞隊(duì)列
優(yōu)先級(jí)隊(duì)列實(shí)現(xiàn) 、無界阻塞隊(duì)列
不存儲(chǔ)元素 、阻塞隊(duì)列
鏈表結(jié)構(gòu)組成、無界阻塞隊(duì)列
鏈表結(jié)構(gòu)組成、雙向阻塞隊(duì)列
37、Fork/Join
Java7 提供的一個(gè)用于并行執(zhí)行任務(wù)的框架,把一個(gè)大任務(wù)分割成若干個(gè)小任務(wù),最終匯總每個(gè)小任務(wù)的結(jié)果后得到大任務(wù)結(jié)果的框架。
37.1 工作竊取算法
是指某個(gè)線程從其他隊(duì)列里竊取任務(wù)來執(zhí)行。當(dāng)大任務(wù)被分割成小任務(wù)時(shí),有的線程可能提前完成任務(wù),此時(shí)閑著不如去幫其他沒完成工作線程。此時(shí)可以去其他隊(duì)列竊取任務(wù),為了減少競(jìng)爭(zhēng),通常使用雙端隊(duì)列,被竊取的線程從頭部拿,竊取的線程從尾部拿任務(wù)執(zhí)行。
37.2 工作竊取算法的優(yōu)缺點(diǎn) 38、包
Java從JDK1.5開始提供了java.util..包,方便程序員在多線程環(huán)境下,無鎖地進(jìn)行原子操作。原子變量的底層使用了處理器提供的原子指令,但是不同的CPU架構(gòu)可能提供的原子指令不一樣,也有可能需要某種形式的內(nèi)部鎖,所以該方法不能絕對(duì)保證線程不被阻塞。
在包里一共有12個(gè)類,四種原子更新方式,分別是原子更新基本類型,原子更新數(shù)組,原子更新引用和原子更新字段。包里的類基本都是使用實(shí)現(xiàn)的包裝類。
類原子更新對(duì)象
原子操作更新基本類型
布爾類型
整型類型
長(zhǎng)整型類型
原子操作更新數(shù)組
整型數(shù)組里的元素
長(zhǎng)整型數(shù)組里的元素
引用類型數(shù)組里的元素
原子操作更新引用類型
引用類型
引用類型里的字段
nce
原子更新帶有標(biāo)記位的引用類型,標(biāo)記位用類型表示,構(gòu)造方法為`nce(V , )`
原子操作更新字段類
整型字段的更新器
ter
長(zhǎng)整型字段的更新器
pdate
帶有版本號(hào)的引用類型 `
39、JDK并發(fā)包中提供了哪幾個(gè)比較常見的處理并發(fā)的工具類?
提供并發(fā)控制手段:、、
線程間數(shù)據(jù)交換:
39.1
這個(gè)類 使一個(gè)線程等待其他線程各自執(zhí)行完畢后再執(zhí)行。 是通過一個(gè)計(jì)數(shù)器來實(shí)現(xiàn)的,計(jì)數(shù)器的初始值是線程的數(shù)量。每當(dāng)一個(gè)線程執(zhí)行完畢后,計(jì)數(shù)器的值就-1,當(dāng)計(jì)數(shù)器的值為0時(shí),表示所有線程都執(zhí)行完畢,然后在閉鎖上等待的線程就可以恢復(fù)工作了。
的構(gòu)造函數(shù)接受一個(gè)int類型的參數(shù)作為計(jì)數(shù)器,你想等待n個(gè)線程完成,就傳入n。
三個(gè)重要的方法:
//調(diào)用await()方法的線程會(huì)被掛起,它會(huì)等待直到count值為0才繼續(xù)執(zhí)行
public void await() throws InterruptedException { };
//和await()類似,只不過等待一定的時(shí)間后count值還沒變?yōu)?的話就會(huì)繼續(xù)執(zhí)行
public boolean await(long timeout, TimeUnit unit) throws InterruptedException { };
//將count值減1
public void countDown() { };
Tips:
39.2
可循環(huán)使用的屏障。它的作用就是會(huì)讓所有線程都等待完成后才會(huì)繼續(xù)下一步行動(dòng)。 讓一組線程到達(dá)一個(gè)屏障(也可以叫同步點(diǎn))時(shí)被阻塞word沒有響應(yīng)保存 等待,直到最后一個(gè)線程到達(dá)屏障時(shí),屏障才會(huì)開門,所有被屏障攔截的線程才會(huì)繼續(xù)運(yùn)行。
舉個(gè)例子,就像生活中我們會(huì)約朋友們到某個(gè)餐廳一起吃飯,有些朋友可能會(huì)早到,有些朋友可能會(huì)晚到,但是這個(gè)餐廳規(guī)定必須等到所有人到齊之后才會(huì)讓我們進(jìn)去。這里的朋友們就是各個(gè)線程,餐廳就是 。
默認(rèn)構(gòu)造器是(int ),其參數(shù)表示屏障攔截的線程數(shù)量,每個(gè)線程調(diào)用await方法告訴我已經(jīng)到達(dá)屏障,然后當(dāng)前線程被阻塞。
39.3 與區(qū)別 計(jì)數(shù)器等待
計(jì)數(shù)器只能使用一次
一個(gè)線程或多個(gè)等待另外n個(gè)線程完成之后才能執(zhí)行
計(jì)數(shù)器可以重置(通過reset()方法)
n個(gè)線程相互等待,任何一個(gè)線程完成之前,所有的線程都必須等待。
39.4
用來控制同時(shí)訪問資源的線程數(shù)量,通過協(xié)調(diào)各個(gè)線程,來保證合理的公共資源的訪問。
應(yīng)用場(chǎng)景:流量控制,特別是公共資源有限的應(yīng)用場(chǎng)景,比如數(shù)據(jù)鏈接,限流等。
39.5
是一個(gè)用于線程間協(xié)作的工具類,它提供一個(gè)同步點(diǎn),在這個(gè)同步點(diǎn)上,兩個(gè)線程可以交換彼此的數(shù)據(jù)。比如第一個(gè)線程執(zhí)行()方法,它會(huì)一直等待第二個(gè)線程也執(zhí)行(),當(dāng)兩個(gè)線程都到同步點(diǎn),就可以交換數(shù)據(jù)了。
一般來說為了避免一直等待的情況,可以使用(V x,long , unit),設(shè)置最大等待時(shí)間。
可以用于遺傳算法。
40、線程池 40.1 為什么使用線程池
幾乎所有需要異步或者并發(fā)執(zhí)行任務(wù)的程序都可以使用線程池。合理使用會(huì)給我們帶來以下好處:
40.2 線程池工作流程
假設(shè)線程池的最大容量為5個(gè),其中核心線程池容量為3個(gè),非核心線程池容量為2個(gè)。工作隊(duì)列的容量大小為2個(gè)。
一開始,線程池里的線程個(gè)數(shù)為0,這時(shí)候任務(wù)1來了,線程池管理者就會(huì)創(chuàng)建1個(gè)線程,用來執(zhí)行任務(wù)1,接著任務(wù)2、3也來了,線程池管理者就會(huì)再創(chuàng)建兩個(gè)線程,用來執(zhí)行任務(wù)2和3。此時(shí),線程池里一共3個(gè)線程,也就是核心線程池滿了。
這時(shí)候,又來一個(gè)任務(wù)4,但是核心線程池已經(jīng)滿了,管理者就會(huì)讓它到工作隊(duì)列進(jìn)行排隊(duì),任務(wù)5來了,同樣也是在工作隊(duì)列里等待。此時(shí),等待隊(duì)列已滿。
這時(shí)候任務(wù)1執(zhí)行完了,釋放了一個(gè)線程1,就可以讓任務(wù)4出隊(duì)被處理。然后,任務(wù)6和7來了。此時(shí),三個(gè)核心線程都在工作,工作隊(duì)列還有一個(gè)空位。任務(wù)6去了工作隊(duì)列排隊(duì),任務(wù)7沒有位置坐。因?yàn)榫€程池最大可以放5個(gè)線程,只有核心線程池3個(gè)滿了,還有兩個(gè)位置可以放非核心線程,所以管理者會(huì)創(chuàng)建一個(gè)非核心線程,然后任務(wù)5出隊(duì),任務(wù)7入隊(duì)。
直到線程池全滿(5個(gè)),工作隊(duì)列也滿,新來的任務(wù)才會(huì)無法被處理。優(yōu)先使用線程池中的核心線程池。
40.3 創(chuàng)建線程池參數(shù)有哪些作用?
public ThreadPoolExecutor( int corePoolSize,
int maximumPoolSize,
long keepAliveTime,
TimeUnit unit,
BlockingQueue<Runnable> workQueue,
ThreadFactory threadFactory,
RejectedExecutionHandler handler)
:設(shè)置創(chuàng)建線程的工廠,可以通過線程工廠給每個(gè)創(chuàng)建出來的線程設(shè)置更有意義的名字。:飽和策略也叫拒絕策略。當(dāng)隊(duì)列和線程池都滿了,即達(dá)到飽和狀態(tài)。所以需要采取策略來處理新的任務(wù)。默認(rèn)策略是。 40.4 向線程池提交任務(wù)
可以使用()和()兩種方式提交任務(wù)。
40.5 關(guān)閉線程池
可以通過()或()來關(guān)閉線程池。它們的原理是遍歷線程池中的工作線程,然后逐個(gè)調(diào)用線程的來中斷線程,所以無法響應(yīng)終端的任務(wù)可以能永遠(yuǎn)無法停止。
首先將線程池狀態(tài)設(shè)置成STOP,然后嘗試停止所有的正在執(zhí)行或者暫停的線程,并返回等待執(zhí)行任務(wù)的列表。
只是將線程池的狀態(tài)設(shè)置成狀態(tài),然后中斷所有沒有正在執(zhí)行任務(wù)的線程。
只要調(diào)用兩者之一,就會(huì)返回true,當(dāng)所有任務(wù)都已關(guān)閉,就會(huì)返回true。
一般來說調(diào)用方法來關(guān)閉線程池,如果任務(wù)不一定要執(zhí)行完,可以直接調(diào)用方法。
40.6 線程池如何合理設(shè)置
配置線程池可以從以下幾個(gè)方面考慮。
任務(wù)優(yōu)先級(jí),高中低。任務(wù)時(shí)間執(zhí)行長(zhǎng)短。任務(wù)依賴性:是否依賴其他系統(tǒng)資源。
可以通過.().()來獲取cpu個(gè)數(shù)。建議使用有界隊(duì)列,增加系統(tǒng)的預(yù)警能力和穩(wěn)定性。
41、
從JDK5開始,把工作單元和執(zhí)行機(jī)制分開。工作單元包括和,而執(zhí)行機(jī)制由框架提供。
41.1 框架的主要成員 :可以通過工廠類來創(chuàng)建。有2種類型: 接口:和實(shí)現(xiàn)接口的類來表示異步計(jì)算的結(jié)果。和:它們的接口實(shí)現(xiàn)類都可以被或執(zhí)行。不能返回結(jié)果,可以返回結(jié)果。 41.2
查看源碼:
public static ExecutorService newFixedThreadPool(int nThreads) {
return new ThreadPoolExecutor(nThreads, nThreads,
0L, TimeUnit.MILLISECONDS,
new LinkedBlockingQueue<Runnable>());}
1、特點(diǎn)
2、使用無界隊(duì)列的影響
3、 處理流程
4、適用場(chǎng)景
適用于為了滿足資源管理的需求,而需要限制當(dāng)前線程數(shù)量的應(yīng)用場(chǎng)景,它適用于負(fù)載比較重的服務(wù)器。
41.3
查看源碼:
public static ExecutorService newSingleThreadExecutor() {
return new FinalizableDelegatedExecutorService
(new ThreadPoolExecutor(1, 1,
0L, TimeUnit.MILLISECONDS,
new LinkedBlockingQueue<Runnable>()));
}
1、特點(diǎn)
2、適用場(chǎng)景
適用于需要保證順序地執(zhí)行各個(gè)任務(wù),并且在任意時(shí)間點(diǎn),不會(huì)有多個(gè)線程是活動(dòng)的應(yīng)用場(chǎng)景。
3、工作流程
執(zhí)行流程以及造成的影響同。
41.4
查看源碼:
public static ExecutorService newCachedThreadPool() {
return new ThreadPoolExecutor(0, Integer.MAX_VALUE,
60L, TimeUnit.SECONDS,
new SynchronousQueue<Runnable>());
1、特點(diǎn)
2、工作流程
3、 適用場(chǎng)景
是無界的線程池,適用于執(zhí)行很多的短期異步任務(wù)的小程序,或者是負(fù)載較輕的服務(wù)器。