1. 程式人生 > 實用技巧 >方法入參和返回值

方法入參和返回值

一、簡介

程式:指令和資料的集合

程序:程式的一次執行過程,是系統資源分配的基本單位

執行緒:是cpu排程和執行的單位

二、執行緒實現

  1. 繼承Thread
  • ThreadImpl extends Thread
  • 重寫run()
  • new Thread().start()
  1. 實現Runnable介面
  • ThreadImpl implements Runnable
  • 重寫run()
  • new Thread(new ThreadImpl( )).start()
  1. 實現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表示式來建立該介面的物件

四、執行緒狀態

  1. 新建:執行緒被建立,處於新建狀態

  2. 就緒:執行緒物件呼叫start()方法開啟執行緒,等待被cpu排程執行

  3. 執行:就緒狀態的執行緒獲得了cpu時間片,執行run()方法

  4. 阻塞: 執行緒放棄了對cpu的使用權,暫時停止執行

    ​ 等待阻塞:執行緒呼叫wait()方法,進入等待阻塞

    ​ 同步阻塞:執行緒獲取synchronized同步鎖失敗,進入同步阻塞

    ​ 其他阻塞:執行緒呼叫sleep()方法,進入阻塞狀態

    wait() sleep()
    會釋放鎖,屬於Object類 不會釋放鎖,屬於Thread類
    只能在同步方法或者程式碼塊裡使用,不需要拋異常 可以在任何地方使用,需要丟擲異常
    需要notify()或notifyAll()喚醒 不需要喚醒,休眠之後自動退出阻塞
  5. 死亡:執行緒執行完了run()方法,或者因為異常而退出了run()方法,執行緒的生命週期結束

方法 說明
setPriority( int ) 更改執行緒的優先順序
sleep( long ) 指定的毫秒內休眠
join() 等待執行緒終止
yield() 讓出cpu,再和其他執行緒一起爭搶cpu
interrupt() 中斷執行緒,別用
isAlive() 測試執行緒是否處於活動狀態

守護執行緒(setDaemon(true))

  • 執行緒分為使用者執行緒守護執行緒
  • jvm必須確保使用者執行緒執行完畢,不用等待守護執行緒執行完畢
  • 比如:後臺記錄操作日誌,監控記憶體,垃圾回收等待。。。

死鎖

​ 兩個或多個執行緒,同時阻塞,都在等待某個資源的釋放,而這個資源又被其他的執行緒佔用,所以執行緒就一致阻塞,導致執行緒死鎖。比如:現在有A、B兩個執行緒,同時申請對方的資源,就會相互等待,執行緒一直處於等待狀態,導致死鎖。

​ 一個同步程式碼塊同時擁有 " 兩個以上物件的鎖 ",就可能發生 " 死鎖 "。

執行緒死鎖的四個條件

  1. 互斥:一個資源任意時刻,只能被一個執行緒佔用
  2. 不剝奪:執行緒資源在使用完之前,不能被其他執行緒剝奪
  3. 請求和保持:執行緒保持了多個資源,又發出了新的資源請求,該資源被其他執行緒佔用
  4. 迴圈等待:在資源等待鏈中,每一個執行緒獲得的資源同時,被下一個執行緒請求

破壞死鎖:破壞四個條件中的一種, 互斥條件沒辦法破壞。

  1. 破壞不剝奪:申請不到資源時,可以主動釋放當前佔有的資源
  2. 破壞請求和保持:一次性申請所有資源
  3. 破壞迴圈等待:按照順序請求和釋放資源

五、執行緒同步

一、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());
    }
}