Java多線程、線程安全、線程池創(chuàng)建方式
多線程是實(shí)現(xiàn)多任務(wù)并行處理的核心技術(shù),小到程序的異步執(zhí)行,大到高并發(fā)系統(tǒng)的開發(fā),都離不開多線程。比如我們平時(shí)用的軟件,一邊聽歌一邊刷評(píng)論,就是多線程的典型場(chǎng)景。
一、多線程:
1.1 兩個(gè)基礎(chǔ)概念
簡(jiǎn)單說:進(jìn)程是軟件的實(shí)例,線程是軟件里的具體任務(wù)。
多線程就是讓一個(gè)程序同時(shí)執(zhí)行多個(gè)任務(wù)。
- 進(jìn)程:運(yùn)行中的程序,比如打開的微信、IDEA,是操作系統(tǒng)資源分配的基本單位。
- 線程:進(jìn)程中的最小執(zhí)行單元,一個(gè)進(jìn)程可以包含多個(gè)線程,線程共享進(jìn)程的資源(內(nèi)存、文件等),是 CPU 調(diào)度的基本單位。
1.2 三種創(chuàng)建方式
Java 中創(chuàng)建多線程有3 種核心方式,各有優(yōu)缺點(diǎn),適用于不同場(chǎng)景,入門先掌握這三種即可,其中實(shí)現(xiàn) Runnable 接口是最常用的方式。
方式 1:繼承 Thread 類
Thread類是 Java 多線程的核心類,直接繼承它,重寫run()方法(線程的執(zhí)行體),調(diào)用start()方法啟動(dòng)線程。
核心步驟
- 自定義類繼承
Thread類; - 重寫
run()方法,編寫線程執(zhí)行邏輯; - 創(chuàng)建自定義線程對(duì)象,調(diào)用
start()方法啟動(dòng)(注意:不能直接調(diào)用 run (),否則就是普通方法)。
代碼示例
// 1. 自定義線程類,繼承Thread
public class MyThread extends Thread {
// 2. 重寫run()方法,線程執(zhí)行的邏輯寫在這里
@Override
public void run() {
for (int i = 0; i < 5; i++) {
// Thread.currentThread().getName() 獲取當(dāng)前線程名
System.out.println(Thread.currentThread().getName() + ":執(zhí)行了" + i);
}
}
public static void main(String[] args) {
// 3. 創(chuàng)建線程對(duì)象,設(shè)置線程名
MyThread t1 = new MyThread();
t1.setName("線程1");
MyThread t2 = new MyThread();
t2.setName("線程2");
// 4. 啟動(dòng)線程:調(diào)用start()
t1.start();
t2.start();
// 主線程也執(zhí)行邏輯,對(duì)比看多線程效果
for (int i = 0; i < 5; i++) {
System.out.println("主線程:執(zhí)行了" + i);
}
}
}運(yùn)行結(jié)果
多線程的執(zhí)行順序由CPU 調(diào)度決定,所以每次運(yùn)行結(jié)果都不一樣,體現(xiàn)了線程的隨機(jī)性:
主線程:執(zhí)行了0
線程1:執(zhí)行了0
線程2:執(zhí)行了0
線程1:執(zhí)行了1
主線程:執(zhí)行了1
...
缺點(diǎn)
Java 是單繼承,自定義類繼承 Thread 后,就不能繼承其他類,靈活性差。
方式 2:實(shí)現(xiàn) Runnable 接口
Runnable是線程的任務(wù)接口,只定義了一個(gè)run()方法,把線程和任務(wù)分離,更符合面向?qū)ο笏枷?,是開發(fā)中最常用的方式。
核心步驟
- 自定義類實(shí)現(xiàn)
Runnable接口,重寫run()方法; - 創(chuàng)建任務(wù)對(duì)象;
- 把任務(wù)對(duì)象傳給
Thread類的構(gòu)造器,創(chuàng)建線程對(duì)象; - 調(diào)用
start()啟動(dòng)線程。
代碼示例
// 1. 自定義任務(wù)類,實(shí)現(xiàn)Runnable接口
public class MyRunnable implements Runnable {
// 2. 重寫run()方法,編寫任務(wù)邏輯
@Override
public void run() {
for (int i = 0; i < 5; i++) {
System.out.println(Thread.currentThread().getName() + ":執(zhí)行了" + i);
}
}
public static void main(String[] args) {
// 3. 創(chuàng)建任務(wù)對(duì)象
MyRunnable task = new MyRunnable();
// 4. 把任務(wù)傳給Thread,創(chuàng)建線程對(duì)象
Thread t1 = new Thread(task, "線程1");
Thread t2 = new Thread(task, "線程2");
// 5. 啟動(dòng)線程
t1.start();
t2.start();
}
}優(yōu)點(diǎn)
- 避免單繼承的限制,自定義類可以繼承其他類;
- 線程和任務(wù)分離,一個(gè)任務(wù)可以被多個(gè)線程執(zhí)行,資源復(fù)用。
方式 3:實(shí)現(xiàn) Callable 接口
前兩種方式的run()方法沒有返回值,不能拋出編譯時(shí)異常,如果需要線程執(zhí)行后返回結(jié)果(比如計(jì)算一個(gè)數(shù)值),就用Callable接口,這是 JDK5 新增的方式。
核心要點(diǎn)
Callable<V>:泛型 V 表示返回值類型,重寫call()方法(執(zhí)行體),有返回值,可拋異常;- 需要配合
FutureTask類使用:封裝Callable任務(wù),獲取線程執(zhí)行結(jié)果; - 最終還是通過
Thread類啟動(dòng)線程。
代碼示例
import java.util.concurrent.Callable;
import java.util.concurrent.FutureTask;
// 1. 自定義任務(wù)類,實(shí)現(xiàn)Callable接口,指定泛型為返回值類型(Integer)
public class MyCallable implements Callable<Integer> {
// 2. 重寫call()方法,有返回值,可拋異常
@Override
public Integer call() throws Exception {
// 模擬計(jì)算:1-5的和
int sum = 0;
for (int i = 1; i <= 5; i++) {
sum += i;
System.out.println(Thread.currentThread().getName() + ":計(jì)算中,i=" + i);
}
return sum; // 返回計(jì)算結(jié)果
}
public static void main(String[] args) throws Exception {
// 3. 創(chuàng)建Callable任務(wù)對(duì)象
MyCallable callable = new MyCallable();
// 4. 用FutureTask封裝任務(wù),用于獲取返回值
FutureTask<Integer> futureTask = new FutureTask<>(callable);
// 5. 把FutureTask傳給Thread,創(chuàng)建線程并啟動(dòng)
Thread t = new Thread(futureTask, "計(jì)算線程");
t.start();
// 6. 獲取線程執(zhí)行結(jié)果:get()方法會(huì)阻塞,直到線程執(zhí)行完成
Integer result = futureTask.get();
System.out.println(t.getName() + "執(zhí)行結(jié)果:1-5的和=" + result);
}
}運(yùn)行結(jié)果
計(jì)算線程:計(jì)算中,i=1
計(jì)算線程:計(jì)算中,i=2
計(jì)算線程:計(jì)算中,i=3
計(jì)算線程:計(jì)算中,i=4
計(jì)算線程:計(jì)算中,i=5
計(jì)算線程執(zhí)行結(jié)果:1-5的和=15
優(yōu)點(diǎn)
- 有返回值,能滿足 “線程執(zhí)行后需要結(jié)果” 的場(chǎng)景;
call()方法可以拋出編譯時(shí)異常,無需手動(dòng)捕獲。
1.3 多線程常用方法
整理了開發(fā)中最常用的線程方法,分為線程啟動(dòng) / 命名、線程休眠 / 等待、線程狀態(tài)獲取三類:
| 方法名 | 作用 | 注意事項(xiàng) |
|---|---|---|
| start() | 啟動(dòng)線程,JVM 會(huì)自動(dòng)調(diào)用run() | 不能重復(fù)調(diào)用,否則拋異常 |
| run() | 線程執(zhí)行體,編寫業(yè)務(wù)邏輯 | 直接調(diào)用是普通方法,不會(huì)啟動(dòng)新線程 |
| setName(String name) | 設(shè)置線程名 | 也可以通過 Thread 構(gòu)造器設(shè)置 |
| getName() | 獲取線程名 | 默認(rèn)線程名:Thread-0、Thread-1... |
| sleep(long ms) | 讓線程休眠指定毫秒數(shù) | 靜態(tài)方法,休眠時(shí)不會(huì)釋放鎖,會(huì)拋出 InterruptedException |
| join() | 讓調(diào)用該方法的線程先執(zhí)行完 | 比如 t1.join (),主線程會(huì)等 t1 執(zhí)行完再執(zhí)行 |
| Thread.currentThread() | 獲取當(dāng)前執(zhí)行的線程對(duì)象 | 靜態(tài)方法,常用在匿名線程中 |
| isAlive() | 判斷線程是否存活 | 啟動(dòng)后、結(jié)束前為 true |
常用方法示例:
public class ThreadMethodDemo {
public static void main(String[] args) throws InterruptedException {
Thread t = new Thread(() -> {
for (int i = 0; i < 3; i++) {
try {
Thread.sleep(1000); // 休眠1秒
} catch (InterruptedException e) {
e.printStackTrace();
}
System.out.println(Thread.currentThread().getName() + ":執(zhí)行" + i);
}
}, "休眠線程");
t.start();
t.join(); // 主線程等待t執(zhí)行完再執(zhí)行
System.out.println("主線程:執(zhí)行完成,休眠線程已結(jié)束");
}
}運(yùn)行結(jié)果
休眠線程:執(zhí)行0
休眠線程:執(zhí)行1
休眠線程:執(zhí)行2
主線程:執(zhí)行完成,休眠線程已結(jié)束
二、線程安全:
2.1 什么是線程安全問題?
當(dāng)多個(gè)線程同時(shí)操作同一個(gè)共享資源時(shí),會(huì)導(dǎo)致數(shù)據(jù)錯(cuò)亂、結(jié)果不符合預(yù)期,這就是線程安全問題。
核心條件:
- 多線程環(huán)境
- 操作共享資源
- 共享資源的操作是非原子性的(一行代碼可能對(duì)應(yīng)多個(gè) CPU 指令)。
2.2 模擬線程安全問題:
假設(shè)火車站有 10 張票,3 個(gè)窗口同時(shí)賣票,模擬多線程售票,會(huì)出現(xiàn)超賣(賣成 0 張以下)、重復(fù)賣的問題,這是典型的線程安全問題。
代碼示例
// 售票任務(wù)類,實(shí)現(xiàn)Runnable
public class TicketRunnable implements Runnable {
// 共享資源:10張票(多個(gè)線程共用這個(gè)變量)
private int ticketNum = 10;
@Override
public void run() {
// 循環(huán)賣票
while (true) {
// 有票就賣
if (ticketNum > 0) {
try {
Thread.sleep(500); // 模擬選座、打印票的耗時(shí)
} catch (InterruptedException e) {
e.printStackTrace();
}
System.out.println(Thread.currentThread().getName() + "賣出了1張票,剩余:" + --ticketNum);
} else {
// 無票就退出
break;
}
}
}
public static void main(String[] args) {
// 一個(gè)任務(wù)對(duì)象,3個(gè)線程共享
TicketRunnable task = new TicketRunnable();
new Thread(task, "窗口1").start();
new Thread(task, "窗口2").start();
new Thread(task, "窗口3").start();
}
}運(yùn)行結(jié)果
窗口1賣出了1張票,剩余:9
窗口2賣出了1張票,剩余:8
窗口3賣出了1張票,剩余:7
...
窗口2賣出了1張票,剩余:1
窗口3賣出了1張票,剩余:0
窗口1賣出了1張票,剩余:-1 // 超賣,線程安全問題
問題原因
- 多個(gè)線程同時(shí)判斷
ticketNum > 0,比如票只剩 1 張時(shí),3 個(gè)線程都判斷為 true,然后依次執(zhí)行--ticketNum,最終就會(huì)賣成 - 1。
2.3 解決線程安全問題:線程同步
解決線程安全問題的核心思路是線程同步,也就是給共享資源的操作代碼加鎖,讓多個(gè)線程排隊(duì)執(zhí)行這部分代碼,而不是同時(shí)執(zhí)行。
Java 中提供了3 種加鎖方式:同步代碼塊、同步方法、Lock 鎖,都能解決線程安全問題,下面基于售票案例逐一實(shí)現(xiàn)。
方式 1:同步代碼塊(synchronized)
用synchronized關(guān)鍵字包裹操作共享資源的代碼塊,指定鎖對(duì)象,多個(gè)線程必須使用同一個(gè)鎖對(duì)象才能實(shí)現(xiàn)同步。
語法
synchronized(鎖對(duì)象) {
// 操作共享資源的代碼塊(臨界區(qū))
}
鎖對(duì)象要求
- 可以是任意對(duì)象,但多個(gè)線程的鎖對(duì)象必須是同一個(gè);
- 推薦使用共享資源本身或this(當(dāng)前任務(wù)對(duì)象)作為鎖對(duì)象。
售票案例改造
public class TicketSyncBlock implements Runnable {
private int ticketNum = 10;
// 鎖對(duì)象:多個(gè)線程共用同一個(gè)
private Object lock = new Object();
@Override
public void run() {
while (true) {
// 同步代碼塊:加鎖,操作共享資源的代碼排隊(duì)執(zhí)行
synchronized (lock) {
if (ticketNum > 0) {
try {
Thread.sleep(500);
} catch (InterruptedException e) {
e.printStackTrace();
}
System.out.println(Thread.currentThread().getName() + "賣出了1張票,剩余:" + --ticketNum);
} else {
break;
}
}
}
}
public static void main(String[] args) {
TicketSyncBlock task = new TicketSyncBlock();
new Thread(task, "窗口1").start();
new Thread(task, "窗口2").start();
new Thread(task, "窗口3").start();
}
}運(yùn)行結(jié)果
窗口1賣出了1張票,剩余:9
窗口1賣出了1張票,剩余:8
窗口2賣出了1張票,剩余:7
...
窗口3賣出了1張票,剩余:1
窗口3賣出了1張票,剩余:0
方式 2:同步方法
把操作共享資源的代碼提取成一個(gè)方法,用synchronized修飾,這個(gè)方法就是同步方法,無需手動(dòng)指定鎖對(duì)象。
鎖對(duì)象規(guī)則
- 非靜態(tài)同步方法:鎖對(duì)象是
this(當(dāng)前任務(wù)對(duì)象); - 靜態(tài)同步方法:鎖對(duì)象是類的 Class 對(duì)象(比如 TicketSyncMethod.class)。
售票案例改造
public class TicketSyncMethod implements Runnable {
private int ticketNum = 10;
@Override
public void run() {
while (true) {
// 調(diào)用同步方法
if (!sellTicket()) {
break;
}
}
}
// 同步方法:synchronized修飾,操作共享資源的代碼
private synchronized boolean sellTicket() {
if (ticketNum > 0) {
try {
Thread.sleep(500);
} catch (InterruptedException e) {
e.printStackTrace();
}
System.out.println(Thread.currentThread().getName() + "賣出了1張票,剩余:" + --ticketNum);
return true;
} else {
return false;
}
}
public static void main(String[] args) {
TicketSyncMethod task = new TicketSyncMethod();
new Thread(task, "窗口1").start();
new Thread(task, "窗口2").start();
new Thread(task, "窗口3").start();
}
}優(yōu)點(diǎn)
代碼更簡(jiǎn)潔,無需手動(dòng)創(chuàng)建鎖對(duì)象,適合整個(gè)方法都是操作共享資源的場(chǎng)景。
方式 3:Lock 鎖
synchronized是隱式鎖(自動(dòng)加鎖、自動(dòng)釋放鎖,代碼執(zhí)行完 / 拋異常都會(huì)釋放),而Lock是顯式鎖,需要手動(dòng)加鎖和手動(dòng)釋放鎖,靈活性更高,是開發(fā)中常用的進(jìn)階方式。
Java 中Lock的核心實(shí)現(xiàn)類是ReentrantLock(可重入鎖)。
核心方法
lock():手動(dòng)加鎖;unlock():手動(dòng)釋放鎖(必須放在 finally 中,保證鎖一定釋放,避免死鎖)。
售票案例改造
import java.util.concurrent.locks.ReentrantLock;
public class TicketLock implements Runnable {
private int ticketNum = 10;
// 創(chuàng)建Lock鎖對(duì)象,可重入鎖
private ReentrantLock lock = new ReentrantLock();
@Override
public void run() {
while (true) {
// 手動(dòng)加鎖
lock.lock();
try {
// 操作共享資源的代碼
if (ticketNum > 0) {
Thread.sleep(500);
System.out.println(Thread.currentThread().getName() + "賣出了1張票,剩余:" + --ticketNum);
} else {
break;
}
} catch (InterruptedException e) {
e.printStackTrace();
} finally {
// 手動(dòng)釋放鎖,放在finally中,保證一定執(zhí)行
lock.unlock();
}
}
}
public static void main(String[] args) {
TicketLock task = new TicketLock();
new Thread(task, "窗口1").start();
new Thread(task, "窗口2").start();
new Thread(task, "窗口3").start();
}
}優(yōu)點(diǎn)
- 靈活性高:可以在代碼任意位置加鎖、釋放鎖,不像 synchronized 只能包裹代碼塊 / 修飾方法;
- 支持公平鎖 / 非公平鎖:創(chuàng)建 ReentrantLock 時(shí)傳入
true就是公平鎖(先等待的線程先執(zhí)行); - 提供更多方法:比如
tryLock()(嘗試加鎖,加鎖失敗不阻塞)。
2.4 三種同步方式對(duì)比
| 同步方式 | 鎖類型 | 加鎖 / 釋放 | 靈活性 | 適用場(chǎng)景 |
|---|---|---|---|---|
| 同步代碼塊 | 隱式鎖 | 自動(dòng) | 中等 | 部分代碼操作共享資源 |
| 同步方法 | 隱式鎖 | 自動(dòng) | 低 | 整個(gè)方法操作共享資源 |
| Lock 鎖 | 顯式鎖 | 手動(dòng)(finally 釋放) | 高 | 復(fù)雜的同步場(chǎng)景,需要靈活加鎖 / 釋放 |
核心總結(jié):解決線程安全問題的本質(zhì)是讓多個(gè)線程排隊(duì)執(zhí)行共享資源的代碼,加鎖是實(shí)現(xiàn)排隊(duì)的關(guān)鍵。
三、線程池:
3.1 為什么需要線程池?
線程池就是一個(gè)線程的容器,提前創(chuàng)建好若干個(gè)線程,任務(wù)來了直接分配線程執(zhí)行,任務(wù)執(zhí)行完線程不銷毀,放回線程池復(fù)用。就像餐廳的服務(wù)員,不會(huì)來了一個(gè)客人就雇一個(gè)服務(wù)員,而是提前雇好,客人走了服務(wù)員繼續(xù)服務(wù)下一個(gè)客人。
手動(dòng)創(chuàng)建線程雖然簡(jiǎn)單,但存在很多問題:
- 線程創(chuàng)建和銷毀消耗系統(tǒng)資源(CPU、內(nèi)存);
- 無限制創(chuàng)建線程會(huì)導(dǎo)致內(nèi)存溢出(OOM);
- 手動(dòng)管理線程的狀態(tài)(啟動(dòng)、停止)非常繁瑣。
3.2 線程池的核心優(yōu)勢(shì)
- 線程復(fù)用:避免頻繁創(chuàng)建銷毀線程,減少資源開銷;
- 控制線程數(shù)量:防止創(chuàng)建過多線程導(dǎo)致內(nèi)存溢出;
- 提高響應(yīng)速度:任務(wù)來了直接用池中的線程,無需等待線程創(chuàng)建;
- 統(tǒng)一管理:方便對(duì)線程進(jìn)行監(jiān)控、統(tǒng)計(jì)、調(diào)優(yōu)。
3.3 線程池處理兩種任務(wù):
線程池可以處理我們之前講的Runnable 任務(wù)(無返回值)和Callable 任務(wù)(有返回值),核心步驟都是:
- 創(chuàng)建線程池;
- 提交任務(wù)給線程池;
- 關(guān)閉線程池(可選,根據(jù)業(yè)務(wù)場(chǎng)景)。
Java 中線程池的核心接口是ExecutorService,下面用最常用的固定線程池演示處理兩種任務(wù)。
案例 1:線程池處理 Runnable 任務(wù)(無返回值)
import java.util.concurrent.ExecutorService;
import java.util.concurrent.Executors;
public class ThreadPoolRunnable {
public static void main(String[] args) {
// 1. 創(chuàng)建固定線程池:核心線程數(shù)為2
ExecutorService pool = Executors.newFixedThreadPool(2);
//使用線程池的實(shí)現(xiàn)類ThreadPoolExecutor創(chuàng)建線程池對(duì)象。(有七個(gè)參數(shù))
//ExecutorService pool = new ThreadPoolExecutor(
// 2,//核心線程數(shù)
// 2,//最大線程數(shù)
// 0,//線程空閑時(shí)間
// TimeUnit.SECONDS,//時(shí)間單位
// new ArrayBlockingQueue<>(2),//任務(wù)隊(duì)列
// Executors.defaultThreadFactory(),//線程工廠對(duì)象
// new ThreadPoolExecutor.AbortPolicy()//拒絕策略
//);
// 2. 提交Runnable任務(wù)給線程池
pool.submit(new Runnable() {
@Override
public void run() {
for (int i = 0; i < 3; i++) {
System.out.println(Thread.currentThread().getName() + "執(zhí)行Runnable任務(wù):" + i);
}
}
});
pool.submit(() -> { // Lambda簡(jiǎn)化Runnable
for (int i = 0; i < 3; i++) {
System.out.println(Thread.currentThread().getName() + "執(zhí)行Lambda任務(wù):" + i);
}
});
// 3. 關(guān)閉線程池:shutdown()等待任務(wù)執(zhí)行完再關(guān)閉,shutdownNow()立即關(guān)閉
pool.shutdown();
}
}案例 2:線程池處理 Callable 任務(wù)(有返回值)
import java.util.concurrent.ExecutorService;
import java.util.concurrent.Executors;
import java.util.concurrent.Future;
public class ThreadPoolCallable {
public static void main(String[] args) throws Exception {
// 1. 創(chuàng)建固定線程池,核心線程數(shù)1
ExecutorService pool = Executors.newFixedThreadPool(1);
// 2. 提交Callable任務(wù),F(xiàn)uture接收返回值
Future<Integer> future = pool.submit(() -> {
// 計(jì)算1-10的和
int sum = 0;
for (int i = 1; i <= 10; i++) {
sum += i;
}
return sum;
});
// 3. 獲取返回值
Integer result = future.get();
System.out.println("Callable任務(wù)執(zhí)行結(jié)果:1-10的和=" + result);
// 4. 關(guān)閉線程池
pool.shutdown();
}
}運(yùn)行結(jié)果
Callable任務(wù)執(zhí)行結(jié)果:1-10的和=55
3.4 Executors 工具類:
Java 提供了java.util.concurrent.Executors工具類,封裝了線程池的創(chuàng)建邏輯,提供了4 種常用的線程池創(chuàng)建方法,滿足 90% 的開發(fā)需求,入門直接用這個(gè)工具類即可。
4 種常用線程池總結(jié)
| 線程池方法 | 核心特點(diǎn) | 核心線程數(shù) | 最大線程數(shù) | 適用場(chǎng)景 |
|---|---|---|---|---|
| newFixedThreadPool(n) | 固定線程池,線程數(shù)固定,任務(wù)排隊(duì) | n | n | 任務(wù)量固定,需要穩(wěn)定執(zhí)行的場(chǎng)景(比如常規(guī)業(yè)務(wù)處理) |
| newSingleThreadExecutor() | 單線程池,只有 1 個(gè)線程 | 1 | 1 | 任務(wù)需要按順序執(zhí)行的場(chǎng)景 |
| newCachedThreadPool() | 緩存線程池,線程數(shù)動(dòng)態(tài)擴(kuò)容,空閑線程 60 秒銷毀 | 0 | Integer.MAX_VALUE | 任務(wù)量波動(dòng)大的場(chǎng)景(比如高并發(fā)請(qǐng)求) |
| newScheduledThreadPool(n) | 定時(shí)線程池,支持定時(shí) / 延遲執(zhí)行任務(wù) | n | Integer.MAX_VALUE | 定時(shí)任務(wù)、延遲任務(wù)(比如定時(shí)備份、定時(shí)統(tǒng)計(jì)) |
定時(shí)線程池示例
import java.util.concurrent.Executors;
import java.util.concurrent.ScheduledExecutorService;
import java.util.concurrent.TimeUnit;
public class ScheduledThreadPoolDemo {
public static void main(String[] args) {
// 創(chuàng)建定時(shí)線程池,核心線程數(shù)2
ScheduledExecutorService pool = Executors.newScheduledThreadPool(2);
// 1. 延遲執(zhí)行:延遲1秒后執(zhí)行一次任務(wù)
pool.schedule(() -> {
System.out.println(Thread.currentThread().getName() + ":延遲1秒執(zhí)行");
}, 1, TimeUnit.SECONDS);
// 2. 定時(shí)執(zhí)行:延遲2秒后,每3秒執(zhí)行一次任務(wù)
pool.scheduleAtFixedRate(() -> {
System.out.println(Thread.currentThread().getName() + ":定時(shí)執(zhí)行,每3秒一次");
}, 2, 3, TimeUnit.SECONDS);
}
}運(yùn)行結(jié)果
pool-1-thread-1:延遲1秒執(zhí)行
pool-1-thread-2:定時(shí)執(zhí)行,每3秒一次
pool-1-thread-2:定時(shí)執(zhí)行,每3秒一次
...
3.5 并發(fā) & 并行
學(xué)習(xí)多線程,一定會(huì)遇到并發(fā)和并行,兩者都表示 “同時(shí)執(zhí)行”,但本質(zhì)不同,用通俗的語言和例子講清楚:
并發(fā)(Concurrency)
- 定義:同一時(shí)間段內(nèi)多個(gè)任務(wù)交替執(zhí)行,看似同時(shí)執(zhí)行,實(shí)際是 CPU 在多個(gè)任務(wù)之間快速切換;
- 底層:適用于單核 CPU;
- 例子:一個(gè)廚師同時(shí)做兩碗面,先煮第一碗的面,再煮第二碗的面,交替進(jìn)行。
并行(Parallelism)
- 定義:同一時(shí)刻多個(gè)任務(wù)同時(shí)執(zhí)行,真正的同時(shí)執(zhí)行;
- 底層:適用于多核 CPU,每個(gè)核執(zhí)行一個(gè)任務(wù);
- 例子:兩個(gè)廚師同時(shí)做兩碗面,各自做各自的,互不干擾。
并發(fā)是交替執(zhí)行,并行是同時(shí)執(zhí)行;Java 多線程在單核 CPU 上是并發(fā),在多核 CPU 上是并行。
四、核心知識(shí)點(diǎn)總結(jié)
- 多線程創(chuàng)建:繼承 Thread(單繼承限制)、實(shí)現(xiàn) Runnable(最常用,無繼承限制)、實(shí)現(xiàn) Callable(帶返回值),啟動(dòng)線程必須調(diào)用
start(); - 線程安全:多線程操作共享資源導(dǎo)致數(shù)據(jù)錯(cuò)亂,解決方式是加鎖(同步代碼塊、同步方法、Lock 鎖),核心是讓線程排隊(duì)執(zhí)行;
- 線程池:Executors 工具類快速創(chuàng)建 4 種常用線程池,處理 Runnable(無返回值)和 Callable(有返回值)任務(wù),優(yōu)勢(shì)是線程復(fù)用、控制數(shù)量;
- 并發(fā) & 并行:并發(fā)是單核交替執(zhí)行,并行是多核同時(shí)執(zhí)行。
到此這篇關(guān)于Java多線程、線程安全、線程池創(chuàng)建方式的文章就介紹到這了,更多相關(guān)Java多線程 線程安全 線程池內(nèi)容請(qǐng)搜索腳本之家以前的文章或繼續(xù)瀏覽下面的相關(guān)文章希望大家以后多多支持腳本之家!
相關(guān)文章
詳解idea maven項(xiàng)目如何使用lib下得jar包
這篇文章主要介紹了詳解idea maven項(xiàng)目如何使用lib下得jar包,小編覺得挺不錯(cuò)的,現(xiàn)在分享給大家,也給大家做個(gè)參考。一起跟隨小編過來看看吧2017-12-12
springBoot中動(dòng)態(tài)加載jar包的完整指南
這篇文章主要為大家詳細(xì)介紹了springBoot中動(dòng)態(tài)加載jar包的相關(guān)方法,文中的示例代碼講解詳細(xì),感興趣的小伙伴可以跟隨小編一起學(xué)習(xí)一下2025-10-10
java實(shí)現(xiàn)簡(jiǎn)單控制臺(tái)五子棋游戲
這篇文章主要為大家詳細(xì)介紹了java實(shí)現(xiàn)簡(jiǎn)單控制臺(tái)五子棋游戲,文中示例代碼介紹的非常詳細(xì),具有一定的參考價(jià)值,感興趣的小伙伴們可以參考一下2019-11-11
Java中的權(quán)限修飾符(protected)示例詳解
這篇文章主要給大家介紹了關(guān)于Java中權(quán)限修飾符(protected)的相關(guān)資料,文中通過示例代碼介紹的非常詳細(xì),對(duì)大家的學(xué)習(xí)或者工作具有一定的參考學(xué)習(xí)價(jià)值,需要的朋友們下面隨著小編來一起學(xué)習(xí)學(xué)習(xí)吧2021-01-01
關(guān)于SSM框架下各層的解釋說明(Controller等)
這篇文章主要介紹了關(guān)于SSM框架下各層的解釋說明(Controller等),具有很好的參考價(jià)值,希望對(duì)大家有所幫助。如有錯(cuò)誤或未考慮完全的地方,望不吝賜教2022-02-02

