多線程基礎(chǔ)概念介紹
進(jìn)程
是程序或任務(wù)的執(zhí)行的過程,具有動(dòng)態(tài)性;
它持有資源(共享內(nèi)存,共享文件)和線程;
如:打開QQ、搜狗輸入法軟件時(shí),我們啟動(dòng)了兩個(gè)進(jìn)程。
線程
系統(tǒng)中最小的執(zhí)行單元。 比如一個(gè)軟件里邊的各種任務(wù)就是線程;
同一進(jìn)程中有多個(gè)線程;
線程共享進(jìn)程的資源。
多線程
一個(gè)進(jìn)程中可以開啟多條線程,多條線程可以并行(同時(shí))執(zhí)行不同的任務(wù);
多線程技術(shù)可以提高程序的執(zhí)行效率。
線程創(chuàng)建的兩種方式比較
- 繼承Thread類
public class ExtendThread {
public static void main(String[] args) {
MyThread mt = new MyThread();
mt.start();
// mt.start();
MyThread mt2 = new MyThread();
mt2.start();
}
}
class MyThread extends Thread{
@Override
public void run(){
System.out.println("使用extends創(chuàng)建線程!!");
}
}
- 實(shí)現(xiàn)Runnable接口
public class ImplementThread {
public static void main(String[] args) {
Mythread2 mt = new Mythread2();
Thread th = new Thread(mt);
th.start();
Thread th2 = new Thread(mt);
th2.start();
}
}
class Mythread2 implements Runnable{
@Override
public void run() {
System.out.println("使用implements創(chuàng)建線程!");
}
}
注意事項(xiàng):同一線程的start()方法不能重復(fù)調(diào)用
start()方法相關(guān)源碼如下:
public synchronized void start() {
if (threadStatus != 0) //start()方法不能被重復(fù)調(diào)用,否則會(huì)拋出異常
throw new IllegalThreadStateException();
/* Notify the group that this thread is about to be started
* so that it can be added to the group's list of threads
* and the group's unstarted count can be decremented. */
group.add(this); //加入到線程組中
boolean started = false;
try {
start0();
started = true;
} finally {
try {
if (!started) {
group.threadStartFailed(this);
}
} catch (Throwable ignore) {
/* do nothing. If start0 threw a Throwable then
it will be passed up the call stack */
}
}
}
}
private native void start0();
附:Thread常用方法介紹

兩種實(shí)現(xiàn)方式對比:
Runnable方式可以避免Thread方式由于Java單繼承特性帶來的缺陷
Runnable方式實(shí)現(xiàn)可以被多個(gè)線程共享,適合于多個(gè)線程處理同一資源的情況
線程的生命周期
下圖是線程生命周期的詳細(xì)狀態(tài)轉(zhuǎn)換圖(注意:不能從阻塞狀態(tài)直接到運(yùn)行狀態(tài))

下面將對每一個(gè)狀態(tài)進(jìn)行簡單的介紹
創(chuàng)建
新建一個(gè)線程對象,如Thread td = new Thread();就緒
創(chuàng)建線程對象之后,調(diào)用start()方法(注意:此時(shí)線程只是進(jìn)入了線程隊(duì)列,等待獲取CPU服務(wù),具備了運(yùn)行條件,但不一定已經(jīng)開始運(yùn)行了)運(yùn)行
處于就緒狀態(tài)的線程,一旦獲取CPU資源,就會(huì)進(jìn)入運(yùn)行狀態(tài),開始執(zhí)行run()方法里面的邏輯阻塞
一個(gè)正在執(zhí)行的線程在某些情況下,由于某種原因而暫時(shí)讓出了CPU資源,暫停了自己的執(zhí)行,便進(jìn)入了阻塞狀態(tài)。如:調(diào)用了sleep()方法終止
線程的run()方法執(zhí)行完畢,或者線程調(diào)用了stop()方法(現(xiàn)在已經(jīng)不推薦使用這種方式),線程便進(jìn)入了終止?fàn)顟B(tài)
附:更加詳細(xì)的線程狀態(tài)轉(zhuǎn)換圖

守護(hù)線程介紹
Java線程有兩類
用戶線程
運(yùn)行在前臺(tái),執(zhí)行具體的任務(wù)。如:用戶的主線程,連接網(wǎng)絡(luò)的子線程等守護(hù)線程
運(yùn)行在后臺(tái),為其他前臺(tái)線程服務(wù)。
特點(diǎn):
一旦所有用戶線程都結(jié)束運(yùn)行,守護(hù)線程也會(huì)隨JVM一起結(jié)束工作
應(yīng)用:
數(shù)據(jù)庫連接池中的監(jiān)測線程
JVM虛擬機(jī)啟動(dòng)后的監(jiān)測線程
常見:
垃圾回收線程
使用方法:
調(diào)用Thread類的setDaemon(true)方法來設(shè)置當(dāng)前線程為守護(hù)線程
注意事項(xiàng):
①setDaemon(true)必須在start()方法之前調(diào)用,否則會(huì)拋出異常IllegalThreadStateException
②在守護(hù)線程中產(chǎn)生的新線程也是守護(hù)線程
③不是所有的任務(wù)都可已分配給守護(hù)線程來執(zhí)行,如:讀寫操作或計(jì)算邏輯
內(nèi)存可見性
原子性
跟數(shù)據(jù)庫事務(wù)的原子性概念差不多,即一個(gè)操作(有可能包含有多個(gè)子操作)要么全部執(zhí)行(生效),要么全部都不執(zhí)行(都不生效)。
可見性
一個(gè)線程對共享變量值的修改,能夠及時(shí)地被其他線程看到。
共享變量
如果一個(gè)變量在多個(gè)線程的工作內(nèi)存中都存在副本,那么這個(gè)變量就是這幾個(gè)線程的共享變量。
Java內(nèi)存模型(JMM)
描述了Java程序中各種變量(線程共享變量)的訪問規(guī)則,以及在JVM中將變量存儲(chǔ)到內(nèi)存和從內(nèi)存中讀取出變量這樣的底層細(xì)節(jié)。

注意:
- 所有的變量都存儲(chǔ)在主內(nèi)存中
- 每個(gè)線程都有自己獨(dú)立的工作內(nèi)存,里面保存該線程使用到的變量的副本(即主內(nèi)存中共享變量的副本)
共享變量可見性實(shí)現(xiàn)原理

線程1對共享變量的修改想要被線程2及時(shí)看到,必須經(jīng)過以下兩個(gè)步驟:
- 把工作內(nèi)存1中更新過的共享變量刷新到主內(nèi)存中
- 將主內(nèi)存中最新的共享變量的值更新到工作內(nèi)存2中
Java語言層面實(shí)現(xiàn)可見性的兩種方式
- synchronized
synchronized 關(guān)鍵字,代表這個(gè)方法加鎖,相當(dāng)于不管哪一個(gè)線程A每次運(yùn)行到這個(gè)方法時(shí),都要檢查有沒有其它正在用這個(gè)方法的線程B(或者C D等),有的話要等正在使用這個(gè)方法的線程B(或者C D)運(yùn)行完這個(gè)方法后再運(yùn)行此線程A,如果沒有其他線程正在用這個(gè)方法則線程A可以運(yùn)行這個(gè)方法。
線程執(zhí)行互斥代碼的過程:
①獲得互斥鎖
②清空工作內(nèi)存
③從主內(nèi)存拷貝變量的最新副本到工作內(nèi)存
④執(zhí)行代碼
⑤將更改后的共享變量的值刷新到主內(nèi)存
⑥釋放互斥鎖
synchronized 關(guān)鍵字的兩種用法
①synchronized 方法
通過在方法聲明中加入 synchronized關(guān)鍵字來聲明 synchronized 方法。如:
public synchronized void accessVal(int newVal);
②synchronized 塊
通過 synchronized關(guān)鍵字來聲明synchronized 塊。語法如下:
synchronized(syncObject)
{
//允許訪問控制的代碼
}
對synchronized (this)的一些理解(this指的是調(diào)用這個(gè)方法的對象)
- 當(dāng)兩個(gè)并發(fā)線程訪問同一個(gè)對象object中的這個(gè)synchronized(this)同步代碼塊時(shí),一個(gè)時(shí)間內(nèi)只能有一個(gè)線程得到執(zhí)行。另一個(gè)線程必須等待當(dāng)前線程執(zhí)行完這個(gè)代碼塊以后才能執(zhí)行該代碼塊。
- 然而,當(dāng)一個(gè)線程訪問object的一個(gè)synchronized(this)同步代碼塊時(shí),另一個(gè)線程仍然可以訪問該object中的非synchronized(this)同步代碼塊。
- 尤其關(guān)鍵的是,當(dāng)一個(gè)線程訪問object的一個(gè)synchronized(this)同步代碼塊時(shí),其他線程對object中所有其它synchronized(this)同步代碼塊的訪問將被阻塞。
- 第三個(gè)例子同樣適用其它同步代碼塊。也就是說,當(dāng)一個(gè)線程訪問object的一個(gè)synchronized(this)同步代碼塊時(shí),它就獲得了這個(gè)object的對象鎖。結(jié)果,其它線程對該object對象所有同步代碼部分的訪問都被暫時(shí)阻塞。
- 以上規(guī)則對其它對象鎖同樣適用。
注意:
線程加鎖時(shí),將清空工作內(nèi)存中共享變量的值,從而使用共享變量時(shí)需要從主內(nèi)存重新讀取最新的值
線程解鎖時(shí),必須把共享變量的最新值刷新到主內(nèi)存中
- volatile
能夠保證volatile變量的可見性;不能保證volatile變量復(fù)合操作的原子性
實(shí)現(xiàn)方式:
通過加入內(nèi)存屏障和禁止重排序優(yōu)化實(shí)現(xiàn)
①對volatile變量執(zhí)行寫操作時(shí),會(huì)在寫操作后加入一條store屏障指令
②對volatile變量執(zhí)行讀操作時(shí),會(huì)在讀操作前加入一條load屏障指令
線程寫volatile變量的過程
①改變線程工作內(nèi)存中volatile變量副本的值
②將改變后的副本的值從工作內(nèi)存刷新到主內(nèi)存
線程讀volatile變量的過程:
①從主內(nèi)存中讀取volatile變量的最新值到線程的工作內(nèi)存中
②從工作內(nèi)存中讀取volatile變量的副本的值
適用場合:
①對變量的寫操作不依賴其當(dāng)前值
不滿足:number++、count = count+5
滿足:boolean變量,記錄溫度變化的變量
②變量沒有包含在具有其它變量的不變式中
不滿足:不變式 low < up
兩種實(shí)現(xiàn)方式比較
- volatile不用加鎖,比synchronized更加輕量級,不會(huì)阻塞線程;
- 從內(nèi)存可見性分析,volatile讀操作相當(dāng)于加鎖,volatile寫操作相當(dāng)于解鎖;
- synchronized既能保證可見性,又能夠保證原子性;而volatile只能保證可見性,不能夠保證原子性
參考資料
[1]Java總結(jié)篇系列:Java多線程(一)
[2]深入理解java中的synchronized關(guān)鍵字
[3]java中synchronized的用法詳解
補(bǔ)充問題
1.Thread類的yield方法作用
yield()應(yīng)該做的是讓當(dāng)前運(yùn)行線程回到可運(yùn)行狀態(tài),只允許具有相同優(yōu)先級的其他線程獲得運(yùn)行機(jī)會(huì)。因此,使用yield()的目的是讓相同優(yōu)先級的線程之間能適當(dāng)?shù)妮嗈D(zhuǎn)執(zhí)行。但是,實(shí)際中無法保證yield()達(dá)到讓步目的,因?yàn)樽尣降木€程還有可能被線程調(diào)度程序再次選中。
注意:sleep()可以使低優(yōu)先級的線程得到執(zhí)行的機(jī)會(huì),當(dāng)然也可以讓同優(yōu)先級、高優(yōu)先級的線程有執(zhí)行的機(jī)會(huì)。而yield()方法只能讓同優(yōu)先級的線程有執(zhí)行的機(jī)會(huì)。
2.為什么每個(gè)線程要使用工作內(nèi)存,而不直接訪問主內(nèi)存
①為了減少各線程對主內(nèi)存的頻繁訪問,各線程只需要訪問自己的工作內(nèi)存即可以獲取共享變量的副本值,將訪問壓力分?jǐn)偟礁骶€程的工作內(nèi)存中,主內(nèi)存只需要按照一定的規(guī)則同步共享變量的值到各線程的工作內(nèi)存。
注意:
“synchronized” — 保證在塊開始時(shí)都同步主內(nèi)存的值到工作內(nèi)存,而塊結(jié)束時(shí)將變量同步回主內(nèi)存
3.happens-before原則