目錄
一、執行緒與行程
二、執行緒調度
三、同步與異步
四、并發與并行
五、Java執行緒的使用
1、繼承Thread
2、實作Runnable
3、通過匿名內部類實作
六、Thread常用方法
1、設定和獲取執行緒名稱
2、執行緒休眠sleep
3、執行緒阻塞
4、執行緒中斷
七、執行緒的六種不同狀態
八、守護執行緒
九、執行緒安全
1、為什么會出現執行緒安全問題?
2、 解決方案一:同步代碼塊sychronized
3、 解決方案二:同步方法
4、解決方案三:顯式鎖
5、Java公平鎖和非公平鎖
6、Java執行緒死鎖
十、Java多執行緒通信問題
十一、帶回傳值的執行緒Callable
1、Runnable 與 Callable
2、Callable使用步驟
3、Runnable 與 Callable的相同點
4、Runnable 與 Callable的不同點
5、Callable獲取回傳值
6、FutureTask類
十二、Java執行緒池
1、執行緒池 Executors
2、執行緒池的好處
3、Java中的四種執行緒池 . ExecutorService
十三、Lambda運算式
一、執行緒與行程
行程
- 是指一個記憶體中運行的應用程式,每個行程都有一個獨立的記憶體空間 ,
執行緒
- 是行程中的一個執行路徑,共享一個記憶體空間,執行緒之間可以自由切換,并發執行,一個行程最少有一個執行緒,
- 執行緒實際上是在行程基礎之上的進一步劃分,一個行程啟動之后,里面的若干執行路徑又可以劃分成若干個執行緒,
二、執行緒調度
分時調度
- 所有執行緒輪流使用 CPU 的使用權,平均分配每個執行緒占用 CPU 的時間,
搶占式調度
- 優先讓優先級高的執行緒使用 CPU,如果執行緒的優先級相同,那么會隨機選擇一個(執行緒隨機性),Java使用的為搶占式調度,
- CPU使用搶占式調度模式在多個執行緒間進行著高速的切換,對于CPU的一個核新而言,某個時刻, 只能執行一個執行緒,而 CPU的在多個執行緒間切換速度相對我們的感覺要快,看上去就是在同一時刻運行, 其實,多執行緒程式并不能提高程式的運行速度,但能夠提高程式運行效率,讓CPU的使用率更高,
三、同步與異步
同步:排隊執行 , 效率低但是安全,
異步:同時執行 , 效率高但是資料不安全,
四、并發與并行
并發:指兩個或多個事件在同一個時間段內發生,
并行:指兩個或多個事件在同一時刻發生(同時發生),
五、Java執行緒的使用
1、繼承Thread


運行結果:
- 第一次:

- 第二次:

程式執行緒執行的時序圖:

2、實作Runnable


運行結果:

實作 Runnable與繼承Thread比較

3、通過匿名內部類實作

運行結果:

六、Thread常用方法
1、設定和獲取執行緒名稱

2、執行緒休眠sleep
每隔一秒輸出一次:

3、執行緒阻塞
https://blog.csdn.net/sunshine_2211468152/article/details/87299708
https://baike.baidu.com/item/%E7%BA%BF%E7%A8%8B%E9%98%BB%E5%A1%9E/2233470?fr=aladdin
4、執行緒中斷




修改run方法,發現中斷標記后自殺:(可以先釋放占用的資源后自殺)


七、執行緒的六種不同狀態


詳見:https://blog.csdn.net/pange1991/article/details/53860651
八、守護執行緒
執行緒:分為守護執行緒和用戶執行緒
- 用戶執行緒:當一個行程不包含任何的存活的用戶執行緒時,行程結束,
- 守護執行緒:守護用戶執行緒的,當最后一個用戶執行緒結束時,所有守護執行緒自動死亡,
代碼示例:


代碼改造:

運行結果:

九、執行緒安全
1、為什么會出現執行緒安全問題?
實體:



運行結果出現了余票為負的情況
分析:
| 執行緒1 | 執行緒2 | 執行緒3 | count |
| while(count>0) | 就緒 | 1 | |
| system.out.println("正在準備賣票") | 就緒 | 1 | |
| Thread.sleep(1000) | while(count>0) | 1 | |
| system.out.println("正在準備賣票") | 1 | ||
| Thread.sleep(1000) | 1 | ||
| while(count>0) | 1 | ||
| system.out.println("正在準備賣票") | 1 | ||
| Thread.sleep(1000) | 1 | ||
| count-- | 0 | ||
| system.out.println("出票成功,余額:"+count) | 0 | ||
| while(count>0) | 0 | ||
| count-- | -1 | ||
| system.out.println("出票成功,余額:"+count) | -1 | ||
| count-- | -2 | ||
| system.out.println("出票成功,余額:"+count) | -2 |
2、 解決方案一:同步代碼塊sychronized
要看同一把鎖!


把該任務分配給三個子執行緒,爭奪同一個Object物件o的鎖:排隊執行

3、 解決方案二:同步方法
1、同步方法的鎖是?
非靜態方法:this
靜態方法:類名.class
2、一個類里面有多個同步方法?
只要一個同步方法運行,其他同步方法就不能運行


4、解決方案三:顯式鎖
執行緒分配到同一個Ticket任務,爭奪同一把鎖l


5、Java公平鎖和非公平鎖
鎖Lock分為公平鎖和非公平鎖,
- 公平鎖:表示執行緒獲取鎖的順序是按照加鎖的順序來分配的,及先來先得,先進先出的順序,
- 非公平鎖:表示獲取鎖的搶占機制,是隨機獲取鎖的,和公平鎖不一樣的就是先來的不一定能拿到鎖,有可能一直拿不到鎖,所以結果不公平,
公平鎖,就是很公平,在并發環境中,每個執行緒在獲取鎖時會先查看此鎖維護的等待佇列,如果為空,或者當前執行緒是等待佇列的第一個,就占有鎖, 否則就會加入到等待佇列中,以后會按照FIFO的規則從佇列中取到自己,
非公平鎖比較粗魯,上來就直接嘗試占有鎖,如果嘗試失敗,就再采用類似公平鎖那種方式

以下內容轉自鏈接:https://www.imooc.com/article/284585
(1)什么是非公平鎖?
如上圖,現在執行緒1加了鎖,然后執行緒2嘗試加鎖,失敗后進入了等待佇列,處于阻塞中,然后執行緒1釋放了鎖,準備來喚醒執行緒2重新嘗試加鎖,
注意一點,此時執行緒2可還停留在等待佇列里啊,還沒開始嘗試重新加鎖呢!然而,不幸的事情發生了,這時半路殺出個程咬金,來了一個執行緒3!執行緒3突然嘗試對ReentrantLock發起加鎖操作,此時會發生什么事情?
很簡單!執行緒2還沒來得及重新嘗試加鎖呢,也就是說,還沒來得及嘗試重新執行CAS操作將state的值從0變為1呢!執行緒3沖上來直接一個CAS操作,嘗試將state的值從0變為1,結果還成功了!一旦CAS操作成功,執行緒3就會將“加鎖執行緒”這個變數設定為他自己,給大家來一張圖,看看這整個程序:

明明人家執行緒2規規矩矩的排隊領鎖呢,結果你執行緒3不守規矩,執行緒1剛釋放鎖,不分青紅皂白,直接就跑過來搶先加鎖了,這就導致執行緒2被喚醒過后,重新嘗試加鎖執行CAS操作,結果毫無疑問,失敗!
原因很簡單啊!因為加鎖CAS操作,是要嘗試將state從0變為1,結果此時state已經是1了,所以CAS操作一定會失敗!一旦加鎖失敗,就會導致執行緒2繼續留在等待佇列里不斷的等著,等著執行緒3釋放鎖之后,再來喚醒自己,真是可憐!先來的執行緒2居然加不到鎖!
同樣給大家來一張圖,體會一下執行緒2這無助的程序:

上述的鎖策略,就是所謂的非公平鎖!
如果你用默認的建構式來創建ReentrantLock物件,默認的鎖策略就是非公平的,在非公平鎖策略之下,不一定說先來排隊的執行緒就就先會得到機會加鎖,而是出現各種執行緒隨意搶占的情況,那如果要實作公平鎖的策略該怎么辦呢?也很簡單,在構造ReentrantLock物件的時候傳入一個true即可:
ReentrantLock lock = new ReentrantLock(true)
此時就是說讓他使用公平鎖的策略,那么公平鎖具體是什么意思呢?
(2)什么是公平鎖?
咱們重新回到第一張圖,就是執行緒1剛剛釋放鎖之后,執行緒2還沒來得及重新加鎖的那個狀態,
同樣,這時假設來了一個執行緒3,突然殺出來,想要加鎖,
如果是公平鎖的策略,那么此時執行緒3不會跟個愣頭青一樣盲目的直接加鎖,他會先判斷一下:咦?AQS的等待佇列里,有沒有人在排隊啊?如果有人在排隊的話,說明我前面有兄弟正想要加鎖啊!如果AQS的佇列里真的有執行緒排著隊,那我執行緒3就不能跟個二愣子一樣直接搶占加鎖了,因為現在咱們是公平策略,得按照先來后到的順序依次排隊,誰先入隊,誰就先從佇列里出來加鎖!所以,執行緒3此時一判斷,發現佇列里有人排隊,自己就會乖乖的排到佇列后面去,而不會貿然加鎖!
同樣,整個程序我們用下面這張圖給大家直觀的展示一下:

上面的等待佇列中,執行緒3會按照公平原則直接進入佇列尾部進行排隊,接著,執行緒2不是被喚醒了么?他就會重新嘗試進行CAS加鎖,此時沒人跟他搶,他當然可以加鎖成功了,然后呢,執行緒2就會將state值變為1,同時設定“加鎖執行緒”是自己,最后,執行緒2自己從等待佇列里出隊,
整個程序,參見下圖:

這個就是公平鎖的策略,過來加鎖的執行緒全部是按照先來后到的順序,依次進入等待佇列中排隊的,不會盲目的胡亂搶占加鎖,非常的公平,
6、Java執行緒死鎖
(1)死鎖的定義
多執行緒以及多行程改善了系統資源的利用率并提高了系統 的處理能力,然而,并發執行也帶來了新的問題——死鎖,所謂死鎖是指多個執行緒因競爭資源而造成的一種僵局(互相等待),若無外力作用,這些行程都將無法向前推進,
所謂死鎖是指兩個或兩個以上的執行緒在執行程序中,因爭奪資源而造成的一種互相等待的現象,若無外力作用,它們都將無法推進下去,
(2)死鎖產生的原因
1、系統資源的競爭
通常系統中擁有的不可剝奪資源,其數量不足以滿足多個行程運行的需要,使得行程在運行程序中,會因爭奪資源而陷入僵局,如磁帶機、列印機等,只有對不可剝奪資源的競爭才可能產生死鎖,對可剝奪資源的競爭是不會引起死鎖的,
2、行程推進順序非法
行程在運行程序中,請求和釋放資源的順序不當,也同樣會導致死鎖,例如,并發行程 P1、P2分別保持了資源R1、R2,而行程P1申請資源R2,行程P2申請資源R1時,兩者都會因為所需資源被占用而阻塞,
從上面兩個例子中,我們可以得出結論,產生死鎖可能性的最根本原因是:執行緒在獲得一個鎖L1的情況下再去申請另外一個鎖L2,也就是鎖L1想要包含了鎖L2,也就是說在獲得了鎖L1,并且沒有釋放鎖L1的情況下,又去申請獲得鎖L2,這個是產生死鎖的最根本原因,另一個原因是默認的鎖申請操作是阻塞的,

3、死鎖產生的必要條件:
產生死鎖必須同時滿足以下四個條件,只要其中任一條件不成立,死鎖就不會發生,
(1)互斥:行程要求對所分配的資源(如列印機)進行排他性控制,即在一段時間內某資源僅為一個行程所占有,此時若有其他行程請求該資源,則請求行程只能等待,
(2)不剝奪:行程所獲得的資源在未使用完畢之前,不能被其他行程強行奪走,即只能由獲得該資源的行程自己來釋放(只能是主動釋放),
(3)請求和保持:行程已經保持了至少一個資源,但又提出了新的資源請求,而該資源已被其他行程占有,此時請求行程被阻塞,但對自己已獲得的資源保持不放,
(4)回圈等待:存在一種行程資源的回圈等待鏈,鏈中每一個行程已獲得的資源同時被鏈中下一個行程所請求,即存在一個處于等待狀態的行程集合{Pl, P2, ..., pn},其中Pi等 待的資源被P(i+1)占有(i=0, 1, ..., n-1),Pn等待的資源被P0占有,如圖1所示,
(3)產生死鎖的實體
看下面一段代碼:
public class DeadLock {
public static void main(String[] args) {
Police p = new Police();
CulPrit c = new CulPrit();
new MyThread(c,p).start();
c.say(p);
}
static class MyThread extends Thread {
private CulPrit c;
private Police p;
public MyThread(CulPrit c,Police p) {
this.c = c;
this.p = p;
}
@Override
public void run() {
p.say(c);
}
}
static class Police {
public synchronized void say(CulPrit c) {
System.out.println("警察:你放走人質,我放走你");
c.fun();
}
public synchronized void fun() {
System.out.println("警察:罪犯放走了人質,警察也放走了罪犯");
}
}
static class CulPrit{
public synchronized void say(Police p){
System.out.println("罪犯:你放走我,我放走人質");
p.fun();
}
public synchronized void fun(){
System.out.println("罪犯:警察放走了罪犯,罪犯也放走了人質");
}
}
}
運行結果:警察和罪犯互相等待對方的回復fun,但是警察的say占用了this鎖,罪犯的say也占用了this鎖,

運行多次后出現了另外一種輸出:此時在子執行緒執行p.say(c)之前主執行緒搶先執行了p.fun(),程式正常結束,

該部分參考鏈接:
https://www.cnblogs.com/xiaoxi/p/8311034.html
十、Java多執行緒通信問題
Object類的一些方法:
void | notify() | 喚醒正在此物件監視器上等待的單個執行緒, |
|---|---|---|
void | notifyAll() | 喚醒等待此物件監視器的所有執行緒, |
void | wait() | 導致當前執行緒等待它,通常是 通知或 中斷 , |
void | wait?(long timeoutMillis) | 導致當前執行緒等待它,通常是 通知或 中斷 ,或者直到經過一定量的實時, |
void | wait?(long timeoutMillis, int nanos) | 導致當前執行緒等待它,通常是 通知或 中斷 ,或者直到經過一定量的實時, |
import java.util.concurrent.locks.Lock;
import java.util.concurrent.locks.ReentrantLock;
public class Demo4 {
/**
* 多執行緒通信問題, 生產者與消費者問題
* @param args
*/
public static void main(String[] args) {
Food f = new Food();
new Cook(f).start();
new Waiter(f).start();
}
//廚師
static class Cook extends Thread{
private Food f;
public Cook(Food f) {
this.f = f;
}
@Override
public void run() {
for(int i=0;i<100;i++){
if(i%2==0){
f.setNameAndSaste("老干媽小米粥","香辣味");
}else{
f.setNameAndSaste("煎餅果子","甜辣味");
}
}
}
}
//服務生
static class Waiter extends Thread{
private Food f;
public Waiter(Food f) {
this.f = f;
}
@Override
public void run() {
for(int i=0;i<100;i++){
try {
Thread.sleep(100);
} catch (InterruptedException e) {
e.printStackTrace();
}
f.get();
}
}
}
//食物
static class Food{
private String name;
private String taste;
public synchronized void setNameAndSaste(String name,String taste){
if(flag) {
this.name = name;
try {
Thread.sleep(100);
} catch (InterruptedException e) {
e.printStackTrace();
}
this.taste = taste;
}
}
public synchronized void get(){
System.out.println("服務員端走的菜的名稱是:" + name + ",味道:" + taste);
}
}
}
運行結果:

服務員端走的菜口味與廚師設定的口味不一樣,
分析:i%2==0時廚師做完老干媽小米粥,還沒等到服務員端走菜,又“回手掏” 開始了做菜,這次i%2==1廚師setName("煎餅果子")然后睡眠100ms這時被服務員執行緒搶占了鎖,服務員把菜端走了,因此菜品的名稱是“煎餅果子”,口味卻是”香辣味“,
這里廚師是生產者執行緒,而服務員是消費者執行緒,生產者生產一個單位的產品還未結束,就被消費者執行緒搶先運行,導致了產品的資料錯亂,
對Food進行修改:



運行結果:菜品資料皆正確

十一、帶回傳值的執行緒Callable
1、Runnable 與 Callable

2、Callable使用步驟

3、Runnable 與 Callable的相同點

4、Runnable 與 Callable的不同點
![]()
5、Callable獲取回傳值
Callalble介面支持回傳執行結果,需要呼叫FutureTask.get()得到,此方法會阻塞主行程的繼續往下執行,如果不呼叫不會阻塞,
6、FutureTask類
| 構造器 | 描述 |
|---|---|
FutureTask?(Runnable runnable, V result) | 創建一個 |
FutureTask?(Callable<V> callable) | 創建一個 |
| 變數和型別 | 方法 | 描述 |
|---|---|---|
protected void | done() | 當此任務轉換到狀態 |
V | get() | 如果需要等待計算完成,然后檢索其結果, |
V | get?(long timeout, TimeUnit unit) | 如果需要,最多等待計算完成的給定時間,然后檢索其結果(如果可用), |
protected boolean | runAndReset() | 執行計算而不設定其結果,然后將此未來重置為初始狀態,如果計算遇到例外或被取消則無法執行此操作, |
protected void | set?(V v) | 將此future的結果設定為給定值,除非已設定或已取消此未來, |
protected void | setException?(Throwable t) | 導致此未來報告帶有給定throwable的 |
String | toString() | 回傳此FutureTask的字串表示形式, |
實體:
import java.util.concurrent.Callable;
import java.util.concurrent.ExecutionException;
import java.util.concurrent.FutureTask;
public class DemoCallable {
public static void main(String[] args) throws ExecutionException, InterruptedException {
Callable callable = new MyCallable();
FutureTask<Integer> futureTask = new FutureTask<>(callable);
new Thread(futureTask).start();
Integer j = futureTask.get();//獲取回傳值
System.out.println(j);
for(int i=0;i<10;i++){
try{
Thread.sleep(100);
}catch (Exception e){
e.printStackTrace();
}
System.out.println(i);
}
}
static class MyCallable implements Callable{
@Override
public Integer call() throws Exception {
for(int i=0;i<10;i++){
try{
Thread.sleep(100);
}catch (Exception e){
e.printStackTrace();
}
System.out.println(i);
}
return 100;
}
}
}
運行結果:可以看到在子執行緒運行結束以后主執行緒才執行后續的代碼
十二、Java執行緒池
1、執行緒池 Executors
如果并發的執行緒數量很多,并且每個執行緒都是執行一個時間很短的任務就結束了,這樣頻繁創建執行緒就會大大降低系統的效率,因為頻繁創建執行緒和銷毀執行緒需要時間,執行緒池就是一個容納多個執行緒的容器,池中的執行緒可以反復使用,省去了頻繁創建執行緒物件的操作,節省了大量的時間和資源,
2、執行緒池的好處
- 降低資源消耗,
- 提高回應速度,
- 提高執行緒的可管理性,

3、Java中的四種執行緒池 . ExecutorService
(1) 快取執行緒池
/*** 快取執行緒池.
* (長度無限制)
* 執行流程:
* 1. 判斷執行緒池是否存在空閑執行緒
* 2. 存在則使用
* 3. 不存在,則創建執行緒并放入執行緒池, 然后使用
*/
ExecutorService service = Executors.newCachedThreadPool();
//向執行緒池中加入新的任務
service.execute(new Runnable() {
@Override
public void run() {
System.out.println("執行緒的名稱:"+Thread.currentThread().getName());
}
});
service.execute(new Runnable() {
@Override
public void run() {
System.out.println("執行緒的名稱:"+Thread.currentThread().getName());
}
});
service.execute(new Runnable() {
@Override
public void run() {
System.out.println("執行緒的名稱:"+Thread.currentThread().getName());
}
});

(2) 定長執行緒池
/**
* 定長執行緒池,
* (長度是指定的數值)
* 執行流程:
* 1. 判斷執行緒池是否存在空閑執行緒
* 2. 存在則使用
* 3. 不存在空閑執行緒,且執行緒池未滿的情況下,則創建執行緒并放入執行緒池,然后使用
* 4. 不存在空閑執行緒,且執行緒池已滿的情況下,則等待執行緒池存在空閑執行緒
*/
Executorservice service = Executors.newFixedThreadPool(2) ;
service.execute(new Runnable() {
@override
pub1ic void run() {
System.out.println("執行緒的名稱:"+Thread.currentThread().getName();
}
});
service.execute(new Runnable() {
@override
pub1ic void run() {
System.out.println("執行緒的名稱:"+Thread.currentThread().getName()) ;
}
});
3. 單執行緒執行緒池
效果與定長執行緒池創建時傳入數值1效果-致.
/**
* 單執行緒執行緒池.
* 執行流程:
* 1. 判斷執行緒池的那個執行緒是否空閑
* 2. 空閑則使用
* 3. 不空閑, 則等待池中的單個執行緒空閑后使用
*/
ExecutorService service = Executors.newsingleThreadexecutor();
service.execute(new Runnable(){
@override
public void run() {
System.out.println("執行緒的名稱:"+Thread.currentThread().getName());
}
});
service.execute(new Runnable() {
@override
public void run() {
System.out.println("執行緒的名稱:"+Thread.currentThread().getName()) ;
}
});
4. 周期性任務定長執行緒池
pub1ic static void main(String[] args) {
/**
* 周期任務定長執行緒池.
* 執行流程:
* 1. 判斷執行緒池是否存在空閑執行緒
* 2. 存在則使用
* 3. 不存在空閑執行緒,且執行緒池未滿的情況下,則創建執行緒并放入執行緒池,然后使用
* 4. 不存在空閑執行緒,且執行緒池已滿的情況下,則等待執行緒池存在空閑執行緒
* 周期性任務執行時:
* 定時執行,當某個時機觸發時,自動執行某任務.
*/
SchedledExecutorservice service = Executors.newscheduledThreadPool(2) ;
/**
* 定時執行
* 引數1. runnable型別的任務
* 引數2. 時長數字
* 引數3. 時長數字的單位
*/
/*service.schedule(new Runnable(){
@override
public void run() {
System.out.println("倆人相視一笑~嘿嘿嘿");
},5,TimeUnit.SECONDS);
*/
/**
*周期執行
*引數1. runnable型別的任務
*引數2. 時長數字(延遲執行的時長)
*引數3. 周期時長(每次執行的間隔時間)
*引數4. 時長數字的單位
*/
service.scheduleAtFixedRate(new Runnable(){
@override
public void run(){
System.out.println("倆人相視一笑~嘿嘿嘿") ;
},5,2,TimeUnit.SECONDS);
}
十三、Lambda運算式
使用方法:
![]()

面向物件的一般代碼:

使用Lambda運算式:


轉載請註明出處,本文鏈接:https://www.uj5u.com/qita/290008.html
標籤:其他
