1、什么是線程
單核CPU=一個車間:一次執(zhí)行一個進程,如果執(zhí)行多個程序,會在多個進程中來回切換,執(zhí)行到進程里面會在多個線程之間來回切換。多核CPU=一個工廠:每次可執(zhí)行多個進程;進程:一個車間為一個進程(一個運行的程序);進程是一種重量級的資源,系統(tǒng)會分配內存和CPU資源,啟動和停止慢,內存相互獨立線程:車間內一個工人為一個線程;多線程:一個進程包含多個線程;多個線程都可以共享一個進程的內存空間;
1.1、什么是多線程?
- 多線程是在CPU切換到某個進程之后,會在多個線程之間來回切換,每個線程就會分配到一定的cpu時間,線程是CPU分配時間的單元
1.2、并行和并發(fā)
- 并行:多個cpu同時執(zhí)行多個線程
- 并發(fā):一個CPU同時執(zhí)行多個線程,CPU在線程之間來回切換,讓線程都能執(zhí)行(不是同時執(zhí)行)
1.3、同步和異步
- 同步:多個指令是依次執(zhí)行的,一個指令執(zhí)行時會阻塞當前線程,其他指令必須要在該指令完成之后執(zhí)行。
- 異步:多個線程同時指向自己的命令,一個線程執(zhí)行完后,給另一個線程通知
2、多線程的應用場景
- 大型企業(yè)級應用都有高并發(fā)的特點,因為會大量的用戶,比如:淘寶、京東、抖音等。如果服務器是單線程,所有用戶必須排隊執(zhí)行,必須為每個用戶的每個請求,分配一個獨立的線程,完成獨立任務,相互不影響。單機版程序(如:大型游戲)需要執(zhí)行大量的任務:圖形渲染、動作控制、網(wǎng)絡通信等。需要多線程同時執(zhí)行上面任務。
3、啟動線程的方法
3.1、繼承Thread
public class MyThread extends Thread{
@Override
public void run() {
for (int i = 0; i <100 ; i++) {
System.out.println("hello 我是"+Thread.currentThread().getName());
}
}
public static void main(String[] args) {
MyThread myThread = new MyThread();
myThread.start();//一個線程不能調用兩次以上的start方法 IllegalThreadStateException
}
}
3.2、實現(xiàn)Runnable接口
public class MyRunnable implements Runnable{
@Override
public void run() {
System.out.println("Runnable :"+ Thread.currentThread().getName());
}
public static void main(String[] args) {
MyRunnable myRunnable = new MyRunnable();
Thread thread = new Thread(myRunnable);
thread.start();
//匿名內部類寫法
new Thread(new Runnable() {
@Override
public void run() {
System.out.println("匿名內部類"+Thread.currentThread().getName());
}
}).start();
//lambda表達式寫法
new Thread(() ->{
System.out.println("lambda:"+Thread.currentThread().getName());
}).start();
}
}
3.3、實現(xiàn)Callable接口(有返回值)
public class MyCallable implements Callable<Long> {//需指定返回值類型
@Override
public Long call() throws Exception {
return 1+1L;
}
public static void main(String[] args) {
//創(chuàng)建MyCallable對象
MyCallable myCallable = new MyCallable();
//創(chuàng)建FutureTask對象
FutureTask<Long> futureTask = new FutureTask<>(myCallable);
//創(chuàng)建線程對象
Thread thread = new Thread(futureTask);
thread.start();
try {
System.out.println("獲得結果:"+futureTask.get());
} catch (InterruptedException e) {
e.printStackTrace();
} catch (ExecutionException e) {
e.printStackTrace();
}
}
}
4、線程的啟動和停止
4.1、線程啟動
- 面試題:啟動線程是執(zhí)行start方法還是run方法?
- start()方法
- 只有調用了start方法,系統(tǒng)才會創(chuàng)建新的線程,直接調用run方法,會在主線程中執(zhí)行
- 注意:不要手動調用start方法,一個start方法只能被一個線程對象調用一次
4.2、線程停止
- stop方法(強制停止,不能釋放鎖資源,可能造成死鎖等嚴重問題,禁用)
- 等待run方法運行完
- 通過條件中斷run的運行
5、線程的睡眠
Thread.sleep(毫秒)- 線程一旦進入睡眠,會讓出CPU
- 使用場景:執(zhí)行大量任務時,進行一定睡眠,可以減少CPU消耗
6、后臺進程
- 也叫守護線程,精靈線程
- 將一個線程設置為后臺線程:
setDaemon(true) - 守護線程的作用是為其他線程提供服務,一旦其他線程停止了,守護線程會自動停止
7、線程的合并
- 當前線程可以合并其他線程,當其他線程操作執(zhí)行完,再執(zhí)行當前線程的操作。
- 注意:兩個線程不能互相合并,否則會出現(xiàn)死鎖的問題
8、線程的優(yōu)先級
- 默認情況下線程的執(zhí)行是搶占式的,沒有特定的次序,不同的優(yōu)先級獲得CPU的概率不同
-
setPriority(int)設置優(yōu)先級,默認是5,可設置范圍1~10
9、線程的生命周期
[圖片上傳失敗...(image-f8805d-1600949547611)]
- 新建:對象剛被創(chuàng)建出來,還沒有調用start
- 就緒:調用了start,但沒有搶到CPU,不能執(zhí)行
- 運行:搶到了CPU,正在執(zhí)行run
- 阻塞:線程遇到某些情況,暫停執(zhí)行
- 死亡:run方法執(zhí)行完
10、線程同步問題
10.1、為什么會出現(xiàn)線程同步問題?
- 線程是搶占式的,一個線程在執(zhí)行指令的時候,可能被其他線程中斷,可能會出現(xiàn)數(shù)據(jù)不一致的情況
- 多個線程同時操作一個資源
10.2、線程同步問題的解決方案(上鎖機制)
-
同步方法
- 語法
public synchronized 返回類型 方法名(參數(shù)){ }- 作用是對整個方法上鎖,保證代碼的原子性,一旦一個線程進入方法后,就會持有鎖,其他線程不能進入該方法,等線程執(zhí)行完,其他線程才能進去。
- 相關面試題:
-
StringBuffer與StringBuilder的區(qū)別 -
ArrayList和Vector的區(qū)別 -
HashMap和Hashtable的區(qū)別
-
- 相關知識點
- 同步方法鎖的對象是this,當前的對象
- 鎖機制:上鎖后,JVM會啟動監(jiān)視器(Monitor),監(jiān)視進入代碼塊或方法體的線程,如果方法體或代碼塊的鎖被某個線程所有,監(jiān)視器就會拒絕其他線程進入代碼塊或方法,直到持有鎖的線程執(zhí)行完,釋放鎖,才會讓其他線程進去
- 一旦上鎖,程序的性能會有所降低
-
同步代碼塊
- 語法
public 返回類型 方法名(參數(shù)){ ... synchronized(鎖對象){ 代碼 } ... }- 注意
- 任何Java對象(Object)都可以成為鎖對象
- 鎖對象不能是局部變量
- 一旦線程進入代碼塊就上鎖,持有鎖對象,執(zhí)行完代碼塊后自動釋放鎖,拋異常也會釋放鎖
- 對比同步方法和同步代碼塊
- 同步方法更簡潔
- 同步方法鎖粒度更大(粒度越大鎖的范圍越大),粒度越大性能越差,同步代碼塊的性能高于同步方法
-
同步鎖
- 在java1.5出現(xiàn),在
java.util.concurrent包中 - Lock接口,最上層接口
- 子接口:
ReadLock、WriteLock、ReadWriteLock - 常用的實現(xiàn)類:
ReentrantLock重入鎖 - 創(chuàng)建對象:
Lock lock = new RentrantLock() - 常用方法:
lock.lock()上鎖、lock.unlock()釋放鎖 - 語法
lock.lock(); try{ 代碼 }finally{ lock.unlock(); } - 在java1.5出現(xiàn),在
11、單例模式
-
單例模式主要分位餓漢式和懶漢式,作用是保證一個類只有一個實例對象
- 優(yōu)點:
- 減少系統(tǒng)資源的消耗
- 符合某些特殊業(yè)務的要求
- 優(yōu)點:
-
餓漢式
- 一開始就創(chuàng)建對象,不管
getInstance()是否被調用,內存資源都會被消耗掉 - 系統(tǒng)中的Runtime屬于餓漢式
public class Singleton01 { private static Singleton01 instance = new Singleton01(); private Singleton01(){ } private static Singleton01 getInstance(){ return instance; } } - 一開始就創(chuàng)建對象,不管
-
懶漢式
- 一開始不創(chuàng)建對象,
getInstance()被調用時候創(chuàng)建對象,內存一開始不消耗 - 存在線程同步問題(需要在
getInstance()方法中判斷對象是否為null)
//普通懶漢式 public class Singleton02 { private static Singleton02 instance ; private Singleton02(){ } private static Singleton02 getInstance(){ if (instance == null){ instance = new Singleton02(); } return instance; } }//雙檢鎖懶漢式 public class Singleton06 { private static Singleton06 instance ; private Singleton06(){ } private static Singleton06 getInstance(){ if (instance == null){ synchronized (Singleton06.class){ if (instance == null){ instance = new Singleton06(); } } } return instance; } } - 一開始不創(chuàng)建對象,
12、volatile關鍵字
- 用于修飾變量,提高線程可見性,是線程同步的輕量級解決方案,能保證可見性,不能保證原子性
- 可見性:變量的值每個線程都能直接獲取到
- 原子性:代碼作為整體運行,不可再分
- 變量加上volatile關鍵字后,線程直接從主內存讀取值,不是從緩存讀取,任意線程修改變量后,其他線程都可以看到
- 變量沒有被
volatile修飾
在這里插入圖片描述
- 變量被
volatile修飾

image.png
13、線程死鎖
- 出現(xiàn)的情況:
- 上鎖后沒有正常釋放鎖
- 兩個線程都持有對方需要的鎖,又需要對方持有的鎖,就可能進入相互等待的情況
public class LockTest {
private static Object lock1 = new Object();
private static Object lock2 = new Object();
public static void main(String[] args) {
Thread thread1 = new Thread(() -> {
for (int i = 0; i < 100; i++) {
synchronized (lock1){
synchronized (lock2){
System.out.println("thread1------------"+i);
try {
Thread.sleep(100);
} catch (InterruptedException e) {
e.printStackTrace();
}
}
}
}
});
Thread thread2 = new Thread(()->{
for (int i = 0; i < 100; i++) {
synchronized (lock2){
synchronized (lock1){
System.out.println("thread2------------"+i);
}
}
}
});
thread1.start();
thread2.start();
}
}
14、線程的等待和通知
通過鎖對象的調用,定義在Object類中
-
等待
wait:讓線程進入阻塞狀態(tài)-
wait():一直等待,直到被通知 -
wait(毫秒):等待指定時長,線程可以被通知或自動喚醒
-
-
通知
notify:讓等待狀態(tài)的線程從阻塞狀態(tài)恢復到就緒狀態(tài)-
notify():通知一個等待線程 -
notifyAll():通知所有等待線程
-
注意,只有鎖對象才能調用notify或者wait方法,必須在同步代碼塊的鎖對象或同步方法的this調用。否則會出現(xiàn)
IllegalMonitorStateException異常
15、wait和sleep的區(qū)別?
- wait會釋放鎖資源,sleep不會
- sleep是線程對象調用,wait是鎖對象調用;
- sleep必須等睡眠時間結束,wait可以等時間結束,也可以被喚醒
16、生產(chǎn)者消費者模式
不是GOF23設計模式之一,是與線程相關的設計模式
-
作用:
- 解耦:讓生產(chǎn)者消費者之間解耦,生產(chǎn)者消費者之間不會直接訪問
- 高并發(fā):生產(chǎn)者消費者解耦,生產(chǎn)者和消費者不需要互相等待,生產(chǎn)者能處理更多的消費者請求
- 解決忙閑不均:解決了生產(chǎn)者速度太快消費者需求太低,或者生產(chǎn)者速度太慢,消費者需求太大的問題
-
實現(xiàn)過程:
- 定義緩沖區(qū),用于存放數(shù)據(jù),緩沖區(qū)有上限
- 生產(chǎn)者線程將生產(chǎn)數(shù)據(jù)存入緩沖區(qū),當緩沖區(qū)滿了,讓生產(chǎn)者等待,如果沒滿,通知生產(chǎn)者繼續(xù)生產(chǎn)
- 消費者從緩沖區(qū)取數(shù)據(jù),當緩沖區(qū)空了,讓消費者等待,等有數(shù)據(jù),通知消費者繼續(xù)消費
17、阻塞隊列
- 是一種集合,根據(jù)數(shù)據(jù)的個數(shù)自動產(chǎn)生阻塞
-
BlockingQueue<T>父接口 - 常用實現(xiàn)類:
-
LinkedBlockingQueue鏈表結構的阻塞隊列 (插入刪除效率高) -
ArrayBlockingQueue數(shù)組結構的阻塞隊列(查詢效率高)
-
- 常用方法
-
put()添加數(shù)據(jù)到末尾,如果達到上線,就阻塞當前線程 -
take()從隊列頭刪除數(shù)據(jù),如果為空,就阻塞
-
18、線程池
18.1、線程的作用?
- 回收線程資源,線程是一種很重要的資源,線程的創(chuàng)建和銷毀都很消耗資源,頻繁的創(chuàng)建和銷毀線程會降低程序的性能
- 注意:線程池中的線程執(zhí)行完任務后不是直接死亡,而是回到池中,可以重復利用
18.2、線程池API
-
Executor接口-
execute(Runnable)執(zhí)行單個線程任務
-
-
ExecutorService接口-
showdown:關閉 -
shutdownNow:立刻關閉 -
submit:提交
-
ThreadPoolExecutor線程實現(xiàn)類-
Executors工具類幫助創(chuàng)建不同類型的線程池
-
主要方法:
-
ExecutorService newCachedThreadPool()長度不限的線程,不能控制并發(fā)量,速度更快
//長度不限線程池 public static void testCachedThreadPool(){ ExecutorService threadPool = Executors.newCachedThreadPool();//長度不限的線程池 for (int i = 0; i < 10; i++) { threadPool.execute(()->{ System.out.println("當前執(zhí)行的是"+Thread.currentThread().getName()); }); try { Thread.sleep(1); } catch (InterruptedException e) { e.printStackTrace(); } } threadPool.shutdown(); }-
ExecutorService newFixedThreadPool(int)長度固定的線程池,可以控制并發(fā)量,并發(fā)量大時,需要排隊
//長度固定線程池 public static void testFixedThreadPool(){ ExecutorService threadPool = Executors.newFixedThreadPool(5);//長度不限的線程池 for (int i = 0; i < 10; i++) { threadPool.execute(()->{ System.out.println("當前執(zhí)行的是"+Thread.currentThread().getName()); try { Thread.sleep(3000); } catch (InterruptedException e) { e.printStackTrace(); } }); } threadPool.shutdown(); }-
ExecutorService newSingleThreadExecutor()單線程線程池
//單線程線程池 public static void testSingleThreadPool(){ ExecutorService threadPool = Executors.newSingleThreadExecutor();//長度不限的線程池 for (int i = 0; i < 10; i++) { threadPool.execute(()->{ System.out.println("當前執(zhí)行的是"+Thread.currentThread().getName()); try { Thread.sleep(3000); } catch (InterruptedException e) { e.printStackTrace(); } }); } threadPool.shutdown(); }-
ScheduledExecutorService newScheduledThreadPool(int)可調度的線程池,執(zhí)行線程時,可以設置執(zhí)行周期和延時
public static void testScheduleThreadPool(){ ScheduledExecutorService threadPool = Executors.newScheduledThreadPool(10); threadPool.scheduleAtFixedRate(()->{ System.out.println("當前執(zhí)行的是"+Thread.currentThread().getName()); },5,1, TimeUnit.SECONDS); //threadPool.scheduleWithFixedDelay() } -
-
自定義線程池
- 構造方法:
ThreadPoolExector - 核心線程數(shù):
corePoolSize - 最大線程數(shù):
maximumPoolSize - 存活時間:
keepAliveTime - 時間單位:
timeUnit - 阻塞隊列,保存執(zhí)行任務(Runnable):
workingQueue - 優(yōu)化配置
- 核心線程配置和最大線程數(shù)一樣,減少創(chuàng)建新線程和銷毀線程的開銷
- 核心線程數(shù)配置和
cpu核心數(shù)相關, 核心數(shù) * N (N >= 1) N具體看任務量、執(zhí)行時間等情況 - 阻塞隊列使用
LinkedBlockingQueue,添加和刪除任務效率高
- 構造方法:
public static void myselfThreadPool(){
int processors = Runtime.getRuntime().availableProcessors();
System.out.println(processors);
int n = 2;
ThreadPoolExecutor threadPool = new ThreadPoolExecutor(processors * n, processors * n, 60L, TimeUnit.SECONDS, new LinkedBlockingQueue<Runnable>());
for (int i = 0; i < 10; i++) {
threadPool.execute(()->{
System.out.println("當前執(zhí)行的是"+Thread.currentThread().getName());
});
}
threadPool.shutdown();
}
19、ThreadLocal
19.1、ThreadLocal是什么?
- ThreadLocal的作用是提供線程內的局部變量,這種變量在線程的生命周期內起作用,減少同一個線程內多個函數(shù)或者組件之間一些公共變量的傳遞的復雜度。
19.2、syncronized 與 ThreadLocal的區(qū)別?
- syncronized雖然能解決線程間變量的隔離的問題,解決的方法是當一個線程上鎖之后,拒絕其他線程訪問,當前只有一個線程在訪問這個資源,但是這種方式需要對每一個線程都加鎖,降低了程序的效率,也降低了程序的并發(fā)性
- syncronized 以時間換空間,讓所有線程排隊訪問,只提供了一份變量,側重的是多個線程之間訪問資源的同步
- ThreadLocal不需要加鎖,也能解決線程間變量的隔離性,可滿足程序的高并發(fā),變量只在線程的生命周期內起作用
- ThreadLocal以空間換時間,為每一個線程都提供一份單獨的副本,實現(xiàn)訪問互不干擾,每個線程都訪問自己獨有的那份變量,使用ThreadLocal可以保證程序擁有更高的并發(fā)性。
19.3、ThreadLocal的優(yōu)點?
- 綁定參數(shù):保存每個線程綁定的數(shù)據(jù),在需要的地方可以直接獲取,避免參數(shù)直接傳遞帶來的代碼耦合問題
- 線程隔離:各個線程之間的數(shù)據(jù)相互隔離又具有并發(fā)性,避免同步方法帶來的性能損失
19.4、ThreadLocal使用場景
- JDBC連接數(shù)據(jù)庫,獲取Connection的時候,為保證在高并發(fā)的情況下進行事務操作,保證在dao層getConnection()時候得到的對象和事務的Connection對象是同一個對象,就需要使用到ThreadLocal來實現(xiàn)線程之間的數(shù)據(jù)隔離,同時也不影響數(shù)據(jù)庫連接的性能。
- 解決方案,在DBUtils中定義ThreadLocal對象,當connection為空的時候,使用數(shù)據(jù)庫連接池獲取一個連接對象,然后將這個連接對象保存到ThreadLocal,之后調用getConnection()方法返回的是ThreadLocal中的connection,這樣就實現(xiàn)了connection對象的隔離,需要注意的是,事務提交之后需要解綁當前線程綁定的連接對象,
threadLocal.remove(),目的是為了避免內存泄漏
在這里插入圖片描述