一、創建多執行緒的四種方式
1.方式一:繼承Thread類的方式
-
創建一個繼承于Thread類的子類
-
重寫Thread類的run() --> 將此執行緒執行的操作宣告在run()中
-
創建Thread類的子類的物件
-
通過此物件呼叫start():①啟動當前執行緒 ② 呼叫當前執行緒的run()
說明兩個問題:
問題一:我們啟動一個執行緒,必須呼叫start(),不能呼叫run()的方式啟動執行緒,
問題二:如果再啟動一個執行緒,必須重新創建一個Thread子類的物件,呼叫此物件的start(),
2.方式二:實作Runnable介面的方式
-
創建一個實作了Runnable介面的類
-
實作類去實作Runnable中的抽象方法:run()
-
創建實作類的物件
-
將此物件作為引數傳遞到Thread類的構造器中,創建Thread類的物件
-
通過Thread類的物件呼叫start()
兩種方式的對比:
開發中:優先選擇:實作Runnable介面的方式
原因:
-
實作的方式沒類的單繼承性的局限性
-
實作的方式更適合來處理多個執行緒共享資料的情況,
//聯系:
public class Thread implements Runnable{}
相同點:兩種方式都需要重寫run(),將執行緒要執行的邏輯宣告在run()中,
目前兩種方式,要想啟動執行緒,都是呼叫的Thread類中的start(),
3.方式三:實作Callable介面
這是JDK 5.0新增的方式!!!
//1.創建一個實作Callable的實作類
class NumThread implements Callable{
//2.實作call方法,將此執行緒需要執行的操作宣告在call()中
@Override
public Object call() throws Exception {
int sum = 0;
for (int i = 1; i <= 100; i++) {
if(i % 2 == 0){
System.out.println(i);
sum += i;
}
}
return sum;
}
}
public class ThreadNew {
public static void main(String[] args) {
//3.創建Callable介面實作類的物件
NumThread numThread = new NumThread();
//4.將此Callable介面實作類的物件作為傳遞到FutureTask構造器中,創建FutureTask的物件
FutureTask futureTask = new FutureTask(numThread);
//5.將FutureTask的物件作為引數傳遞到Thread類的構造器中,創建Thread物件,并呼叫start()
new Thread(futureTask).start();
try {
//6.獲取Callable中call方法的回傳值
//get()回傳值即為FutureTask構造器引數Callable實作類重寫的call()的回傳值,
Object sum = futureTask.get();
System.out.println("總和為:" + sum);
} catch (InterruptedException e) {
e.printStackTrace();
} catch (ExecutionException e) {
e.printStackTrace();
}
}
}
說明:
如何理解實作Callable介面的方式創建多執行緒比實作Runnable介面創建多執行緒方式強大?
-
call()可以回傳值的,
-
call()可以拋出例外,被外面的操作捕獲,獲取例外的資訊
-
Callable是支持泛型的
4.方式四:使用執行緒池
這是JDK 5.0新增的方式!!!
class NumberThread implements Runnable{
@Override
public void run() {
for(int i = 0;i <= 100;i++){
if(i % 2 == 0){
System.out.println(Thread.currentThread().getName() + ": " + i);
}
}
}
}
class NumberThread1 implements Runnable{
@Override
public void run() {
for(int i = 0;i <= 100;i++){
if(i % 2 != 0){
System.out.println(Thread.currentThread().getName() + ": " + i);
}
}
}
}
public class ThreadPool {
public static void main(String[] args) {
//1. 提供指定執行緒數量的執行緒池
ExecutorService service = Executors.newFixedThreadPool(10);
ThreadPoolExecutor service1 = (ThreadPoolExecutor) service;
//設定執行緒池的屬性
//System.out.println(service.getClass());
//service1.setCorePoolSize(15);
//service1.setKeepAliveTime();
//2.執行指定的執行緒的操作,需要提供實作Runnable介面或Callable介面實作類的物件
service.execute(new NumberThread());//適合適用于Runnable
service.execute(new NumberThread1());//適合適用于Runnable
//service.submit(Callable callable);//適合使用于Callable
//3.關閉連接池
service.shutdown();
}
}
好處:
-
提高回應速度(減少了創建新執行緒的時間)
-
降低資源消耗(重復利用執行緒池中執行緒,不需要每次都創建)
-
便于執行緒管理
-
corePoolSize:核心池的大小
-
maximumPoolSize:最大執行緒數
-
keepAliveTime:執行緒沒任務時最多保持多長時間后會終止
面試題:Java中多執行緒的創建有幾種方式?答:四種,
二、Thread類中的常用方法
1.常用方法
| 方法 | 詳細說明 |
|---|---|
| 1.start(): | 啟動當前執行緒;呼叫當前執行緒的run() |
| 2.run(): | 通常需要重寫Thread類中的此方法,將創建的執行緒要執行的操作宣告在此方法中 |
| 3.currentThread(): | 靜態方法,回傳執行當前代碼的執行緒 |
| 4.getName(): | 獲取當前執行緒的名字 |
| 5.setName(): | 設定當前執行緒的名字 |
| 6.yield(): | 釋放當前cpu的執行權 |
| 7.join(): | 在執行緒a中呼叫執行緒b的join(),此時執行緒a就進入阻塞狀態,直到執行緒b完全執行完以后,執行緒a才結束阻塞狀態, |
| 8.stop(): | 已過時,當執行此方法時,強制結束當前執行緒, |
| 9.sleep(long millitime): | 讓當前執行緒“睡眠”指定的millitime毫秒,在指定的millitime毫秒時間內,當前執行緒是阻塞狀態, |
| 10.isAlive(): | 判斷當前執行緒是否存活 |
執行緒的優先級:
-
MAX_PRIORITY:10
-
MIN _PRIORITY:1
-
NORM_PRIORITY:5 -->默認優先級
如何獲取和設定當前執行緒的優先級?
-
getPriority():獲取執行緒的優先級
-
setPriority(int p):設定執行緒的優先級
說明:高優先級的執行緒要搶占低優先級執行緒cpu的執行權,但是只是從概率上講,高優先級的執行緒高概率的情況下被執行,并不意味著只當高優先級的執行緒執行完以后,低優先級的執行緒才執行,
執行緒通信:wait() / notify() / notifyAll() :此三個方法定義在Object類中的,
執行緒的分類:一種是守護執行緒,一種是用戶執行緒,
三、Thread的生命周期
圖示:

說明:
-
生命周期關注兩個概念:狀態、相應的方法
-
關注:狀態a-->狀態b:哪些方法執行了(回呼方法),某個方法主動呼叫:狀態a-->狀態b
-
阻塞:臨時狀態,不可以作為最終狀態,
-
死亡:最終狀態,
四、并行與并發
1.單核CPU與多核CPU的理解
單核CPU,其實是一種假的多執行緒,因為在一個時間單元內,也只能執行一個執行緒的任務,
例如:雖然有多車道,但是收費站只有一個作業人員在收費,只有收了費才能通過,那么CPU就好比收費人員,如果某個人不想交錢,那么收費人員可以把他“掛起”(晾著他,等他想通了,準備好了錢,再去收費,)
但是因為CPU時間單元特別短,因此感覺不出來,
如果是多核的話,才能更好的發揮多執行緒的效率,(現在的服務器都是多核的)
一個Java應用程式java.exe,其實至少三個執行緒:main()主執行緒,gc()垃圾回收執行緒,例外處理執行緒,當然如果發生例外,會影響主執行緒,
2.并行與并發的理解
并行:多個CPU同時執行多個任務,比如:多個人同時做不同的事,
并發:一個CPU(采用時間片)同時執行多個任務,比如:秒殺、多個人做同一件事,
五、執行緒的同步機制
1.背景
例子:創建個視窗賣票,總票數為100張.使用實作Runnable介面的方式,
問題:賣票程序中,出現了重票、錯票 -->出現了執行緒的安全問題,
問題出現的原因:當某個執行緒操作車票的程序中,尚未操作完成時,其他執行緒參與進來,也操作車票,
如何解決:當一個執行緒a在操作ticket的時候,其他執行緒不能參與進來,直到執行緒a操作完ticket時,其他執行緒才可以開始操作ticket,這種情況即使執行緒a出現了阻塞,也不能被改變,
2.Java解決方案:同步機制
在Java中,我們通過同步機制,來解決執行緒的安全問題,
1.方式一:同步代碼塊
synchronized(同步監視器){
//需要被同步的代碼
}
說明:
-
操作共享資料的代碼,即為需要被同步的代碼, -->不能包含代碼多了,也不能包含代碼少了,
-
共享資料:多個執行緒共同操作的變數,比如:ticket就是共享資料,
-
同步監視器,俗稱:鎖,任何一個類的物件,都可以充當鎖,
要求:多個執行緒必須要共用同一把鎖,
在實作Runnable介面創建多執行緒的方式中,我們可以考慮使用this充當同步監視器,
在繼承Thread類創建多執行緒的方式中,慎用this充當同步監視器,考慮使用當前類充當同步監視器,
2.方式二:同步方法
如果操作共享資料的代碼完整的宣告在一個方法中,我們不妨將此方法宣告同步的,
關于同步方法的總結:
-
同步方法仍然涉及到同步監視器,只是不需要我們顯式的宣告,
-
非靜態的同步方法,同步監視器是:this
-
靜態的同步方法,同步監視器是:當前類本身
3.方式三:Lock鎖 --- JDK5.0新增
1. 面試題:synchronized 與 Lock的異同?
相同:二者都可以解決執行緒安全問題
不同:synchronized機制在執行完相應的同步代碼以后,自動的釋放同步監視器
Lock需要手動的啟動同步(lock(),同時結束同步也需要手動的實作(unlock())
2.使用的優先順序
Lock ---> 同步代碼塊(已經進入了方法體,分配了相應資源 ) --->? 同步方法(在方法體之外)
3.利弊
同步的方式,解決了執行緒的安全問題,---好處
操作同步代碼時,只能一個執行緒參與,其他執行緒等待,相當于是一個單執行緒的程序,效率低,
面試題:Java是如何解決執行緒安全問題的,有幾種方式?并對比幾種方式的不同
面試題:synchronized和Lock方式解決執行緒安全問題的對比,
六、死鎖問題
1.死鎖的理解
不同的執行緒分別占用對方需要的同步資源不放棄,都在等待對方放棄自己需要的同步資源,就形成了執行緒的死鎖,
出現死鎖后,不會出現例外,不會出現提示,只是有所的執行緒都處于阻塞狀態,無法繼續,
我們使用同步時,要避免出現死鎖,
2.舉例
public static void main(String[] args) {
StringBuffer s1 = new StringBuffer();
StringBuffer s2 = new StringBuffer();
new Thread(){
@Override
public void run() {
synchronized (s1){
s1.append("a");
s2.append("1");
try {
Thread.sleep(100);
} catch (InterruptedException e) {
e.printStackTrace();
}
synchronized (s2){
s1.append("b");
s2.append("2");
System.out.println(s1);
System.out.println(s2);
}
}
}
}.start();
new Thread(new Runnable() {
@Override
public void run() {
synchronized (s2){
s1.append("c");
s2.append("3");
try {
Thread.sleep(100);
} catch (InterruptedException e) {
e.printStackTrace();
}
synchronized (s1){
s1.append("d");
s2.append("4");
System.out.println(s1);
System.out.println(s2);
}
}
}
}).start();
}
3.執行緒安全的單例模式(懶漢式)
使用同步機制將單例模式中的懶漢式改寫為執行緒安全的,
class Bank{
private Bank(){}
private static Bank instance = null;
public static Bank getInstance(){
//方式一:效率稍差
// synchronized (Bank.class) {
// if(instance == null){
//
// instance = new Bank();
// }
// return instance;
// }
//方式二:效率更高
if(instance == null){
synchronized (Bank.class) {
if(instance == null){
instance = new Bank();
}
}
}
return instance;
}
}
面試題:寫一個執行緒安全的單例模式,
餓漢式,
懶漢式:上面提供的,
七、執行緒通信
1.執行緒通信涉及到的三個方法:
wait():一旦執行此方法,當前執行緒就進入阻塞狀態,并釋放同步監視器,
notify():一旦執行此方法,就會喚醒被wait的一個執行緒,如果有多個執行緒被wait,就喚醒優先級高的那個,
notifyAll():一旦執行此方法,就會喚醒所有被wait的執行緒,
2.說明
1.wait(),notify(),notifyAll()三個方法必須使用在同步代碼塊或同步方法中,
2.wait(),notify(),notifyAll()三個方法的呼叫者必須是同步代碼塊或同步方法中的同步監視器,否則,會出現IllegalMonitorStateException例外,
3.wait(),notify(),notifyAll()三個方法是定義在java.lang.Object類中,
3.面試題
面試題:sleep() 和 wait()的異同?
1.相同點:一旦執行方法,都可以使得當前的執行緒進入阻塞狀態,
2.不同點:兩個方法宣告的位置不同:Thread類中宣告sleep() , Object類中宣告wait(),
①呼叫的要求不同:sleep()可以在任何需要的場景下呼叫, wait()必須使用在同步代碼塊或同步方法中
②關于是否釋放同步監視器:如果兩個方法都使用在同步代碼塊或同步方法中,sleep()不會釋放鎖,wait()會釋放鎖,
4.小結釋放鎖的操作
- 當前執行緒的同步方法、同步代碼塊執行結束,
- 當前執行緒在同步代碼塊、同步方法中遇到break、return終止了該代碼塊、 該方法的繼續執行,
- 當前執行緒在同步代碼塊、同步方法中出現了未處理的Error或Exception,導致例外結束,
- 當前執行緒在同步代碼塊、同步方法中執行了執行緒物件的wait()方法,當前執行緒暫停,并釋放鎖,
5.小結不會釋放鎖的操作
- 執行緒執行同步代碼塊或同步方法時,程式呼叫Thread.sleep()、 Thread.yield()方法暫停當前執行緒的執行,
- 執行緒執行同步代碼塊時,其他執行緒呼叫了該執行緒的suspend()方法將該執行緒掛起,該執行緒不會釋放鎖(同步監視器), 應盡量避免使用suspend()和resume()來控制執行緒
八、程式、行程、執行緒的理解
1.程式(program)
概念:是為完成特定任務、用某種語言撰寫的一組指令的集合,即指一段靜態的代碼,
2.行程(process)
概念:程式的一次執行程序,或是正在運行的一個程式,
說明:行程作為資源分配的單位,系統在運行時會為每個行程分配不同的記憶體區域
3.執行緒(thread)
概念:行程可進一步細化為執行緒,是一個程式內部的一條執行路徑,
說明:執行緒作為調度和執行的單位,每個執行緒擁獨立的運行堆疊和程式計數器(pc),執行緒切換的開銷小,

記憶體結構:

行程可以細化為多個執行緒,
每個執行緒,擁有自己獨
立的:堆疊、程式計數器
多個執行緒,共享同一個行程中的結構:方法區、堆,
轉載請註明出處,本文鏈接:https://www.uj5u.com/houduan/433156.html
標籤:Java
上一篇:ApplicationStartedEvent與ContextStartedEvent有區別嗎?
下一篇:探秘intern()方法
