學習多線程 1.認識程式,進程,線程 程式 程式由指令和數據組成,是靜態的 進程 啟動一個程式,就啟動了一個進程。 進程就是程式的一次執行過程。 進程是動態的。 線程 一個進程包含多個線程。例如:播放視頻時,有動畫,有聲音,有彈幕…… 2.如何創建線程 繼承Thread類 三板斧: 繼承Thread ...
學習多線程
1.認識程式,進程,線程
程式
程式由指令和數據組成,是靜態的
進程
啟動一個程式,就啟動了一個進程。
進程就是程式的一次執行過程。
進程是動態的。
線程
一個進程包含多個線程。例如:播放視頻時,有動畫,有聲音,有彈幕……
2.如何創建線程
繼承Thread類
三板斧:
- 繼承Thread類
- 重寫run方法
- 啟動線程
實現Runnable介面
四板斧:
- 實現Runnable介面
- 重寫run方法
- 將線程對象放入Thread對象
- 啟動Thread對象的線程
實現Callable介面
3.靜態代理模式
一個關於結婚的例子
You you = new You();
WeddingFirm wf = new WeddingFirm(you);
wf.marry();
//You類,WeddingFirm類都實現了Marry介面,也就都實現了marry方法
代入到線程
MyThread mt = new MyThread();
Thread t = new Thread(mt);
t.start();
//MyThread 類,Thread 類都實現了 Runnable 介面,也就都實現了 run 方法
4.lambda表達式
函數式介面
函數式介面
只包含一個抽象方法的介面。一個典型的函數式介面:Runnable介面
逐步簡化
- 外部類
- 靜態內部類
- 局部內部類
- 匿名內部類
- lambda表達式
- 去參數類型
- 去小括弧
- 去大括弧
lambda語法
(參數列表) -> {實現抽象方法的方法體}
參數列表
- 有一個參數,不需要小括弧
- 沒有參數,或有多個參數必須加小括弧
- 參數類型要省略就全省略。為什麼可以省略?Java會去檢查運行類型(即函數式介面)的方法。
方法體
- 只有一條語句:無論有沒有返回值都不需要大括弧。如果寫了return,就一定要加大括弧。
- 有多條語句:一定要加大括弧
舉例:
Thread thread = new Thread(() -> System.out.println("Hello World!")).start();
new Thread(Runnable)
構造方法中可以放入一個對象,這個對象必須是實現了Runnable介面的對象
擴展:
new 代理類(實現了 任意函數式介面的 匿名內部類 對象)
5.線程狀態
線程狀態
線程方法
線程停止
推薦使用 設置標誌位 的方式,如flag =true/false
線程休眠
線程禮讓
線程插隊
監測線程狀態
線程優先順序
守護線程
6.線程同步
同步機制
先瞭解一個名詞:併發
併發
多個線程想要獲得(或操作)某個對象(或者某個資源),這種情況就叫做併發。但是當他們同時操作這個對象時,會有異常發生,例如:搶票問題(某一張票),取錢問題(某一張銀行卡)。
這時,就要想些辦法,來避免這些異常發生,於是,線程同步機制就出現了。說白了,線程同步是一種辦法(或機制),這種辦法,就是為瞭解決併發的問題。
於是,解決併發的問題,就變成瞭如何實現線程同步的問題。
同時和同步的區別:
同時和同步在含義和用法上有明顯的區別。
同時強調的是時間因素,指的是在同一時刻,兩個或多個事件同時發生。例如,當我們說“同時出發”時,指的是在某一特定的時間點,所有人或物都開始行動。
同步則不同,它更多地強調的是事件之間的協調性和一致性,而非單純的時間因素。例如,在舞蹈表演中,每個舞者需要按照同樣的節拍和步伐來表演,這就是同步。也就是說,所有舞者需要在時間和動作上保持一致,才能達到同步的效果。
以上是同時和同步的主要區別,希望對你有所幫助。
上面的解釋來自於百度文心一言。
隊列和鎖
光有隊列,不一定安全。正在打飯的人得到一個鎖,才能打飯,打完了飯,就釋放掉鎖,後面的人繼續獲取鎖,繼續打飯……
如果沒有鎖,前面的人還沒打完飯,後面的人就迫不及待地也要打飯,於是,可能就打起來了……所以,隊列加鎖很有必要。
隊列和鎖解決了線程同步的安全性
同步鎖
synchronized 同步鎖
同步方法
同步方法弊端
同步塊
死鎖
釋放死鎖:
死鎖概念:
避免死鎖:
Lock鎖
Lock鎖的概念
自定義lock鎖
synchronized與lock對比
7.線程通信
生產者與消費者模式
線程通信問題分析
解決線程通信的方法
模型1:
管程法代碼實現
package thread.communication;
/**
* 測試生產者與消費者模式:利用緩衝區解決(管程法)
* 需要生產者,消費者,產品,緩衝區
*/
public class TestPC {
public static void main(String[] args) {
SynContainer synContainer = new SynContainer();
new Producer(synContainer).start();
new Consumer(synContainer).start();
}
}
//消費者
class Consumer extends Thread {
SynContainer synContainer;
public Consumer(SynContainer synContainer) {
this.synContainer = synContainer;
}
@Override
public void run() {
for (int i = 1; i <= 100; i++) {
System.out.println("消費了第" + synContainer.pop().id + "只雞");
}
}
}
//生產者,相當於後廚
class Producer extends Thread {
SynContainer synContainer;
public Producer(SynContainer synContainer) {
this.synContainer = synContainer;
}
@Override
public void run() {
for (int i = 1; i <= 100; i++) {
synContainer.push(new Chicken(i));
System.out.println("生產了第" + i + "只雞");
}
}
}
//容器,相當於前臺
class SynContainer {
//定義數組,存放產品
Chicken[] chickens = new Chicken[10];
//計數器
int cnt = 0;
//生產者放入產品
public synchronized void push(Chicken chicken) {
//如果滿了,就等待消費者消費
if (10 == cnt) {
try {
this.wait();
} catch (InterruptedException e) {
e.printStackTrace();
}
}
//如果沒滿,就放入產品
chickens[cnt++] = chicken;
//通知消費者可以消費
this.notifyAll();
}
//消費者取出產品
public synchronized Chicken pop() {
//如果容器為空,就等待生產者生產
if (0 == cnt) {
try {
this.wait();
} catch (InterruptedException e) {
e.printStackTrace();
}
}
//通知生產者可以生產
this.notifyAll();
//如果沒空,就取出產品
return chickens[--cnt];
}
}
class Chicken {
public int id;
public Chicken(int id) {
this.id = id;
}
}
模型2:
信號燈法代碼實現
package thread.communication;
/**
* 生產者消費者問題2:信號燈法,標誌位解決
*/
public class TestPC2 {
public static void main(String[] args) {
TV tv = new TV();
new Actor(tv).start();
new Watcher(tv).start();
}
}
//生產者:演員
class Actor extends Thread {
TV tv = new TV();
public Actor(TV tv) {
this.tv = tv;
}
@Override
public void run() {
for (int i = 0; i < 20; i++) {
if (i % 2 == 0)
this.tv.play("快樂大本營");
else
this.tv.play("抖音");
}
}
}
//消費者:觀眾
class Watcher extends Thread {
TV tv;
public Watcher(TV tv) {
this.tv = tv;
}
@Override
public void run() {
for (int i = 0; i < 20; i++) {
tv.watch();
}
}
}
//產品:節目
class TV {
//演員表演,觀眾等待
//觀眾觀看,演員等待
String voice;//節目
boolean flag = true;
public synchronized void play(String voice) {
if (!flag) {
try {
this.wait();
} catch (InterruptedException e) {
e.printStackTrace();
}
}
System.out.println("演員表演了" + voice);
//通知觀看
this.notifyAll();
this.voice = voice;
this.flag = !this.flag;
}
//觀看
public synchronized void watch() {
if (flag)
try {
this.wait();
} catch (InterruptedException e) {
e.printStackTrace();
}
System.out.println("觀眾觀看了:" + voice);
//通知演員表演
this.notifyAll();
this.flag = !this.flag;
}
}
註意點:線程間通信的前提是 線程同步,所以依然需要設置鎖。
8.線程池
線程池概念
使用線程池
線程池代碼實現
package thread.pool;
import java.util.concurrent.ExecutorService;
import java.util.concurrent.Executors;
/**
* 測試線程池
*/
public class TestPool {
public static void main(String[] args) {
//1.創建服務,創建線程池
//參數為線程大小
ExecutorService service = Executors.newFixedThreadPool(10);
//執行
service.execute(new MyThread());
service.execute(new MyThread());
service.execute(new MyThread());
service.execute(new MyThread());
//關閉鏈接
service.shutdown();
}
}
class MyThread implements Runnable {
@Override
public void run() {
System.out.println(Thread.currentThread().getName());
}
}