尊重原創著作權: https://www.gewuweb.com/hot/12801.html
Java面試知識整理之—Java多執行緒篇
尊重原創著作權: https://www.gewuweb.com/sitemap.html
行程與執行緒
行程
是一個具有一定獨立功能的程式在一個資料集上的一次動態執行的程序,是作業系統進行資源分配和調度的一個獨立單位,是應用程式運行的載體行程是程式一次執行程序,比如程式從啟動到銷毀是一個行程,
執行緒 是比行程更小的單位,一個行程在執行程序中可以產生多個執行緒,執行緒之間可以共享“堆”和“Metadata
Space”資料,但擁有獨立的“程式計數器”、“虛擬機堆疊”和“本地方法堆疊”,所以執行緒之間切換比行程切換負擔小得多
cpu
執行緒的6種狀(NEW,RUNNABLE、BLOCKED,WAITING,TINED_WATING,TEMINATE)
執行緒狀態
為什么用多執行緒
隨著處理器的核心數不斷增多,單執行緒程式已無法發揮多核處理器的優勢,執行緒比行程切換效率更高,
使用多執行緒后引入的問題
多執行緒引入后可能引發的問題:記憶體泄露、死鎖、背景關系切換等,
多執行緒引入可能存在的問題
JAVA 執行緒實作/創建方式
1.通過繼承Thread類創建執行緒類
public class MyThread extends Thread{
@Override
public void run() {
//do something
}
}
//創建并啟動執行緒
new MyThread().start();
jdk1.8之后,可以通過Lambda運算式(函式式介面)來實作創建一個執行緒:
new Thread(()->{System.out.println("通過函式式介面創建一個執行緒!");}).start();
2.通過實作Runnable介面創建執行緒類
public class DemoThread implements Runnable{
@Override
public void run() {
//當執行緒類實作Runnable介面時,要獲取當前執行緒物件只有通過Thread.currentThread()獲取
System.out.println("當前執行緒:"+ Thread.currentThread().getName());
}
}
//創建并啟動執行緒
DemoThread c =new DemoThread();
new Thread(c,"執行緒1").start();
//通過內部匿名類實體創建一個執行緒
new Thread(new Runnable() {
@Override
public void run() {
System.out.println("通過內部匿名類實體創建一個執行緒!");
}
}).start();
Thread 類中start和run方法的區別
run方法只是thread的一個普通方法呼叫,還是在主執行緒里執行,是不會開啟多執行緒
start方法來啟動執行緒,真正實作了多執行緒運行,這時無需等待run方法體代碼執行完畢而直接繼續執行下面的代碼,通過呼叫Thread類的start()方法來啟動一個執行緒,這時此執行緒處于就緒(可運行)狀態,并沒有運行,一旦得到cpu時間片,就開始執行run()方法,這里方法run()稱為執行緒體,它包含了要執行的這個執行緒的內容,run方法運行結束,此執行緒隨即終止,
sleep 與 wait 區別
1.對于sleep()方法是屬于Thread 類中的,而 wait()方法則是屬于Object 類中的
2.sleep()方法導致了程式暫停執行指定的時間,讓出 cpu給其他執行緒,但是他的監控狀態依然保持著,當指定的時間到了又會自動恢復運行狀態,
3.在呼叫 sleep()方法的程序中,執行緒不會釋放物件鎖,
4.而當呼叫 wait()方法的時候,執行緒會放棄物件鎖,進入等待此物件的等待鎖定池,只有針對此物件呼叫
notify()方法后本執行緒才進入物件鎖定池準備獲取物件鎖進入運行狀態,
3.通過Callable和Future介面創建執行緒
public class CallThread implements Callable<String> {
@Override
public String call() throws Exception {
Thread.sleep(500);
return "callable and future";
}
}
CallThread callThread = new CallThread();
//使用執行緒池
ExecutorService executor = Executors.newCachedThreadPool();
Future<String> submit = executor.submit(callThread);
try {
String s = submit.get();
System.out.println("獲取結果:" + s);
} catch (InterruptedException e) {
e.printStackTrace();
} catch (ExecutionException e) {
e.printStackTrace();
}
//或者通過Thread啟動
FutureTask<String> result =new FutureTask<>(callThread);
new Thread(result).start();
//2.接收執行緒運算后的結果
try {
String s = submit.get();
System.out.println("獲取結果:" + s);
}catch (InterruptedException | ExecutionException e) {
e.printStackTrace();
}
使用Callable和Future創建執行緒的基本步驟
1、創建Callable介面實作類,并實作call()方法,該方法將作為執行緒執行體,且該方法有回傳值,再創建Callable實作類的實體;
2、使用FutureTask類來包裝Callable物件,該FutureTask物件封裝了該Callable物件的call()方法的回傳值;
3、使用FutureTask物件作為Thread物件的target創建并啟動新執行緒;
4、呼叫FutureTask物件的get()方法來獲得子執行緒執行結束后的回傳值,
4.基于執行緒池的方式
ExecutorService threadPool = Executors.newFixedThreadPool(10);
while(true) {
threadPool.execute(new Runnable() {
@Override
public void run() {
System.out.println(Thread.currentThread().getName() + " is running ..");
try {
Thread.sleep(3000);
} catch (InterruptedException e) { e.printStackTrace();
}
}
});
}
JAVA執行緒池
Java 里面執行緒池的頂級介面是 Executor,但是嚴格意義上講 Executor 并不是一個執行緒池,而只是一個執行執行緒的工具,真正的執行緒池介面是
ExecutorService,
Java通過Executors提供四種執行緒池,分別為:
1.newCachedThreadPool:創建一個可快取執行緒池,如果執行緒池長度超過處理需要,可靈活回收空閑執行緒,若無可回收,則新建執行緒,
2:newFixedThreadPool :創建一個定長執行緒池,可控制執行緒最大并發數,超出的執行緒會在佇列中等待,
3.newScheduledThreadPool :創建一個周期執行緒池,支持定時及周期性任務執行,
4.newSingleThreadExecutor: 創建一個單執行緒化的執行緒池,它只會用唯一的作業執行緒來執行任務,保證所有任務按照指定順序(FIFO,
LIFO, 優先級)執行,
java執行緒池
Java鎖的種類
Java鎖的種類可以分為以下幾大類:樂觀鎖/悲觀鎖、獨享鎖/共享鎖、互斥鎖/讀寫鎖、可重入鎖、公平鎖/非公平鎖、分段鎖、偏向鎖/輕量級鎖/重量級鎖、自旋鎖,
java 鎖
Synchronized 同步鎖
synchronized可以保證在同一個時刻,只有一個執行緒可以執行某個方法或者某個代碼塊,synchronized可保證一個執行緒的變化(主要是共享資料的變化)被其他執行緒所看到保證可見性,使用關鍵字synchronized實作同步是在JVM內部實作處理,對于應用開發人員來說它是隱式進行的,每個Java物件都有一個與之關聯的monitor,當執行緒呼叫實體同步方法時,會自動獲取實體物件的monitor,當執行緒呼叫靜態同步方法時,會自動獲取該類Class實體物件的monitor,
Monitor 直譯為監視器,中文圈里稱為管程,它的作用是讓執行緒互斥,保護共享資料,另外也可以向其它執行緒發送滿足條件的信號,
synchronized關鍵字最主要有以下3種應用方式:
修飾實體方法 :作用于當前實體加鎖,進入同步代碼前要獲得當前實體的鎖,關鍵指令 monitorenter 和 monitorexit
修飾靜態方法: 作用于當前類物件加鎖,進入同步代碼前要獲得當前類物件的鎖,關鍵訪問標志 ACC_SYNCHRONIZED
修飾代碼塊: 指定加鎖物件,對給定物件加鎖,進入同步代碼庫前要獲得給定物件的鎖,應用場景(
某些情況下,我們撰寫的方法體可能比較大,同時存在一些比較耗時的操作,而需要同步的代碼又只有一小部分,如果直接對整個方法進行同步操作,可能會得不償失,此時我們可以使用同步代碼塊的方式對需要同步的代碼進行包裹,這樣就無需對整個方法進行同步操作了
)
synchronized 鎖
Java1.6開始synchronized 進行了很多的優化, 有適應自旋、鎖消除、鎖粗化、輕量級鎖及偏向鎖等 ,效率有了本質上的提高,在之后推出的
Java1.7 與 1.8 中,均對該關鍵字的實作機理做了優化,引入了 偏向鎖和輕量級鎖 ,都是在物件頭中有標記位,不需要經過作業系統加鎖,
Java鎖Lock介面
Lock和synchronized有一點非常大的不同,采用synchronized不需要用戶去手動釋放鎖,當synchronized方法或者代碼塊執行完之后,系統會自動讓執行緒釋放對鎖的占用;而Lock則必須要用戶去手動釋放鎖,如果沒有主動釋放鎖,就有可能導致出現死鎖現象,
Lock介面有ReentrantLock(可重入鎖)、ReentrantReadWriteLock類(讀寫鎖)
ReentrantLock
:ReentrantLock是Lock介面一種常見的實作,它是支持重進入的鎖即表示該鎖能夠支持一個執行緒對資源的重復加鎖,該鎖還支持設定獲取鎖時的公平與非公平的選擇,
public class LockThread {
//通過建構式傳入布爾引數設定公平鎖true和非公平鎖
//公平鎖內部是FairSync,非公平鎖內部是NonfairSync,
//而不管是FairSync還是NonfariSync,都間接繼承自AbstractQueuedSynchronizer這個抽象類
Lock lock = new ReentrantLock();
public void lock(String name) {
// 獲取鎖
lock.lock();
try {
System.out.println(name + " get the lock");
// 訪問此鎖保護的資源
} finally {
// 必須手動釋放鎖
lock.unlock();
System.out.println(name + " release the lock");
}
}
}
AbstractQueuedSynchronizer(AQS)
AQS(AbstractQueuedSynchronizer)抽象佇列同步器如ReentrantLock,Semaphore,ReentrantReadWriteLock,SynchronousQueue,FutureTask等等皆是基于AQS的,AQS的內部結構主要由同步等待佇列構成,
**AQS中定義了一個狀態變數state(我們查看原始碼可以發現定義為 private volatile int state ) **
,它有以下兩種使用方法:
(1)互斥鎖
當AQS只實作為互斥鎖的時候,每次只要原子更新state的值從0變為1成功了就獲取了鎖,可重入是通過不斷把state原子更新加1實作的,
(2)互斥鎖 + 共享鎖
當AQS需要同時實作為互斥鎖+共享鎖的時候,低16位存盤互斥鎖的狀態,高16位存盤共享鎖的狀態,主要用于實作讀寫鎖,
AQS佇列:AQS中維護了一個佇列,獲取鎖失敗(非tryLock())的執行緒都將進入這個佇列中排隊,等待鎖釋放后喚醒下一個排隊的執行緒(互斥鎖模式下)
Condition佇列:AQS中還有另一個非常重要的內部類ConditionObject,它實作了Condition介面,主要用于實作條件鎖,
AQS
AtomicInteger
AtomicInteger ,一個提供原子操作的 Integer
的類,常見的還有AtomicBoolean、AtomicInteger、AtomicLong、AtomicReference 等,他們的實作原理相同,
區別在于運算物件型別的不同,還可以通過 AtomicReference將一個物件的所有操作轉化成原子操作, 在多執行緒程式中,諸如++i 或 i++等運算不具有原子性,是不安全的執行緒操作之一,通常我們會使用 synchronized 將該操作變成一個原子操作,但
JVM 為此類操作特意提供了一些同步類,使得使用更方便,且使程式運行效率變得更高,通過相關資料顯示,通常AtomicInteger 的性能是
ReentantLock 的好幾倍
volatile
volatile主要用于解決可見性,它修飾變數,相當于對當前陳述句前后加上了“記憶體柵欄”,使當前代碼之前的代碼不會被重排到當前代碼之后,當前代碼之后的指令不會被重排到當前代碼之前,一定程度保證了有序性,而volatile最主要的作用是使修改volatile修飾的變數值時會使所有執行緒中的快取失效,并強制寫入公共主存,保證了各個執行緒的一致,可以看做是輕量級的Synchronized
ReentrantReadWriteLock類(讀寫鎖) - 實作ReadWriteLock介面
支持一寫多讀的同步鎖,讀寫分離,可分別分配讀鎖、寫鎖,應用場景:讀高于寫操作的環境下,可在保證執行緒安全的情況下,提高程式運行效率
public class FileThread {
private int age;
ReentrantReadWriteLock rrwl = new ReentrantReadWriteLock(); // 有兩把鎖
ReentrantReadWriteLock.ReadLock read = rrwl.readLock(); // 讀鎖 - 內部類
ReentrantReadWriteLock.WriteLock write = rrwl.writeLock(); // 寫鎖 - 內部類
// 賦值:寫操作
public void setAge(int age) throws InterruptedException {
write.lock();
try {
Thread.sleep(1000);
this.age = age;
} finally {
write.unlock();
}
}
// 取值:讀操作
public int getAge() throws InterruptedException {
read.lock();
try {
Thread.sleep(1000);
return this.age;
} finally {
read.lock();
}
}
}
volatile
volatile
尊重原創著作權: https://www.gewuweb.com/sitemap.html
尊重原創著作權: https://www.gewuweb.com/hot/14074.html
Java面試知識整理之—Java多執行緒篇
行程與執行緒
行程
是一個具有一定獨立功能的程式在一個資料集上的一次動態執行的程序,是作業系統進行資源分配和調度的一個獨立單位,是應用程式運行的載體行程是程式一次執行程序,比如程式從啟動到銷毀是一個行程,
執行緒 是比行程更小的單位,一個行程在執行程序中可以產生多個執行緒,執行緒之間可以共享“堆”和“Metadata
Space”資料,但擁有獨立的“程式計數器”、“虛擬機堆疊”和“本地方法堆疊”,所以執行緒之間切換比行程切換負擔小得多
cpu
執行緒的6種狀(NEW,RUNNABLE、BLOCKED,WAITING,TINED_WATING,TEMINATE)
執行緒狀態
為什么用多執行緒
隨著處理器的核心數不斷增多,單執行緒程式已無法發揮多核處理器的優勢,執行緒比行程切換效率更高,
使用多執行緒后引入的問題
多執行緒引入后可能引發的問題:記憶體泄露、死鎖、背景關系切換等,
多執行緒引入可能存在的問題
JAVA 執行緒實作/創建方式
1.通過繼承Thread類創建執行緒類
public class MyThread extends Thread{
@Override
public void run() {
//do something
}
}
//創建并啟動執行緒
new MyThread().start();
jdk1.8之后,可以通過Lambda運算式(函式式介面)來實作創建一個執行緒:
new Thread(()->{System.out.println("通過函式式介面創建一個執行緒!");}).start();
2.通過實作Runnable介面創建執行緒類
public class DemoThread implements Runnable{
@Override
public void run() {
//當執行緒類實作Runnable介面時,要獲取當前執行緒物件只有通過Thread.currentThread()獲取
System.out.println("當前執行緒:"+ Thread.currentThread().getName());
}
}
//創建并啟動執行緒
DemoThread c =new DemoThread();
new Thread(c,"執行緒1").start();
//通過內部匿名類實體創建一個執行緒
new Thread(new Runnable() {
@Override
public void run() {
System.out.println("通過內部匿名類實體創建一個執行緒!");
}
}).start();
Thread 類中start和run方法的區別
run方法只是thread的一個普通方法呼叫,還是在主執行緒里執行,是不會開啟多執行緒
start方法來啟動執行緒,真正實作了多執行緒運行,這時無需等待run方法體代碼執行完畢而直接繼續執行下面的代碼,通過呼叫Thread類的start()方法來啟動一個執行緒,這時此執行緒處于就緒(可運行)狀態,并沒有運行,一旦得到cpu時間片,就開始執行run()方法,這里方法run()稱為執行緒體,它包含了要執行的這個執行緒的內容,run方法運行結束,此執行緒隨即終止,
sleep 與 wait 區別
1.對于sleep()方法是屬于Thread 類中的,而 wait()方法則是屬于Object 類中的
2.sleep()方法導致了程式暫停執行指定的時間,讓出 cpu給其他執行緒,但是他的監控狀態依然保持著,當指定的時間到了又會自動恢復運行狀態,
3.在呼叫 sleep()方法的程序中,執行緒不會釋放物件鎖,
4.而當呼叫 wait()方法的時候,執行緒會放棄物件鎖,進入等待此物件的等待鎖定池,只有針對此物件呼叫
notify()方法后本執行緒才進入物件鎖定池準備獲取物件鎖進入運行狀態,
3.通過Callable和Future介面創建執行緒
public class CallThread implements Callable<String> {
@Override
public String call() throws Exception {
Thread.sleep(500);
return "callable and future";
}
}
CallThread callThread = new CallThread();
//使用執行緒池
ExecutorService executor = Executors.newCachedThreadPool();
Future<String> submit = executor.submit(callThread);
try {
String s = submit.get();
System.out.println("獲取結果:" + s);
} catch (InterruptedException e) {
e.printStackTrace();
} catch (ExecutionException e) {
e.printStackTrace();
}
//或者通過Thread啟動
FutureTask<String> result =new FutureTask<>(callThread);
new Thread(result).start();
//2.接收執行緒運算后的結果
try {
String s = submit.get();
System.out.println("獲取結果:" + s);
}catch (InterruptedException | ExecutionException e) {
e.printStackTrace();
}
使用Callable和Future創建執行緒的基本步驟
1、創建Callable介面實作類,并實作call()方法,該方法將作為執行緒執行體,且該方法有回傳值,再創建Callable實作類的實體;
2、使用FutureTask類來包裝Callable物件,該FutureTask物件封裝了該Callable物件的call()方法的回傳值;
3、使用FutureTask物件作為Thread物件的target創建并啟動新執行緒;
4、呼叫FutureTask物件的get()方法來獲得子執行緒執行結束后的回傳值,
4.基于執行緒池的方式
ExecutorService threadPool = Executors.newFixedThreadPool(10);
while(true) {
threadPool.execute(new Runnable() {
@Override
public void run() {
System.out.println(Thread.currentThread().getName() + " is running ..");
try {
Thread.sleep(3000);
} catch (InterruptedException e) { e.printStackTrace();
}
}
});
}
JAVA執行緒池
Java 里面執行緒池的頂級介面是 Executor,但是嚴格意義上講 Executor 并不是一個執行緒池,而只是一個執行執行緒的工具,真正的執行緒池介面是
ExecutorService,
Java通過Executors提供四種執行緒池,分別為:
1.newCachedThreadPool:創建一個可快取執行緒池,如果執行緒池長度超過處理需要,可靈活回收空閑執行緒,若無可回收,則新建執行緒,
2:newFixedThreadPool :創建一個定長執行緒池,可控制執行緒最大并發數,超出的執行緒會在佇列中等待,
3.newScheduledThreadPool :創建一個周期執行緒池,支持定時及周期性任務執行,
4.newSingleThreadExecutor: 創建一個單執行緒化的執行緒池,它只會用唯一的作業執行緒來執行任務,保證所有任務按照指定順序(FIFO,
LIFO, 優先級)執行,
java執行緒池
Java鎖的種類
Java鎖的種類可以分為以下幾大類:樂觀鎖/悲觀鎖、獨享鎖/共享鎖、互斥鎖/讀寫鎖、可重入鎖、公平鎖/非公平鎖、分段鎖、偏向鎖/輕量級鎖/重量級鎖、自旋鎖,
java 鎖
Synchronized 同步鎖
synchronized可以保證在同一個時刻,只有一個執行緒可以執行某個方法或者某個代碼塊,synchronized可保證一個執行緒的變化(主要是共享資料的變化)被其他執行緒所看到保證可見性,使用關鍵字synchronized實作同步是在JVM內部實作處理,對于應用開發人員來說它是隱式進行的,每個Java物件都有一個與之關聯的monitor,當執行緒呼叫實體同步方法時,會自動獲取實體物件的monitor,當執行緒呼叫靜態同步方法時,會自動獲取該類Class實體物件的monitor,
Monitor 直譯為監視器,中文圈里稱為管程,它的作用是讓執行緒互斥,保護共享資料,另外也可以向其它執行緒發送滿足條件的信號,
synchronized關鍵字最主要有以下3種應用方式:
修飾實體方法 :作用于當前實體加鎖,進入同步代碼前要獲得當前實體的鎖,關鍵指令 monitorenter 和 monitorexit
修飾靜態方法: 作用于當前類物件加鎖,進入同步代碼前要獲得當前類物件的鎖,關鍵訪問標志 ACC_SYNCHRONIZED
修飾代碼塊: 指定加鎖物件,對給定物件加鎖,進入同步代碼庫前要獲得給定物件的鎖,應用場景(
某些情況下,我們撰寫的方法體可能比較大,同時存在一些比較耗時的操作,而需要同步的代碼又只有一小部分,如果直接對整個方法進行同步操作,可能會得不償失,此時我們可以使用同步代碼塊的方式對需要同步的代碼進行包裹,這樣就無需對整個方法進行同步操作了
)
synchronized 鎖
Java1.6開始synchronized 進行了很多的優化, 有適應自旋、鎖消除、鎖粗化、輕量級鎖及偏向鎖等 ,效率有了本質上的提高,在之后推出的
Java1.7 與 1.8 中,均對該關鍵字的實作機理做了優化,引入了 偏向鎖和輕量級鎖 ,都是在物件頭中有標記位,不需要經過作業系統加鎖,
Java鎖Lock介面
Lock和synchronized有一點非常大的不同,采用synchronized不需要用戶去手動釋放鎖,當synchronized方法或者代碼塊執行完之后,系統會自動讓執行緒釋放對鎖的占用;而Lock則必須要用戶去手動釋放鎖,如果沒有主動釋放鎖,就有可能導致出現死鎖現象,
Lock介面有ReentrantLock(可重入鎖)、ReentrantReadWriteLock類(讀寫鎖)
ReentrantLock
:ReentrantLock是Lock介面一種常見的實作,它是支持重進入的鎖即表示該鎖能夠支持一個執行緒對資源的重復加鎖,該鎖還支持設定獲取鎖時的公平與非公平的選擇,
public class LockThread {
//通過建構式傳入布爾引數設定公平鎖true和非公平鎖
//公平鎖內部是FairSync,非公平鎖內部是NonfairSync,
//而不管是FairSync還是NonfariSync,都間接繼承自AbstractQueuedSynchronizer這個抽象類
Lock lock = new ReentrantLock();
public void lock(String name) {
// 獲取鎖
lock.lock();
try {
System.out.println(name + " get the lock");
// 訪問此鎖保護的資源
} finally {
// 必須手動釋放鎖
lock.unlock();
System.out.println(name + " release the lock");
}
}
}
AbstractQueuedSynchronizer(AQS)
AQS(AbstractQueuedSynchronizer)抽象佇列同步器如ReentrantLock,Semaphore,ReentrantReadWriteLock,SynchronousQueue,FutureTask等等皆是基于AQS的,AQS的內部結構主要由同步等待佇列構成,
**AQS中定義了一個狀態變數state(我們查看原始碼可以發現定義為 private volatile int state ) **
,它有以下兩種使用方法:
(1)互斥鎖
當AQS只實作為互斥鎖的時候,每次只要原子更新state的值從0變為1成功了就獲取了鎖,可重入是通過不斷把state原子更新加1實作的,
(2)互斥鎖 + 共享鎖
當AQS需要同時實作為互斥鎖+共享鎖的時候,低16位存盤互斥鎖的狀態,高16位存盤共享鎖的狀態,主要用于實作讀寫鎖,
AQS佇列:AQS中維護了一個佇列,獲取鎖失敗(非tryLock())的執行緒都將進入這個佇列中排隊,等待鎖釋放后喚醒下一個排隊的執行緒(互斥鎖模式下)
Condition佇列:AQS中還有另一個非常重要的內部類ConditionObject,它實作了Condition介面,主要用于實作條件鎖,
AQS
AtomicInteger
AtomicInteger ,一個提供原子操作的 Integer
的類,常見的還有AtomicBoolean、AtomicInteger、AtomicLong、AtomicReference 等,他們的實作原理相同,
區別在于運算物件型別的不同,還可以通過 AtomicReference將一個物件的所有操作轉化成原子操作, 在多執行緒程式中,諸如++i 或 i++等運算不具有原子性,是不安全的執行緒操作之一,通常我們會使用 synchronized 將該操作變成一個原子操作,但
JVM 為此類操作特意提供了一些同步類,使得使用更方便,且使程式運行效率變得更高,通過相關資料顯示,通常AtomicInteger 的性能是
ReentantLock 的好幾倍
volatile
volatile主要用于解決可見性,它修飾變數,相當于對當前陳述句前后加上了“記憶體柵欄”,使當前代碼之前的代碼不會被重排到當前代碼之后,當前代碼之后的指令不會被重排到當前代碼之前,一定程度保證了有序性,而volatile最主要的作用是使修改volatile修飾的變數值時會使所有執行緒中的快取失效,并強制寫入公共主存,保證了各個執行緒的一致,可以看做是輕量級的Synchronized
ReentrantReadWriteLock類(讀寫鎖) - 實作ReadWriteLock介面
支持一寫多讀的同步鎖,讀寫分離,可分別分配讀鎖、寫鎖,應用場景:讀高于寫操作的環境下,可在保證執行緒安全的情況下,提高程式運行效率
public class FileThread {
private int age;
ReentrantReadWriteLock rrwl = new ReentrantReadWriteLock(); // 有兩把鎖
ReentrantReadWriteLock.ReadLock read = rrwl.readLock(); // 讀鎖 - 內部類
ReentrantReadWriteLock.WriteLock write = rrwl.writeLock(); // 寫鎖 - 內部類
// 賦值:寫操作
public void setAge(int age) throws InterruptedException {
write.lock();
try {
Thread.sleep(1000);
this.age = age;
} finally {
write.unlock();
}
}
// 取值:讀操作
public int getAge() throws InterruptedException {
read.lock();
try {
Thread.sleep(1000);
return this.age;
} finally {
read.lock();
}
}
}
volatile
volatile
尊重原創著作權: https://www.gewuweb.com/sitemap.html
尊重原創著作權: https://www.gewuweb.com/hot/6380.html
Java面試知識整理之—Java多執行緒篇
行程與執行緒
行程
是一個具有一定獨立功能的程式在一個資料集上的一次動態執行的程序,是作業系統進行資源分配和調度的一個獨立單位,是應用程式運行的載體行程是程式一次執行程序,比如程式從啟動到銷毀是一個行程,
執行緒 是比行程更小的單位,一個行程在執行程序中可以產生多個執行緒,執行緒之間可以共享“堆”和“Metadata
Space”資料,但擁有獨立的“程式計數器”、“虛擬機堆疊”和“本地方法堆疊”,所以執行緒之間切換比行程切換負擔小得多
cpu
執行緒的6種狀(NEW,RUNNABLE、BLOCKED,WAITING,TINED_WATING,TEMINATE)
執行緒狀態
為什么用多執行緒
隨著處理器的核心數不斷增多,單執行緒程式已無法發揮多核處理器的優勢,執行緒比行程切換效率更高,
使用多執行緒后引入的問題
多執行緒引入后可能引發的問題:記憶體泄露、死鎖、背景關系切換等,
多執行緒引入可能存在的問題
JAVA 執行緒實作/創建方式
1.通過繼承Thread類創建執行緒類
public class MyThread extends Thread{
@Override
public void run() {
//do something
}
}
//創建并啟動執行緒
new MyThread().start();
jdk1.8之后,可以通過Lambda運算式(函式式介面)來實作創建一個執行緒:
new Thread(()->{System.out.println("通過函式式介面創建一個執行緒!");}).start();
2.通過實作Runnable介面創建執行緒類
public class DemoThread implements Runnable{
@Override
public void run() {
//當執行緒類實作Runnable介面時,要獲取當前執行緒物件只有通過Thread.currentThread()獲取
System.out.println("當前執行緒:"+ Thread.currentThread().getName());
}
}
//創建并啟動執行緒
DemoThread c =new DemoThread();
new Thread(c,"執行緒1").start();
//通過內部匿名類實體創建一個執行緒
new Thread(new Runnable() {
@Override
public void run() {
System.out.println("通過內部匿名類實體創建一個執行緒!");
}
}).start();
Thread 類中start和run方法的區別
run方法只是thread的一個普通方法呼叫,還是在主執行緒里執行,是不會開啟多執行緒
start方法來啟動執行緒,真正實作了多執行緒運行,這時無需等待run方法體代碼執行完畢而直接繼續執行下面的代碼,通過呼叫Thread類的start()方法來啟動一個執行緒,這時此執行緒處于就緒(可運行)狀態,并沒有運行,一旦得到cpu時間片,就開始執行run()方法,這里方法run()稱為執行緒體,它包含了要執行的這個執行緒的內容,run方法運行結束,此執行緒隨即終止,
sleep 與 wait 區別
1.對于sleep()方法是屬于Thread 類中的,而 wait()方法則是屬于Object 類中的
2.sleep()方法導致了程式暫停執行指定的時間,讓出 cpu給其他執行緒,但是他的監控狀態依然保持著,當指定的時間到了又會自動恢復運行狀態,
3.在呼叫 sleep()方法的程序中,執行緒不會釋放物件鎖,
4.而當呼叫 wait()方法的時候,執行緒會放棄物件鎖,進入等待此物件的等待鎖定池,只有針對此物件呼叫
notify()方法后本執行緒才進入物件鎖定池準備獲取物件鎖進入運行狀態,
3.通過Callable和Future介面創建執行緒
public class CallThread implements Callable<String> {
@Override
public String call() throws Exception {
Thread.sleep(500);
return "callable and future";
}
}
CallThread callThread = new CallThread();
//使用執行緒池
ExecutorService executor = Executors.newCachedThreadPool();
Future<String> submit = executor.submit(callThread);
try {
String s = submit.get();
System.out.println("獲取結果:" + s);
} catch (InterruptedException e) {
e.printStackTrace();
} catch (ExecutionException e) {
e.printStackTrace();
}
//或者通過Thread啟動
FutureTask<String> result =new FutureTask<>(callThread);
new Thread(result).start();
//2.接收執行緒運算后的結果
try {
String s = submit.get();
System.out.println("獲取結果:" + s);
}catch (InterruptedException | ExecutionException e) {
e.printStackTrace();
}
使用Callable和Future創建執行緒的基本步驟
1、創建Callable介面實作類,并實作call()方法,該方法將作為執行緒執行體,且該方法有回傳值,再創建Callable實作類的實體;
2、使用FutureTask類來包裝Callable物件,該FutureTask物件封裝了該Callable物件的call()方法的回傳值;
3、使用FutureTask物件作為Thread物件的target創建并啟動新執行緒;
4、呼叫FutureTask物件的get()方法來獲得子執行緒執行結束后的回傳值,
4.基于執行緒池的方式
ExecutorService threadPool = Executors.newFixedThreadPool(10);
while(true) {
threadPool.execute(new Runnable() {
@Override
public void run() {
System.out.println(Thread.currentThread().getName() + " is running ..");
try {
Thread.sleep(3000);
} catch (InterruptedException e) { e.printStackTrace();
}
}
});
}
JAVA執行緒池
Java 里面執行緒池的頂級介面是 Executor,但是嚴格意義上講 Executor 并不是一個執行緒池,而只是一個執行執行緒的工具,真正的執行緒池介面是
ExecutorService,
Java通過Executors提供四種執行緒池,分別為:
1.newCachedThreadPool:創建一個可快取執行緒池,如果執行緒池長度超過處理需要,可靈活回收空閑執行緒,若無可回收,則新建執行緒,
2:newFixedThreadPool :創建一個定長執行緒池,可控制執行緒最大并發數,超出的執行緒會在佇列中等待,
3.newScheduledThreadPool :創建一個周期執行緒池,支持定時及周期性任務執行,
4.newSingleThreadExecutor: 創建一個單執行緒化的執行緒池,它只會用唯一的作業執行緒來執行任務,保證所有任務按照指定順序(FIFO,
LIFO, 優先級)執行,
java執行緒池
Java鎖的種類
Java鎖的種類可以分為以下幾大類:樂觀鎖/悲觀鎖、獨享鎖/共享鎖、互斥鎖/讀寫鎖、可重入鎖、公平鎖/非公平鎖、分段鎖、偏向鎖/輕量級鎖/重量級鎖、自旋鎖,
java 鎖
Synchronized 同步鎖
synchronized可以保證在同一個時刻,只有一個執行緒可以執行某個方法或者某個代碼塊,synchronized可保證一個執行緒的變化(主要是共享資料的變化)被其他執行緒所看到保證可見性,使用關鍵字synchronized實作同步是在JVM內部實作處理,對于應用開發人員來說它是隱式進行的,每個Java物件都有一個與之關聯的monitor,當執行緒呼叫實體同步方法時,會自動獲取實體物件的monitor,當執行緒呼叫靜態同步方法時,會自動獲取該類Class實體物件的monitor,
Monitor 直譯為監視器,中文圈里稱為管程,它的作用是讓執行緒互斥,保護共享資料,另外也可以向其它執行緒發送滿足條件的信號,
synchronized關鍵字最主要有以下3種應用方式:
修飾實體方法 :作用于當前實體加鎖,進入同步代碼前要獲得當前實體的鎖,關鍵指令 monitorenter 和 monitorexit
修飾靜態方法: 作用于當前類物件加鎖,進入同步代碼前要獲得當前類物件的鎖,關鍵訪問標志 ACC_SYNCHRONIZED
修飾代碼塊: 指定加鎖物件,對給定物件加鎖,進入同步代碼庫前要獲得給定物件的鎖,應用場景(
某些情況下,我們撰寫的方法體可能比較大,同時存在一些比較耗時的操作,而需要同步的代碼又只有一小部分,如果直接對整個方法進行同步操作,可能會得不償失,此時我們可以使用同步代碼塊的方式對需要同步的代碼進行包裹,這樣就無需對整個方法進行同步操作了
)
synchronized 鎖
Java1.6開始synchronized 進行了很多的優化, 有適應自旋、鎖消除、鎖粗化、輕量級鎖及偏向鎖等 ,效率有了本質上的提高,在之后推出的
Java1.7 與 1.8 中,均對該關鍵字的實作機理做了優化,引入了 偏向鎖和輕量級鎖 ,都是在物件頭中有標記位,不需要經過作業系統加鎖,
Java鎖Lock介面
Lock和synchronized有一點非常大的不同,采用synchronized不需要用戶去手動釋放鎖,當synchronized方法或者代碼塊執行完之后,系統會自動讓執行緒釋放對鎖的占用;而Lock則必須要用戶去手動釋放鎖,如果沒有主動釋放鎖,就有可能導致出現死鎖現象,
Lock介面有ReentrantLock(可重入鎖)、ReentrantReadWriteLock類(讀寫鎖)
ReentrantLock
:ReentrantLock是Lock介面一種常見的實作,它是支持重進入的鎖即表示該鎖能夠支持一個執行緒對資源的重復加鎖,該鎖還支持設定獲取鎖時的公平與非公平的選擇,
public class LockThread {
//通過建構式傳入布爾引數設定公平鎖true和非公平鎖
//公平鎖內部是FairSync,非公平鎖內部是NonfairSync,
//而不管是FairSync還是NonfariSync,都間接繼承自AbstractQueuedSynchronizer這個抽象類
Lock lock = new ReentrantLock();
public void lock(String name) {
// 獲取鎖
lock.lock();
try {
System.out.println(name + " get the lock");
// 訪問此鎖保護的資源
} finally {
// 必須手動釋放鎖
lock.unlock();
System.out.println(name + " release the lock");
}
}
}
AbstractQueuedSynchronizer(AQS)
AQS(AbstractQueuedSynchronizer)抽象佇列同步器如ReentrantLock,Semaphore,ReentrantReadWriteLock,SynchronousQueue,FutureTask等等皆是基于AQS的,AQS的內部結構主要由同步等待佇列構成,
**AQS中定義了一個狀態變數state(我們查看原始碼可以發現定義為 private volatile int state ) **
,它有以下兩種使用方法:
(1)互斥鎖
當AQS只實作為互斥鎖的時候,每次只要原子更新state的值從0變為1成功了就獲取了鎖,可重入是通過不斷把state原子更新加1實作的,
(2)互斥鎖 + 共享鎖
當AQS需要同時實作為互斥鎖+共享鎖的時候,低16位存盤互斥鎖的狀態,高16位存盤共享鎖的狀態,主要用于實作讀寫鎖,
AQS佇列:AQS中維護了一個佇列,獲取鎖失敗(非tryLock())的執行緒都將進入這個佇列中排隊,等待鎖釋放后喚醒下一個排隊的執行緒(互斥鎖模式下)
Condition佇列:AQS中還有另一個非常重要的內部類ConditionObject,它實作了Condition介面,主要用于實作條件鎖,
AQS
AtomicInteger
AtomicInteger ,一個提供原子操作的 Integer
的類,常見的還有AtomicBoolean、AtomicInteger、AtomicLong、AtomicReference 等,他們的實作原理相同,
區別在于運算物件型別的不同,還可以通過 AtomicReference將一個物件的所有操作轉化成原子操作, 在多執行緒程式中,諸如++i 或 i++等運算不具有原子性,是不安全的執行緒操作之一,通常我們會使用 synchronized 將該操作變成一個原子操作,但
JVM 為此類操作特意提供了一些同步類,使得使用更方便,且使程式運行效率變得更高,通過相關資料顯示,通常AtomicInteger 的性能是
ReentantLock 的好幾倍
volatile
volatile主要用于解決可見性,它修飾變數,相當于對當前陳述句前后加上了“記憶體柵欄”,使當前代碼之前的代碼不會被重排到當前代碼之后,當前代碼之后的指令不會被重排到當前代碼之前,一定程度保證了有序性,而volatile最主要的作用是使修改volatile修飾的變數值時會使所有執行緒中的快取失效,并強制寫入公共主存,保證了各個執行緒的一致,可以看做是輕量級的Synchronized
ReentrantReadWriteLock類(讀寫鎖) - 實作ReadWriteLock介面
支持一寫多讀的同步鎖,讀寫分離,可分別分配讀鎖、寫鎖,應用場景:讀高于寫操作的環境下,可在保證執行緒安全的情況下,提高程式運行效率
public class FileThread {
private int age;
ReentrantReadWriteLock rrwl = new ReentrantReadWriteLock(); // 有兩把鎖
ReentrantReadWriteLock.ReadLock read = rrwl.readLock(); // 讀鎖 - 內部類
ReentrantReadWriteLock.WriteLock write = rrwl.writeLock(); // 寫鎖 - 內部類
// 賦值:寫操作
public void setAge(int age) throws InterruptedException {
write.lock();
try {
Thread.sleep(1000);
this.age = age;
} finally {
write.unlock();
}
}
// 取值:讀操作
public int getAge() throws InterruptedException {
read.lock();
try {
Thread.sleep(1000);
return this.age;
} finally {
read.lock();
}
}
}
volatile
volatile
轉載請註明出處,本文鏈接:https://www.uj5u.com/qita/467019.html
標籤:其他
