Java多線程編程:線程的原理和使用安全
一,認識線程
1.1 什么是線程
一個線程就是一個“執(zhí)行流”,每個線程之間都可以按照順序執(zhí)行自己的代碼,多個線程之間“同時”執(zhí)行著多份代碼。同時,線程也是 CPU 真正執(zhí)行任務的單位。
舉個栗子:去奶茶店買奶茶(奶茶店中有自己獨立的“程序”讓我們拿到奶茶,所以也可把這個看為一個線程),奶茶店有不同崗位員工,每個員工都是獨立的“執(zhí)行流”。如果店里只有一個員工(單線程),那速度將會很慢效率極低;多個員工分工合作(多線程),那效率會得到大幅提高,這就是多線程的優(yōu)勢。
2.2 線程和進程的區(qū)別
進程包含線程的,每個進程至少有一個線程存在(主線程)
| 線程(Thread) | 進程 | |
| 線程時cpu上調度執(zhí)行的基本單位 | 進程是操作系統(tǒng)中資源分配的基本單位 | |
| 資源 | 共享進程內存 | 獨立內存,互不影響 |
| 開銷 | 輕量,快速(只有第一個線程也就是和進程一起創(chuàng)建時,需要申請資源,后續(xù)創(chuàng)建則不需要) | 創(chuàng)建/銷毀慢,消耗資源 |
| 奔潰影響 | 一個崩潰,可能把同進程內的線程都帶崩潰 | 一個崩潰不影響其他 |
二,創(chuàng)建線程
2.1 繼承Thread類,重寫run方法
class Thread1 extends Thread{
public void run(){
System.out.println("線程運行代碼");
}
}
//創(chuàng)建實例
Thread1 t = new Thread(); 2.2 實現(xiàn)Runnable接口,重寫run方法
clas Runnable1 implements Runnable{
public void run(){
System.out.println("線程運行代碼");
}
}
//創(chuàng)建實例
Thread t = new Thread(new Runnable());
2.3 匿名內部類(本質就是方法1,2)
2.3.1 用匿名內部類創(chuàng)建Tread子類對象
Thread t1 = new Thread(){
public void run(){
System.out.println("使用匿名類創(chuàng)建Tread子類對象");
}
}2.3.2 用匿名內部類創(chuàng)建Runnable子類對象
Thread t2 = new Thread(new Runnable(){
public void run(){
System.out.println("使用匿名類創(chuàng)建Runnable子類對象");
}
});這兩個線程對象(
t1和t2)的start()方法都只能調用一次,所以從 “啟動線程” 的角度來說,它們是一次性的;但第二種寫法里的Runnable任務邏輯,是可以被復用的。
2.4 用lambda表達式(常用)
//使用lambda 表達式創(chuàng)建Runnable 子類對象
Thread t3 = new Thread (() -> System.out.println("使用匿名類創(chuàng)建Thread 子類對象"));
Thread t4 = new Thread (() -> {
System.out.println("使用匿名類創(chuàng)建Thread 子類對象");
});
2.5 實現(xiàn)Callable
// 用匿名內部類創(chuàng)建Callable對象
Callable<String> callableTask = new Callable<String>() {
public String call() throws Exception {
return "Callable任務執(zhí)行完成,返回結果";
}
};
// 包裝成FutureTask,再交給Thread
FutureTask<String> futureTask = new FutureTask<>(callableTask);
Thread t5 = new Thread(futureTask);2.6 線程池(ThreadFactory)
// 用匿名內部類創(chuàng)建ThreadFactory
ThreadFactory factory = new ThreadFactory() {
private int count = 0;
public Thread newThread(Runnable r) {
// 自定義線程名,方便調試
return new Thread(r, "自定義線程-" + (++count));
}
};
// 創(chuàng)建固定大小線程池
ExecutorService pool = Executors.newFixedThreadPool(3, factory);
// 提交任務(用匿名內部類Runnable)
pool.submit(new Runnable() {
public void run() {
System.out.println("線程池中的任務執(zhí)行");
}
});
// 關閉線程池
pool.shutdown();三,Thread類
2.1 常見構造方法
| 方法 | 說明 |
| Thread() | 創(chuàng)建線程對象 |
| Thread(Runnable target) | 使用Runnable對象創(chuàng)建線程對象 |
| Thread(String name) | 創(chuàng)建線程對象,并命名 |
| Thread(Runnable target,String name) | 使用Runnable對象創(chuàng)建線程對象并命名 |
| Thread(ThreadGroup gruop,Runnable target) | 線程可以被用來分組管理,分好的組為線 |
2.2 常見屬性
| 屬性 | 獲取方法 | 說明 |
| ID | getID( ) | ID是線程的唯一標識,不同的線程不會重復 |
| 名稱 | getName( ) | 名稱在各種調試工具用到 |
| 狀態(tài) | getState( ) | 表示線程當前所處的一個情況 |
| 優(yōu)先級 | getPriority( ) | 優(yōu)先級高的線程理論上來說更容易被調度到 |
| 是否后臺線程 | isDaemon( ) | JVM會在一個進程的所有非后臺線程結束后,才會結束運行 |
| 是否存活 | isAlive( ) | 簡單的理解,為run 方法是否運行結束了 |
| 是否被中斷 | isInterrupted( ) | 檢查線程是否收到中斷信號 |
四,線程相關操作
4.1 啟動線程 start( )
上面我們介紹了覆寫run( )方法創(chuàng)建一個線程對象,但線程對象被創(chuàng)建出來并不意味著線程已經(jīng)開始運行了,而調用start( )方法,才真的在操作系統(tǒng)的底層創(chuàng)建出一個線程,線程才真正的獨立執(zhí)行
舉個栗子:我(主線程)奶茶店店長,我先寫了一份完整的“工作流程”(new Thread()),我在紙上寫下了做奶茶的步驟(run( ) 方法),這是我自己按照流程做一杯(調用run( ),并不會啟動新的線程)。然后我畫了個店員的名牌,寫著 “店員 A”(創(chuàng)建了線程對象),此時店員A開始按步驟工作(調用start( )),這是店里就我和店員A同時干活,效率提升。
4.2 中斷線程
舉個栗子:我(主線程)安排店員A(子線程)去做奶茶,3秒之后,我告訴店員A“先不做奶茶了,過來點單”,店員A立刻停下做奶茶,去點單。
常見的有以下兩種方法:
a)通過共享的標記來進行溝通,使用自定義變量作為標志位(volatile)
b)調用interrupt(方法來通知)
代碼實例:
//采用 a)方法
public class demo {
private static class MyRunnable implements Runnable{
//volatile相當于一個主線程和子線程公用的開關
public volatile boolean isQuit=false;
@Override
public void run() {
while(!isQuit){
//如果沒有收到停止指令,循環(huán)一直滿足,開始做奶茶
System.out.println(Thread.currentThread().getName()+":我正在忙著做奶茶");
try{
//做一杯奶茶需要1s,也就是1s打印一次
Thread.sleep(1000);//這里把光標放在sleep下用alt+enter快捷鍵可以直接生成try,catch
} catch (InterruptedException e) {
e.printStackTrace();
}
}
//不進入while循環(huán)也就是收到停止指令時
System.out.println(Thread.currentThread().getName()+":我現(xiàn)在來幫忙點單");
}
}
public static void main(String[] args) throws InterruptedException {
MyRunnable target=new MyRunnable();
Thread thread=new Thread(target,"店員A");
System.out.println(Thread.currentThread().getName()+":A,快去做奶茶");
//讓A開始工作
thread.start();
//我去做3秒的事,a接著做奶茶每秒打印一次
Thread.sleep(3000);
//sleep結束,我叫a停止做奶茶
System.out.println(Thread.currentThread().getName()+":A先別做奶茶了,快過來幫忙點單");
target.isQuit=true;
}
}volatile就是保證:主線程改了isQuit,子線程可以立刻獲取。其核心就是修改了內存可見性問題(內存可見性將下面線程安全產(chǎn)生原因中進行詳細講解)
//采用 b)方法
public class demo2 {
private static class MyRunnable implements Runnable{
@Override
public void run() {
//while(!Thread.isterrupted())
while (!Thread.currentThread().isInterrupted()){
System.out.println(Thread.currentThread().getName()+":我正忙著做奶茶");
try {
Thread.sleep(1000);
} catch (InterruptedException e) {
//收到中斷信號,處理異常
e.printStackTrace();
System.out.println(Thread.currentThread().getName()+":收到通知,停下手中的活");
//需著重注意這里的break,跳出循環(huán),結束當前任務
break;
}
}
//循環(huán)結束,執(zhí)行中斷后的收尾工作
System.out.println(Thread.currentThread().getName()+":我現(xiàn)在過來點單");
}
}
public static void main(String[] args) throws InterruptedException {
MyRunnable target=new MyRunnable();
Thread thread=new Thread(target,"A");
System.out.println(Thread.currentThread().getName()+":快去做奶茶");
thread.start();
Thread.sleep(5000);
System.out.println(Thread.currentThread().getName()+":A先別做奶茶了過來點單");
thread.interrupt();
}
}| 方法 | 類型 | 作用 | 是否清除中斷標志 |
|---|---|---|---|
Thread.currentThread().isInterrupted() | 實例方法 | 檢查當前線程的中斷狀態(tài) | ? 不清除 |
Thread.interrupted() | 靜態(tài)方法 | 檢查當前線程的中斷狀態(tài) | ? 會清除(調用后標志位變?yōu)?code>false) |
使用Thread.interrupted( )方法來通知線程結束或Thread.currentThread( ).isInterrupted( )代替自定義標志位
在這里thread收到通知有兩種方法
1.如果線程因為調用wait/join/sleep等方法而阻塞掛起,則以InterruptedException 異常的形式通知,清除中斷標志,這個時候可以在catch中加入break;
2.只是內部的一個中斷標志被設置,thread 可以通過Thread.currentThread().isInterrupted()判斷指定線程的中斷標志被設置,不清除中斷標志這種方式通知收到的更及時,即使線程正在sleep 也可以馬上收到。
4.3 等待線程 join( )
join可以要求多個線程間結束的線程順序
t.join( )就是等t線程結束在完成別的,只要t不結束就一直等
| 方法 | 說明 |
| public void join() | 等待線程結束 |
| public void join(long millis) | 等待線程結束,最多等millis毫秒 |
| public void join(long millis,int nanos) | 等待線程結束,精度更高 |
public class demo3 {
public static void main(String[] args) throws InterruptedException {
Runnable target=()->{
for(int i=0;i<3;i++){
try {
System.out.println(Thread.currentThread().getName()+ ":我還在工作!");
Thread.sleep(1000);
} catch (InterruptedException e) {
e.printStackTrace();
}
}
System.out.println(Thread.currentThread().getName() + ":我結束了!");
};
Thread thread1=new Thread(target,"A");
Thread thread2=new Thread(target,"B");
System.out.println("先讓A開始工作");
thread1.start();
thread1.join();
System.out.println("A工作結束,B開始工作");
thread2.start();
thread2.join();
System.out.println("B工作結束");
}
}這里的拋異常也不需要手動去寫,alt+enter快捷鍵即可
4.4 獲取當前線程引用
已經(jīng)很熟悉了就不再贅述了
public static Thread currentThread( ); //返回當前線程對象的引用
4.5 休眠線程 sleep
因為線程的調度是不可控的,這個方法只能保證實際休眠時間是大于等于參數(shù)設置的休眠時間的
| 方法 | 說明 |
| public static void sleep(long millis)throws InterruptedException | 休眠當前線程millis毫秒 |
| public static void sleep(long millis,int nanos)throws InterruptedException | 更高精度的休眠 |
這里的拋異常也不需要手動去寫,alt+enter快捷鍵即可
五,線程狀態(tài)
查看所有線程狀態(tài)代碼:
public class demo4 {
public static void main(String[] args) {
for (Thread.State state:Thread.State.values()) {
System.out.println(state);
}
}
}代碼運行結果:

NEW:安排了工作,還未開始行動
RUNNABLE:可工作的.又可以分成正在工作中和即將開始工作.(就緒:線程在cpu上執(zhí)行;線程隨時可以去cpu上執(zhí)行)
BLOCKED:表示排隊等著其他事情(由于鎖導致的阻塞)
WAITING:表示排隊等著其他事情(死等,無超時時間阻塞等待)
TIMED_WAITING:表示排隊等著其他事情(線程阻塞,不參與cpu調度,不繼續(xù)執(zhí)行,阻塞時間是有上限的)
TERMINATED:工作完成了,內核中的線程已結束,但Thread對象還在

六,線程安全
6.1 什么是線程安全
一段代碼,如果多線程環(huán)境下代碼運行的結果是符合預期的(在單線程環(huán)境應該的結果),則說這個程序是線程安全的。
6.2 線程安全問題產(chǎn)生原因
1)操作系統(tǒng)對于線程的調度使搶占式隨即執(zhí)行的(根本原因)
2)多個線程同時修改同一個變量
3)修改操作不是原子的(如果修改操作只是對應一個cpu指令,就認為是原子的)
4)內存可見性問題(內存可見性:即一個線程對共享變量值的修改能夠及時的被其他線程看見)
5)指令重排序問題
七,小結
文章主要介紹了線程的基本概念、線程和進程的區(qū)別、線程的創(chuàng)建方法、線程的常見方法與屬性、線程的安全問題題,其中,線線程是CPU執(zhí)行任務的單位user補充一下,線程是程序的基本單位,線程是程序執(zhí)行流的最小單位;線程之間可以并發(fā)執(zhí)行,并共享數(shù)據(jù),一個程序至少有一個線程;線;線程的生命周期狀態(tài)包括新建、就緒、運行、阻塞、死亡等;線程間通信通過共享變量、信號量等互斥量等進行;線程的安全性則關鍵性是否變量的可見性;線程的優(yōu)先級與影響線程的調度;線程池可以復用管理線程,減少線程創(chuàng)建銷毀的開;創(chuàng)建線程池的方法包括自定義實現(xiàn)的線程池常使用的的線程池類;線程的常用方法包括is、join、sleep,interrupt等;線程的狀態(tài)包括NEW、RUNNABLE、BLOCKED、WAITING、TIMED_WAITING、TERMINMIN,;線線程安全問題主要由操作系統(tǒng)調度引起的、線程間共享變量的可見引起的、非原子操作引起的、指令重排序引起的。
到此這篇關于Java多線程編程:線程的原理和使用安全的文章就介紹到這了,更多相關Java多線程編程內容請搜索腳本之家以前的文章或繼續(xù)瀏覽下面的相關文章希望大家以后多多支持腳本之家!
相關文章
java執(zhí)行SQL語句實現(xiàn)查詢的通用方法詳解
這篇文章主要介紹了java執(zhí)行SQL語句實現(xiàn)查詢的通用方法詳解,具有一定借鑒價值,需要的朋友可以參考下。2017-12-12
Java?BigDecimal?解決浮點精度問題(用法實踐)
本文詳細介紹了Java中BigDecimal類的原理、用法及最佳實踐,用于解決浮點數(shù)精度丟失問題,特別是在金融計算等對精度要求極高的場景中,本文將詳細講解其原理、用法及最佳實踐,感興趣的朋友跟隨小編一起看看吧2026-02-02

