
一、基礎(chǔ)概念
1.1、CPU核心數(shù)和線程數(shù)的關(guān)系
多核心 :單芯片多處理器( Chip Multiprocessors,簡稱CMP),其思想是將大規(guī)模并行處理器中的SMP(對稱多處理器)集成到同一芯片內(nèi),各個處理器并行執(zhí)行不同的進程。這種依靠多個CPU同時并行地運行程序是實現(xiàn)超高速計算的一個重要方向,稱為并行處理,
多線程 :讓同一個處理器上的多個線程同步執(zhí)行并共享處理器的執(zhí)行資源,可最大限度地實現(xiàn)寬發(fā)射、亂序的超標(biāo)量處理,提高處理器運算部件的利用率,緩和由于數(shù)據(jù)相關(guān)或 Cache未命中帶來的訪問內(nèi)存延時。
二者關(guān)系 : 目前CPU基本都是多核,很少看到單核CPU。增加核心數(shù)目就是為了增加線程數(shù),因為操作系統(tǒng)是通過線程來執(zhí)行任務(wù)的,一般情況下它們是1:1對應(yīng)關(guān)系,也就是說四核CPU一般擁有四個線程。但Intel引入超線程技術(shù)后,使核心數(shù)與線程數(shù)形成1:2的關(guān)系.
1.2、時間片輪轉(zhuǎn)機制 (RR 調(diào)度)
定義:系統(tǒng)把所有就緒進程按先入先出的原則排成一個隊列。新來的進程加到就緒隊列末尾。每當(dāng)執(zhí)行進程調(diào)度時,進程調(diào)度程序總是選出就緒隊列的隊首進程,讓它在CPU上運行一個時間片的時間。時間片是一個小的時間單位,通常為10~100ms數(shù)量級。當(dāng)進程用完分給它的時間片后,系統(tǒng)的計時器發(fā)出時鐘中斷,調(diào)度程序便停止該進程的運行,把它放入就緒隊列的末尾;然后,把CPU分給就緒隊列的隊首進程,同樣也讓它運行一個時間片,如此往復(fù)。
根據(jù)上面CPU核心數(shù)和線程數(shù)的關(guān)系 1:1的關(guān)系,如果我們手機是雙核手機,那么我們按道理只能起兩個線程,但是在實際的開發(fā)過程中并不是這樣,我們可能開了十幾個線程 "同時" 在執(zhí)行,這是因為操作系統(tǒng)提供了CPU時間片輪轉(zhuǎn)這個機制,它為每個進程分配一個時間段(即時間片),讓他們在一段時間內(nèi)交替執(zhí)行。
上下文切換時間:由于時間片輪轉(zhuǎn)進制,會使得進程之間不停的進行切換,進程之間切換涉及到保存和裝入到寄存器值及內(nèi)存映像,更新表格及隊列,這個過程是需要消耗時間的。
時間片時間設(shè)置: 時間片如果設(shè)置太短,會導(dǎo)致過多進程不斷切換,由于切換過程會產(chǎn)生上小文切換時間,所以降低CPU效率,設(shè)置太長,又會導(dǎo)致相對較短的交互請求響應(yīng)變差,通常時間片設(shè)置在100ms左右比較合理。
1.3、進程和線程
1.3.1、什么是進程?
進程是程序運行資源分配的最小單元
進程是操作系統(tǒng)進行資源分配和調(diào)度的獨立單元,資源包括CPU,內(nèi)存空間,磁盤IO等等,同一個進程的所有線程共享該進程的全部資源,進程與進程之間相互獨立。
1.3.2、什么是線程?
線程是CPU調(diào)度的最小單位,必須依賴進程而存在。
線程是進程的實體,是CPU調(diào)度和分派的基本單位,線程基本不擁有系統(tǒng)資源,但是擁有程序計數(shù)器、一組寄存器、棧等運行中不可少的資源,同一個進程中的線程共享進程所擁有的全部資源。
1.4、 并發(fā)與并行
1.4.1、什么是并發(fā)
并發(fā)是指一個時間段內(nèi),有幾個程序都在同一個CPU上運行,但任意一個時刻點上只有一個程序在處理機上運行。
多個線程 一個CPU
跟時間掛鉤,單位時間內(nèi)。
1.4.2、什么是并行
并行是指一個時間段內(nèi),有幾個程序都在幾個CPU上運行,任意一個時刻點上,有多個程序在同時運行,并且多道程序之間互不干擾。
多個線程 多個CPU
1.5、同步與異步
1.5.1、什么是同步
同步:在發(fā)出一個同步調(diào)用時,在沒有得到結(jié)果之前,該調(diào)用就不返回,直到結(jié)果的返回。
好比我給朋友打電話,你要不接電話,我就一直打,這個過程啥也不干,就給你打電話,打到你接電話為止。
1.5.2、什么是異步
異步:在發(fā)出一個異步調(diào)用后,調(diào)用者不會立刻得到結(jié)果,該調(diào)用就返回了。
同樣打電話,我先給你發(fā)個消息,告訴我有事找你,然后我就去干我自己的事情去了,等你看到消息給我回電話,當(dāng)然,你也可以不回我電話。
二、多線程使用
2.1、創(chuàng)建多線程
2.1.1、實現(xiàn)Runnable接口
public static class newRunnable implements Runnable {
@Override
public void run() {
System.out.println("Runnable");
}
}
調(diào)用:
new Thread(new newRunnable()).start();
2.1.2、繼承Thread類
public static class newThread extends Thread {
@Override
public void run() {
super.run();
System.out.println("newThread");
}
}
調(diào)用:
new newThread().start();
1、Thread 是java里面對線程的抽象概念,我們通過new thread的時候,其實只是創(chuàng)建了一個thread實例,操作系統(tǒng)并沒有和該線程掛鉤,只有執(zhí)行了start方法后,才是真正意義上啟動了線程。
2、start() 會讓一個線程進入就緒隊列等待分配CPU,分到CPU后才調(diào)用 run() 方法,
3、start() 方法不能重復(fù)調(diào)用,否則會拋出
IllegalThreadStateException異常。
2.1.3、實現(xiàn)Callable<V>接口
Callable接口 是在Java1.5開始提供,可以在任務(wù)執(zhí)行結(jié)束后提供返回值。
public interface Callable<V> {
/**
* Computes a result, or throws an exception if unable to do so.
*
* @return computed result
* @throws Exception if unable to compute a result
*/
V call() throws Exception;
}
從源碼可以看到,Callable 跟 Runnable 對比來看,不同點就在其call方法提供了返回值和進行異常拋出。
使用:
public static class newCallable implements Callable<String> {
@Override
public String call() throws Exception {
System.out.println("newCallable");
Thread.sleep(3000);
return "java1.5后提供,可在任務(wù)執(zhí)行結(jié)束返回相應(yīng)結(jié)果";
}
}
對Callable的調(diào)用需要 FutureTask 這個類,這個類也是 Java1.5 以后提供
FutureTask<String> futureTask = new FutureTask<>(new newCallable());
futureTask.run();
String result = null;
try {
result = futureTask.get();
} catch (InterruptedException | ExecutionException e) {
e.printStackTrace();
}
執(zhí)行結(jié)果:

可以看到,我們通過 FutureTask 的 get 方法 ,get 方法會進行阻塞,直到任務(wù)結(jié)束后,才將返回值進行返回。
2.2、終止線程
2.2.1、 自然終止
線程任務(wù)執(zhí)行完成,則這個線程進行終止。
2.2.2、手動終止
暫停、恢復(fù)和停止操作對應(yīng)在線程Thread的API就是suspend()、resume()和stop()。但是這些API是過期的,也就是不建議使用的,主要原因是方法的調(diào)用不能保證線程資源的正常釋放,容易引起其他副作用的產(chǎn)生。

suspend() :在調(diào)用后,線程不會釋放已經(jīng)占有的資源(比如鎖),而是占有著資源進入睡眠狀態(tài),這樣容易引發(fā)死鎖問題
stop() : 終結(jié)一個線程時不會保證線程的資源正常釋放,通常是沒有給予線程完成資源釋放工作的機會,因此會導(dǎo)致程序可能工作在不確定狀態(tài)下
真正安全的終止線程使用 interrupt() 方法

由于線程之間是協(xié)作式工作,所以在其他線程使用 interrupt() 終止某個線程時候,這個線程并不會立即終止,只是收到了終止通知,通過檢查自身的中斷標(biāo)志位是否為被置為 True 再進行相應(yīng)的操作,當(dāng)然,這個線程完全可以不用理會。
中斷標(biāo)志位的判斷:
1、isInterrupted()
判斷線程是否中斷,如果該線程已被中斷則返回 true 否則返回 false
/**
* Tests whether this thread has been interrupted. The <i>interrupted
* status</i> of the thread is unaffected by this method.
*
* <p>A thread interruption ignored because a thread was not alive
* at the time of the interrupt will be reflected by this method
* returning false.
*
* @return <code>true</code> if this thread has been interrupted;
* <code>false</code> otherwise.
* @see #interrupted()
* @revised 6.0
*/
public boolean isInterrupted() {
return isInterrupted(false);
}
2、interrupted()
判斷線程是否中斷,如果該線程已被中斷返回 true,狀態(tài)返回后該方法會清除中斷標(biāo)志位,重新置為 false,當(dāng)?shù)诙卧俅握{(diào)用的時候又會返回 false ,(除非重新調(diào)用 interrupt()進行中斷 )
/**
* Tests whether the current thread has been interrupted. The
* <i>interrupted status</i> of the thread is cleared by this method. In
* other words, if this method were to be called twice in succession, the
* second call would return false (unless the current thread were
* interrupted again, after the first call had cleared its interrupted
* status and before the second call had examined it).
*
* <p>A thread interruption ignored because a thread was not alive
* at the time of the interrupt will be reflected by this method
* returning false.
*
* @return <code>true</code> if the current thread has been interrupted;
* <code>false</code> otherwise.
* @see #isInterrupted()
* @revised 6.0
*/
public static boolean interrupted() {
return currentThread().isInterrupted(true);
}
下面通過一個Demo演示 isInterrupted() 和 interrupted() 的區(qū)別
isInterrupted:
private static class UseThread extends Thread{
public UseThread(String name) {
super(name);
}
@Override
public void run() {
String threadName = Thread.currentThread().getName();
System.out.println(threadName+" interrupt start flag ="+isInterrupted());
while(!isInterrupted()){
System.out.println(threadName+" is running");
System.out.println(threadName+" inner interrupt flag ="+isInterrupted());
}
System.out.println(threadName+" interrupt end flag ="+isInterrupted());
}
}
運行上面的程序:
開啟線程,休眠一微秒后調(diào)用 interrupt() 進行中斷
public static void main(String[] args) throws InterruptedException {
Thread endThread = new UseThread("test isInterrupted");
endThread.start();
Thread.sleep(1);
endThread.interrupt();
}
結(jié)果:

可以看到 UseThread 的 isInterrupted() 一直為 false ,當(dāng)主線程執(zhí)行 endThread.interrupt() 中斷方法后,其中斷標(biāo)志被置為 true ,跳出循環(huán),結(jié)束 run 方法。我們后續(xù)再調(diào)用 isInterrupted() 方法打印中斷標(biāo)志的值一直為 true,并沒有更改。
interrupted():
我們簡單改了一下代碼:
private static class UseThread extends Thread{
public UseThread(String name) {
super(name);
}
@Override
public void run() {
String threadName = Thread.currentThread().getName();
while(!Thread.interrupted()){
System.out.println(threadName+" is running");
}
System.out.println(threadName+" interrupted end flag ="+Thread.interrupted());
}
}
可以看到,run 方法里面一直循環(huán)執(zhí)行,直到線程被中斷,結(jié)束后我們再次調(diào)用了打印了 Thread.interrupted() 值
調(diào)用:
public static void main(String[] args) throws InterruptedException {
Thread endThread = new UseThread("test interrupted");
endThread.start();
Thread.sleep(1);
endThread.interrupt();
}
同樣休眠一微秒后進行中斷操作。
結(jié)果:

我們再分析一下,前面線程結(jié)束循環(huán)的條件是 Thread.interrupted() 為 true , 但是當(dāng)線程結(jié)束循環(huán)后,我們再次調(diào)用 Thread.interrupted() 方法,發(fā)現(xiàn)其值為又被置為 false ,說明 Thread.interrupted() 執(zhí)行后,會清除 中斷標(biāo)志位,并將其重新置為 false 。
注意:處于死鎖狀態(tài)的線程無法被中斷
如果一個線程處于了阻塞狀態(tài)(如線程調(diào)用了thread.sleep、thread.join、thread.wait),則在線程在檢查中斷標(biāo)示時如果發(fā)現(xiàn)中斷標(biāo)示為true,則會在這些阻塞方法調(diào)用處拋出InterruptedException異常,并且在拋出異常后會立即將線程的中斷標(biāo)示位清除,即重新設(shè)置為false。
我們在前面 isInterrupted 演示的 Demo中 進行修改
private static class UseThread extends Thread {
public UseThread(String name) {
super(name);
}
@Override
public void run() {
String threadName = Thread.currentThread().getName();
System.out.println(threadName + " interrupt start flag =" + isInterrupted());
while (!isInterrupted()) {
try {
// 線程進行休眠3秒
Thread.sleep(3000);
} catch (InterruptedException e) {
e.printStackTrace();
System.out.println("sleep error=" + e.getLocalizedMessage());
}
System.out.println(threadName + " is running");
System.out.println(threadName + " inner interrupt flag =" + isInterrupted());
}
System.out.println(threadName + " interrupt end flag =" + isInterrupted());
}
}
我們再執(zhí)行前面的方法的時候,結(jié)果:

我們可以看到,即使拋了異常,但是線程依舊在執(zhí)行,這個時候標(biāo)志位還有沒有置為true,所以我們要注意
拋出InterruptedException異常的時候 對中斷標(biāo)志位的操作,我們改一下代碼,在catch中再次執(zhí)行 interrupt()來中斷任務(wù)
try {
// 線程進行休眠3秒
Thread.sleep(3000);
} catch (InterruptedException e) {
e.printStackTrace();
// 在catch方法中,執(zhí)行interrupt() 方法中斷任務(wù)。
interrupt();
System.out.println("sleep error=" + e.getLocalizedMessage());
}
結(jié)果:

三、線程之間共享和協(xié)作
3.1、 線程之間共享
前面說過,同一個進程的所有線程共享該進程的全部資源,共享資源就會導(dǎo)致一個問題,當(dāng)多個線程同時訪問一個對象或者一個對象的成員變量,可能會導(dǎo)致數(shù)據(jù)不同步問題,比如 線程A 對數(shù)據(jù)a進行操作,需要從內(nèi)存中進行讀取然后進行相應(yīng)的操作,操作完成后再寫入內(nèi)存中,但是如果數(shù)據(jù)還沒有寫入內(nèi)存中的時候,線程B 也來對這個數(shù)據(jù)進行操作,取到的就是還未寫入內(nèi)存的數(shù)據(jù),導(dǎo)致前后數(shù)據(jù)同步問題。
為了處理這個問題,Java 中引入了關(guān)鍵字 synchronized ( 下一篇文章單獨講)。
3.2、線程之間的協(xié)作
線程之間可以相互配合,共同完成一項工作,比如線程A修改了某個值,這個時候需要通知另一個線程再執(zhí)行后續(xù)操作,整個過程開始與一個線程,最終又再另一個線程執(zhí)行,前者是生產(chǎn)者,后者就是消費者。
3.2.1、 nitify()、notifyAll()、wait() 等待/通知機制
是指一個線程A調(diào)用了對象O的 wait() 方法進入等待狀態(tài),而另一個線程B調(diào)用了對象O的notify()或者notifyAll()方法,線程A收到通知后從對象O的wait()方法返回,進而執(zhí)行后續(xù)操作。上述兩個線程通過對象O來完成交互,而對象上的wait()和notify、notifyAll()的關(guān)系就如同開關(guān)信號一樣,用來完成等待方和通知方之間的交互工作。
我們知道 Object 類是所有類的父類,而 Object 類中就存在相關(guān)方法

notify():
通知一個在對象上等待的線程,使其從wait方法返回,而返回的前提是該線程獲取到了對象的鎖,沒有獲得鎖的線程重新進入WAITING狀態(tài)。
notifyAll():
通知所有等待在該對象上的線程
wait()
調(diào)用該方法的線程進入 WAITING狀態(tài),只有等待另外線程的通知或被中斷才會返回.需要注意,調(diào)用wait()方法后,會釋放對象的鎖
wait(long)
超時等待一段時間,這里的參數(shù)時間是毫秒,也就是等待長達n毫秒,如果沒有通知就超時返回
wait (long,int)
對于超時時間更細粒度的控制,可以達到納秒
下面通過案例說明,雙十一的時候,你購買了三件商品,你在家焦急的等待,沒事就刷新一下手機看商品快遞信息,我們就來模擬一個快遞信息的更新,這里以地點變化進行數(shù)據(jù)更新:
public class NwTest {
// 發(fā)貨地點
public String location = "重慶";
// 所有貨物在不同一趟車上,貨物到了下一站,分別更新對應(yīng)的快遞信息
public synchronized void changeLocationNotify(String location) {
this.location = location;
this.notify();
}
// 所有貨物在同一趟快遞車上,貨物到了下一站,全部信息更新。
public synchronized void changeLocationNotifyAll(String location) {
this.location = location;
System.out.println("changeLocationNotifyAll");
this.notifyAll();
}
public static class LocationThread extends Thread {
public final NwTest mNwTest;
public LocationThread(NwTest nwTest) {
this.mNwTest = nwTest;
}
@Override
public void run() {
super.run();
try {
synchronized (mNwTest) {
System.out.println("LocationThread current location : " + mNwTest.location);
// 等待位置更新
mNwTest.wait();
String name = Thread.currentThread().getName();
// 獲取當(dāng)前商品的商家信息
System.out.println("LocationThread——>current thread name : " + name);
}
} catch (InterruptedException e) {
e.printStackTrace();
}
// 獲取更新后的位置
System.out.println("LocationThread update location : " + mNwTest.location);
}
}
注意:
只能在同步方法或者同步塊中使用
wait()、notifyAll()、notify()方法否則會拋
IllegalMonitorStateException異常
調(diào)用:
public static void main(String[] args) throws InterruptedException {
NwTest nwTest = new NwTest();
for (int x = 0; x < 3; x++) {
new LocationThread(nwTest).start();
}
// 模擬三天后
Thread.sleep(3000);
// 通知單個商品信息進行更新
nwTest.changeLocationNotify("合肥");
}
我們啟動了三個線程,模擬了你購買的三件貨物,如果使用 notify() ,只是使單個商品進行信息更新
結(jié)果:

我們看到三個貨物同時發(fā)貨,其中 Thread_0 最先到達合肥,并進行了數(shù)據(jù)更新。
如果使用 notifyAll() ,所有商品快遞信息都會刷新。
public static void main(String[] args) throws InterruptedException {
NwTest nwTest = new NwTest();
for (int x = 0; x < 3; x++) {
new LocationThread(nwTest).start();
}
Thread.sleep(3000);
// 通知三件商品進行信息更新
nwTest.changeLocationNotifyAll("合肥");
}
結(jié)果:

這就是 notifyAll() 、 notify() 、wait() 基本使用,其中 wait(long) 表示線程會等待 n 毫秒,如果這個時間段內(nèi)沒有收到 notifyAll() 或者 notify() 就自動執(zhí)行后續(xù)方法。
根據(jù)上面的Demo,我們可以整理一下 等待和通知的標(biāo)準(zhǔn)范式
wait():
1)獲取對象的鎖。
2)根據(jù)判斷條件調(diào)用 wait() 方法。
3)條件滿足則執(zhí)行對應(yīng)的邏輯。

notify() 或者 notifyAll()
1)獲得對象的鎖。
2)改變條件,發(fā)送通知。
3)通知所有等待在對象上的線程。

以上主要是整理的多線程的一些基本概念,還有 notify()和wait() 的基本使用,關(guān)鍵字 synchronized 準(zhǔn)備下一篇單獨整理,后續(xù)計劃整理線程池相關(guān)知識以及Android 中 AsyncTask 的源碼分析,喜歡的話點個贊唄!