總覽
- 多線程概述
- 多線程的實(shí)現(xiàn)方式
- 線程控制
- 線程的聲明周期
- 線程同步
- 死鎖問(wèn)題
- 線程間通信機(jī)制(等待喚醒機(jī)制)
- 線程池
- 定時(shí)器
- 與Collection間關(guān)系
1. 多線程概述
什么是多線程揣钦?為什么要使用多線程?
了解多線程必須先了解線程漠酿,要了解線程冯凹,必須先了解進(jìn)程。因?yàn)榫€程是依賴(lài)于進(jìn)程存在的炒嘲。
多進(jìn)程:一邊玩游戲宇姚,一邊聽(tīng)音樂(lè)匈庭。(多進(jìn)程的意義:充分利用計(jì)算機(jī)資源)
單核CPU:在同一時(shí)刻只能執(zhí)行一條指令。系統(tǒng)在不同進(jìn)程之間進(jìn)行高速的切換(時(shí)間片空凸,一般是毫秒級(jí))嚎花,給人類(lèi)一種錯(cuò)覺(jué),好像計(jì)算機(jī)在同一時(shí)刻只執(zhí)行一個(gè)程序
并發(fā):在同一時(shí)間段內(nèi)呀洲,執(zhí)行多個(gè)程序紊选,在同一時(shí)刻只執(zhí)行一個(gè)程序
并行:在同一時(shí)刻執(zhí)行多個(gè)程序
Java命令會(huì)啟動(dòng)JVM,即啟動(dòng)了一個(gè)進(jìn)程(JVM 就是一個(gè)進(jìn)程)
該進(jìn)程會(huì)啟動(dòng)一個(gè)主線程道逗,然后主線程調(diào)用某個(gè)類(lèi)的main方法兵罢,所以main方法都是運(yùn)行在主線程里。(之前我們寫(xiě)的基本都是單線程程序 )
JVM是單線程還是多線程滓窍?
JVM是多線程的卖词,至少會(huì)創(chuàng)建一個(gè)main線程和 一個(gè)GC線程。
幾個(gè)JVM吏夯? 每個(gè)Java進(jìn)程 分配一jvm個(gè)實(shí)例
Java 字節(jié)碼 解釋器此蜈。管理內(nèi)存。
- 進(jìn)程:
正在運(yùn)行的程序噪生,是系統(tǒng)進(jìn)行資源分配和調(diào)用的獨(dú)立單位裆赵。
每一個(gè)進(jìn)程都有它自己的內(nèi)存空間和系統(tǒng)資源。 - 線程:
進(jìn)程的執(zhí)行路徑(任務(wù))
一個(gè)進(jìn)程如果只有一條執(zhí)行路徑跺嗽,則稱(chēng)為 單線程程序战授。
一個(gè)進(jìn)程如果有多條執(zhí)行路徑,則稱(chēng)為多線程程序(意義:提高程序的執(zhí)行率)桨嫁。
為什么要用多線程:
①. 為了更好的利用cpu的資源植兰,如果只有一個(gè)線程,則第二個(gè)任務(wù)必須等到第一個(gè)任務(wù)結(jié)束后才能進(jìn)行璃吧,如果使用多線程則在主線程執(zhí)行任務(wù)的同時(shí)可以執(zhí)行其他任務(wù)楣导,而不需要等待;
②. 進(jìn)程之間不能共享數(shù)據(jù)肚逸,線程可以爷辙;
③. 系統(tǒng)創(chuàng)建進(jìn)程需要為該進(jìn)程重新分配系統(tǒng)資源,創(chuàng)建線程代價(jià)比較须佟膝晾;
④. Java語(yǔ)言?xún)?nèi)置了多線程功能支持,簡(jiǎn)化了java多線程編程务冕。
2. 多線程的實(shí)現(xiàn)方式
一血当、繼承Thread
二、實(shí)現(xiàn)Runnable接口
三、線程池 (實(shí)現(xiàn)Callable接口臊旭,帶返回值
//1. 寫(xiě)一個(gè)類(lèi)繼承Thread
public class MyThread extends Thread{
@Override
public void run() {
// super.run(); 沒(méi)有做任何事情
// 如果只是一些簡(jiǎn)單的任務(wù)落恼,沒(méi)有必要新建一個(gè)線程。因?yàn)榫€程的創(chuàng)建和銷(xiāo)毀要消耗一定的系統(tǒng)資源离熏。
// 線程里面執(zhí)行的往往是一些比較耗時(shí)的任務(wù)佳谦。
// System.out.println("Hello, Thread");
// 2. 重寫(xiě)run方法
for (int i = 0; i < 100; i++) {
System.out.println(getName() + ": " + i);
try {
Thread.sleep(100);
} catch (InterruptedException e) {
e.printStackTrace();
}
}
}
}
Thread:
線程是程序中的執(zhí)行線程。Java 虛擬機(jī)允許應(yīng)用程序并發(fā)地運(yùn)行多個(gè)執(zhí)行線程滋戳。
方法一:
1. 寫(xiě)一個(gè)類(lèi)繼承Thread钻蔑。
2. 重寫(xiě)run()方法。
3. 創(chuàng)建子類(lèi)對(duì)象
4. 啟動(dòng)線程
幾個(gè)小問(wèn)題:
1. 為什么要重寫(xiě)run()方法
把線程要執(zhí)行的代碼封裝到run()里面奸鸯。
2. 啟動(dòng)線程使用的是那個(gè)方法?
start()
3. 線程能不能多次啟動(dòng)?
不能多次啟動(dòng)咪笑,如果啟動(dòng)多次會(huì)報(bào) IllegalThreadStateException。
4. run()和start()方法的區(qū)別
run(): 封裝線程要執(zhí)行的代碼娄涩,直接調(diào)用相當(dāng)于普通的調(diào)用窗怒,不會(huì)創(chuàng)建新的線程。
start(): 新建一個(gè)線程蓄拣,該線程執(zhí)行run().
*/
public class ThreadDemo1 {
public static void main(String[] args) {
// 3. 創(chuàng)建子類(lèi)對(duì)象
Thread thread = new MyThread();
// 4. 啟動(dòng)線程
// 直接執(zhí)行run()方法相當(dāng)于簡(jiǎn)單的方法調(diào)用
// thread.run();
// 啟動(dòng)線程:start(), start()會(huì)調(diào)用系統(tǒng)的API創(chuàng)建一個(gè)新的線程扬虚,該線程會(huì)執(zhí)行該對(duì)象的run()方法
thread.start();
thread.start();
for (int i = 100; i < 2000; i++) {
System.out.println(i);
}
}
}
如何獲取線程的名字:
構(gòu)造方法:
Thread(String name)
String getName()
void setName(String name)
問(wèn)題:如何獲取主線程的名字呢?
static Thread currentThread()
返回對(duì)當(dāng)前正在執(zhí)行的線程對(duì)象的引用球恤。
有了方式一孔轴,為什么還需要方式二呢?
1. 解決Java的單繼承的局限性
2. 便于多線程共享數(shù)據(jù)
3. 將任務(wù)和線程分離碎捺,降低耦合性。
3. 線程的控制贷洲、調(diào)度
線程睡眠
static void sleep(long millis)
在指定的毫秒數(shù)內(nèi)讓當(dāng)前正在執(zhí)行的線程休眠(暫停執(zhí)行)
static void sleep(long millis, int nanos)
在指定的毫秒數(shù)加指定的納秒數(shù)內(nèi)讓當(dāng)前正在執(zhí)行的線程休眠(暫停執(zhí)行)線程加入
void join()
等待該線程終止
void join(long millis)
等待該線程終止的時(shí)間最長(zhǎng)為 millis 毫秒收厨。
void join(long millis, int nanos)
等待該線程終止的時(shí)間最長(zhǎng)為 millis 毫秒 + nanos 納秒。線程禮讓
static void yield()
暫停當(dāng)前正在執(zhí)行的線程對(duì)象优构,并執(zhí)行其他線程
當(dāng)前線程放棄執(zhí)行權(quán)诵叁,重新加入搶奪CPU的執(zhí)行權(quán)的行列。-
守護(hù)線程
void setDaemon(boolean on)
如果on為true, 將該線程標(biāo)記為守護(hù)線程钦椭。
守護(hù)線程:被守護(hù)的線程如果結(jié)束了拧额,那么守護(hù)也會(huì)結(jié)束。問(wèn)題:
誰(shuí)守護(hù)誰(shuí)彪腔?
b.setDaemon(true), 將b為守護(hù)線程, 守護(hù)的對(duì)象:哪個(gè)線程運(yùn)行這行代碼侥锦,就守護(hù)那個(gè)線程。 線程中斷
void stop()
中斷線程, 直接將線程干掉德挣,進(jìn)入死亡恭垦。(上課睡覺(jué),被AK掃射
public void interrupt()
中斷線程。會(huì)拋出InterriptedException, 走的異常處理的機(jī)制,后面的代碼還可以執(zhí)行番挺。(上課睡覺(jué)唠帝,被敲醒)-
線程的優(yōu)先級(jí)
void setPriority(int newPriority)
更改線程的優(yōu)先級(jí)int getPriority()
返回線程的優(yōu)先級(jí)默認(rèn)為5
public static final int MAX_PRIORITY 10
public static final int MIN_PRIORITY 1
public static final int NORM_PRIORITY 5 -
線程調(diào)度
- 分時(shí)調(diào)度(雨露均沾)
- 搶占式調(diào)度(Java)
4. 線程的生命周期
- 新建
- 就緒
- 運(yùn)行
- 死亡
-
阻塞
- 一般阻塞
- 等待阻塞
- 同步阻塞
5. 線程的安全問(wèn)題
電影院售票問(wèn)題,引出線程安全問(wèn)題玄柏。
如何解決線程安全問(wèn)題襟衰?
- 同步代碼塊
synchronized() 實(shí)質(zhì)上是鎖機(jī)制 - 同步方法
synchronized- 普通成員方法:鎖的對(duì)象是調(diào)用此方法的對(duì)象,即this
- 靜態(tài)方法:鎖的對(duì)象是字節(jié)碼文件對(duì)象粪摘,class類(lèi)對(duì)象
- 鎖Lock接口
6. 死鎖問(wèn)題
死鎖問(wèn)題:
在同步代碼塊嵌套的時(shí)候瀑晒,可能會(huì)出現(xiàn)多個(gè)線程相互等待的現(xiàn)象。
為了解決死鎖問(wèn)題赶熟,線程之間應(yīng)該相互配合瑰妄。
7. 線程間的通信機(jī)制
線程之間通信的機(jī)制(等待喚醒機(jī)制):
Object(鎖):
void notify() 喚醒一個(gè)正在等待獲取這個(gè)鎖對(duì)象的線程(隨機(jī))
void notifyAll() 喚醒所有正在等待獲取這個(gè)鎖對(duì)象的線程
void wait() 當(dāng)前線程進(jìn)入阻塞狀態(tài),并釋放鎖對(duì)象映砖,等待被喚醒间坐。
void wait(long timeout) 當(dāng)前線程進(jìn)入阻塞狀態(tài),并釋放鎖對(duì)象邑退,等待被喚醒竹宋。如果在這個(gè)時(shí)間內(nèi)沒(méi)有被喚醒,就自動(dòng)醒來(lái)地技。
void wait(long timeout, int nanos)
思考蜈七,為什么不定義在 Thread 類(lèi)中?
線程之間通信的媒介是鎖對(duì)象莫矗,但是鎖對(duì)象可以是任意對(duì)象飒硅。所以等待喚醒這些應(yīng)該定義Object類(lèi)中。
舉個(gè)栗子:生產(chǎn)者 - 消費(fèi)者模型
線程間還可以配合一起做某些事作谚,如交替數(shù)數(shù)
8. 線程池
為什么會(huì)出現(xiàn)線程池三娩?
啟動(dòng)一個(gè)新線程的成本是比較高的,因?yàn)樗婕暗脚c操作系統(tǒng)進(jìn)行交互妹懒。這種情況下使用線程池可以更好的提高性能雀监,尤其當(dāng)前程序需要?jiǎng)?chuàng)建大量的生存周期很短的線程時(shí),更應(yīng)該考慮線程池
概述
JDK5后出現(xiàn)的眨唬,養(yǎng)線程会前,當(dāng)線程執(zhí)行完任務(wù)后,不會(huì)銷(xiāo)毀線程匾竿,而是在回到線程池中成為空閑狀態(tài)瓦宜,等待分配任務(wù)Executor:
void execute(Runnable command)-
Executors:
public static ExecutorService newCachedThreadPool()
// 創(chuàng)建一個(gè)默認(rèn)線程池,都是臨時(shí)工public static ExecutorService newFixedThreadPool(int nThreads)
// 創(chuàng)建一個(gè)指定數(shù)量初始線程的線程池岭妖,都是正式員工public static ExecutorService newSingleThreadExecutor()
// 創(chuàng)建單個(gè)線程的線程池歉提,只養(yǎng)一個(gè) -
ExecutorService(線程池) extends Executor:
- 提交任務(wù):
void execute(Runnable command)
Future<T> submit(Callable<T> task)
Future<?> submit(Runnable task)
<T> Future<T> submit(Runnable task, T result)- 關(guān)閉線程池:
void shutdown()
啟動(dòng)一次順序關(guān)閉笛坦,執(zhí)行以前提交的任務(wù),但不接受新任務(wù)苔巨。
List<Runnable> shutdownNow()
試圖停止所有正在執(zhí)行的活動(dòng)任務(wù)版扩,暫停處理正在等待的任務(wù),并返回等待執(zhí)行的任務(wù)列表侄泽。
- 提交任務(wù):
Callable<T>: 返回結(jié)果并且可能拋出異常的任務(wù)
T call()
Future<T>:
V get()
如有必要礁芦,等待計(jì)算完成,然后獲取其結(jié)果悼尾。
V get(long timeout, TimeUnit unit)
如有必要柿扣,最多等待為使計(jì)算完成所給定的時(shí)間之后,獲取其結(jié)果(如果結(jié)果可用)闺魏。
9. 定時(shí)器
Timer(定時(shí)器)未状、TimerTask(定時(shí)器任務(wù))
概述
一種線程工具,以后臺(tái)線程的方式去執(zhí)行任務(wù)析桥∷静荩可安排任務(wù)執(zhí)行一次,或者定期重復(fù)執(zhí)行泡仗。以后使用定時(shí)任務(wù)埋虹,常常用任務(wù)調(diào)度框架: quartz。而不會(huì)使用Timer娩怎。但其底層原理是一樣的搔课。方法
void schedule(TimerTask task, Date time)
// 在指定的時(shí)間執(zhí)行指定任務(wù),執(zhí)行一次截亦。
void schedule(TimerTask task, Date firstTime, long period)
// 在指定的時(shí)間第一次執(zhí)行指定任務(wù)爬泥,每個(gè)多少毫秒,再重復(fù)的執(zhí)行這個(gè)任務(wù)
void schedule(TimerTask task, long delay)
// 在指定延遲時(shí)間后崩瓤,執(zhí)行執(zhí)行的任務(wù)急灭,執(zhí)行一次。
void schedule(TimerTask task, long delay, long period)
// 在指定延遲時(shí)間后第一次執(zhí)行指定任務(wù)谷遂,每個(gè)多少毫秒,再重復(fù)的執(zhí)行這個(gè)任務(wù)
void cancel()
// 終止此計(jì)時(shí)器卖鲤,丟棄所有當(dāng)前已安排的任務(wù)肾扰。TimerTask:
abstract void run() // 安排為一次執(zhí)行或重復(fù)執(zhí)行的任務(wù)。
10. Collection集合的線程安全
線程安全的集合:
Vector
HashTable
|-- Properties
StringBuffer
即使Vector是線程安全蛋逾,但是我們也不使用它集晚。
Collections:
static <T> List<T> synchronizedList(List<T> list) // 視圖技術(shù)
static <K,V> Map<K,V> synchronizedMap(Map<K,V> m)
static <T> Set<T> synchronizedSet(Set<T> s)