方法入參和返回值
一、簡介
程式:指令和資料的集合
程序:程式的一次執行過程,是系統資源分配的基本單位
執行緒:是cpu排程和執行的單位
二、執行緒實現
- 繼承Thread
- ThreadImpl extends Thread
- 重寫run()
- new Thread().start()
- 實現Runnable介面
- ThreadImpl implements Runnable
- 重寫run()
- new Thread(new ThreadImpl( )).start()
- 實現Callable介面
- ThreadImpl implements Callable< call()的返回值 >
- 重寫call()
- TestCallable t1 = new TestCallable()
- 建立執行服務 ExcutorService ser = Excutors.newFixedThreadPool(3);建立池子數量
- 提交執行 Future< bollean > result = ser.submit( t1 );
- 獲取結果:result.get()
- 關閉服務:ser.shutdownNow();
推薦使用實現Runnable介面,避免了單繼承的侷限性,靈活方便,方便同一個物件被多個執行緒使用
三、Lambda表示式
- λ是希臘字母中,排第十一的字母==>英語名稱為Lamda
- 優點:
- 避免匿名內部類過多,程式碼看起更加簡潔;
- 去掉了一堆沒有意義的程式碼,只留下核心的邏輯
- 實質屬於函數語言程式設計
(param)->expression [ 表示式 ]
(param)->statement [ 語句 ]
(param)->{statements}
lambda演化:外部類 --> 靜態內部類 --> 區域性類 --> 匿名內部類(藉助介面或父類實現) --> lambda表示式【 (引數)-> { 方法體 } 】
函式式介面
定義:只包含一個抽象方法。
對於函式式介面,可以通過Lamda表示式來建立該介面的物件
四、執行緒狀態
-
新建:執行緒被建立,處於新建狀態
-
就緒:執行緒物件呼叫start()方法開啟執行緒,等待被cpu排程執行
-
執行:就緒狀態的執行緒獲得了cpu時間片,執行run()方法
-
阻塞: 執行緒放棄了對cpu的使用權,暫時停止執行
等待阻塞:執行緒呼叫wait()方法,進入等待阻塞
同步阻塞:執行緒獲取synchronized同步鎖失敗,進入同步阻塞
其他阻塞:執行緒呼叫sleep()方法,進入阻塞狀態
wait() sleep() 會釋放鎖,屬於Object類 不會釋放鎖,屬於Thread類 只能在同步方法或者程式碼塊裡使用,不需要拋異常 可以在任何地方使用,需要丟擲異常 需要notify()或notifyAll()喚醒 不需要喚醒,休眠之後自動退出阻塞 -
死亡:執行緒執行完了run()方法,或者因為異常而退出了run()方法,執行緒的生命週期結束
方法 | 說明 |
---|---|
setPriority( int ) | 更改執行緒的優先順序 |
sleep( long ) | 指定的毫秒內休眠 |
join() | 等待執行緒終止 |
yield() | 讓出cpu,再和其他執行緒一起爭搶cpu |
interrupt() | 中斷執行緒,別用 |
isAlive() | 測試執行緒是否處於活動狀態 |
守護執行緒(setDaemon(true))
- 執行緒分為使用者執行緒和守護執行緒
- jvm必須確保使用者執行緒執行完畢,不用等待守護執行緒執行完畢
- 比如:後臺記錄操作日誌,監控記憶體,垃圾回收等待。。。
死鎖
兩個或多個執行緒,同時阻塞,都在等待某個資源的釋放,而這個資源又被其他的執行緒佔用,所以執行緒就一致阻塞,導致執行緒死鎖。比如:現在有A、B兩個執行緒,同時申請對方的資源,就會相互等待,執行緒一直處於等待狀態,導致死鎖。
一個同步程式碼塊同時擁有 " 兩個以上物件的鎖 ",就可能發生 " 死鎖 "。
執行緒死鎖的四個條件:
- 互斥:一個資源任意時刻,只能被一個執行緒佔用
- 不剝奪:執行緒資源在使用完之前,不能被其他執行緒剝奪
- 請求和保持:執行緒保持了多個資源,又發出了新的資源請求,該資源被其他執行緒佔用
- 迴圈等待:在資源等待鏈中,每一個執行緒獲得的資源同時,被下一個執行緒請求
破壞死鎖:破壞四個條件中的一種, 互斥條件沒辦法破壞。
- 破壞不剝奪:申請不到資源時,可以主動釋放當前佔有的資源
- 破壞請求和保持:一次性申請所有資源
- 破壞迴圈等待:按照順序請求和釋放資源
五、執行緒同步
一、synchronized
為了保證資料在方法中被正確訪問的正確性,在訪問時加入鎖機制synchronized,當一個執行緒獲得物件的排它鎖,獨佔資源其他執行緒必須等待,存在以下問題:
- 一個執行緒持有鎖會導致其他需要此鎖的執行緒掛起
- 在多執行緒競爭下,加鎖,釋放鎖會導致比較多的上下文切換和排程延時,引起效能問題
- 如果一個優先順序高的執行緒等待一個優先順序低的執行緒釋放鎖,會導致優先順序導致,引起效能問題
CopyOnWriteArrayList是執行緒安全的集合
同步方法以及同步塊
private關鍵字保證資料只能被方法訪問,所以只需要對方法提出一套機制--->synchronized關鍵字,它包括兩種用法,同步方法和同步程式碼塊
- 同步方法:public synchronized void method(){},鎖的是this,缺陷—>影響效率
- 同步程式碼塊:synchronized (Obj){},需要鎖的是增刪改的物件
二、Lock
JDk5.0開始,提供了更強大的執行緒同步機制——通過顯示顯示定義同步鎖。鎖提供了對共享資源的獨佔訪問,每次只能有一個執行緒對Lock物件加鎖,訪問資源之前應先獲得Lock物件。
ReentrantLock(可重入鎖)類實現了Lock,擁有和synchronized相同的併發性和記憶體語義,在實現執行緒安全的控制中,比較常用的是ReentrantLock,可以顯示加鎖和釋放鎖。
三、Lock和Synchronized的對比
Lock | Synchronized |
---|---|
顯示鎖,需要手動開啟和關閉鎖 | 隱式鎖,出了作用域會自動釋放鎖 |
只能鎖程式碼塊 | 鎖程式碼塊和方法 |
JVM花費更少的時間去排程執行緒,效能好 |
六、執行緒間通訊
java提供了幾個方法解決了執行緒間的通訊問題
方法名 | 描述 |
---|---|
wait() | 執行緒一直等待,直到其他執行緒通知,會釋放鎖 |
wait(long timeout) | 指定等待的毫秒數 |
notify() | 隨機喚醒一個處於等待狀態的執行緒 |
notifyAll() | 喚醒同一個物件上所有呼叫wait()的執行緒,優先級別高的優先排程 |
生產者消費者問題
這是一個執行緒同步問題,生/消共享同一個資源,僅有一個synchronized是不夠的
- synchronized可以阻止併發更新同一個共享資源,實現了同步
- 但是,不能實現執行緒間的通訊
解決方式一:管程法
- 生產者:負責生產資料的模組(模組可以是方法、物件、執行緒、程序)
- 消費者:負責處理資料的模組(模組可以是方法、物件、執行緒、程序)
- 緩衝區:消費者不能直接使用生產者的資料,他們之間有個 " 緩衝區 " ; 生產者將生產好的資料放入緩衝區,消費者從緩衝區取出資料
package thread;
/**
*
* @author 柯神_
* @date 2020-11-29 17:17:17
* @Description 執行緒間通訊,
* 生產者消費者問題
* 解決方式:一、管程法
* 二、訊號燈法
*/
public class ThreadCommunication {
public static void main(String[] args) {
Buffer buffer = new Buffer();
Producer producer = new Producer(buffer);
Consumer consumer = new Consumer(buffer);
new Thread(producer).start();
new Thread(consumer).start();
}
}
/**
* 一、管程法
*/
//生產者
class Producer implements Runnable{
Buffer buffer;
public Producer(Buffer buffer) {
this.buffer = buffer;
}
@Override
public void run() {
for (int i = 0; i < 100; i++) {
buffer.push(new Product(i));
System.out.println("生產了" + i + "只雞!");
}
}
}
//消費者
class Consumer implements Runnable{
Buffer buffer;
public Consumer(Buffer buffer) {
this.buffer = buffer;
}
@Override
public void run() {
for (int i = 0; i < 100; i++) {
System.out.println("消費了==>" + buffer.getP().id + "只雞!");
}
}
}
//產品
class Product{
int id;
public Product(int id) {
this.id = id;
}
}
//緩衝區
class Buffer{
//定義一個容器大小
Product[] products = new Product[10];
//容器計數器
int count = 0;
//1.生產者生產產品
public synchronized void push(Product product){
/**
* 緩衝區滿了,生產等待,通知消費
*/
if (count == products.length - 1){
try {
this.wait();
} catch (InterruptedException e) {
e.printStackTrace();
}
}
/**
* 緩衝區未滿,可以生產
*/
products[count] = product;
count++;
/**
* 有產品了
* 通知消費者消費
*/
this.notifyAll();
}
//2.消費者消費產品
public synchronized Product getP(){
/**
* 緩衝區空了,等待消費,通知生產
*/
if (count == 0){
try {
this.wait();
} catch (InterruptedException e) {
e.printStackTrace();
}
}
/**
* 緩衝區有產品,可以消費
*/
count--;
Product product = products[count];
/**
* 吃了,緩衝區沒滿了
* 通知生產者生產
*/
this.notifyAll();
return product;
}
}
解決方式二:訊號燈法
訊號燈法是通過標誌位來實現
/**
* 二、訊號燈法:通過標誌位來實現
*/
package thread;
import lombok.SneakyThrows;
/**
* 二、訊號燈法:通過標誌位來實現
*/
public class ThreadCommunication2 {
public static void main(String[] args) {
TV tv = new TV();
Player player = new Player(tv);
Watcher watcher = new Watcher(tv);
new Thread(player).start();
new Thread(watcher).start();
}
}
//生產者 ==>演員錄製
class Player implements Runnable{
TV tv;
public Player(TV tv) {
this.tv = tv;
}
@Override
public void run() {
for (int i = 0; i < 10; i++) {
if (i % 2 == 0) {
this.tv.play("錄製快本中。。。");
} else {
this.tv.play("新聞聯播錄製...");
}
}
}
}
//消費者 ==>觀眾
class Watcher implements Runnable{
TV tv;
public Watcher(TV tv) {
this.tv = tv;
}
@Override
public void run() {
for (int i = 0; i < 10; i++) {
tv.watch();
}
}
}
//產品 ==>小視訊節目
class TV{
private String name;
private boolean flag = true; //標誌位
/**
* 表演錄製:觀眾等待
*/
@SneakyThrows
public synchronized void play(String name){
if (!flag){
wait();
}
System.out.println("演員表演了" + name);
this.notify();
this.name = name;
this.flag = !this.flag;
}
/**
* 觀眾觀看:演員等待
*/
@SneakyThrows
public synchronized void watch(){
if (flag){
this.wait();
}
System.out.println("觀眾觀看=======>" + name);
this.notifyAll();
this.flag = !this.flag;
}
}
七、執行緒池
背景:執行緒頻繁地建立和銷燬,對系統的效能影響很大
執行緒池:提前建立好多個執行緒,放入執行緒池中,使用時直接獲取,使用完放回池中,實現資源重複利用。
優點:
- 提高了響應速度(減少了建立執行緒的時間)
- 降低了資源的消耗(執行緒使用完放回池中,實現資源重複利用)
- 便於執行緒管理
- corePoolSize:核心池的大小
- maximumPoolSize:最大執行緒數
- keepAliveTime:執行緒沒有任務時,多長時間後終止
JDK5.0開始,提供了執行緒池相關的API:ExcutorService 和 Excutors
ExecutorService:真正的執行緒池介面。常見子類ThreadPoolExcutor
- shutdown()關閉連線池
Executors:工具類、執行緒池的工廠類,用於建立並返回不同型別的執行緒池。
package thread;
import java.util.concurrent.Executor;
import java.util.concurrent.ExecutorService;
import java.util.concurrent.Executors;
/**
*
* @author 柯神_
* @date 2020-11-29 20:21:10
* @Description
* 測試執行緒池
*/
public class PoolTest {
public static void main(String[] args) {
//建立執行緒服務,執行緒池,引數為執行緒池的大小
ExecutorService service = Executors.newFixedThreadPool(10);
service.execute(new MyPool());
service.execute(new MyPool());
service.execute(new MyPool());
service.execute(new MyPool());
service.shutdown();
}
}
class MyPool implements Runnable {
@Override
public void run() {
System.out.println(Thread.currentThread().getName());
}
}