1. 程式人生 > 實用技巧 >Spring Boot 2.4.0 釋出說明

Spring Boot 2.4.0 釋出說明

多執行緒 1.執行緒的核心概念
  • 執行緒就是獨立的執行路徑;
  • 線上程執行時,即使沒有建立執行緒,後臺也會有很多執行緒,如 :GC 執行緒、主執行緒。
  • main()稱之為主執行緒,為了系統的入口,用於執行整個程式。‘
  • 在一個程序中,如果開闢了多個執行緒,執行緒的執行由排程器安排排程,排程器是與作業系統緊密相關的,先後順序是不能人為的干預的。
  • 對同一份資源操作時,會存在資源搶奪的問題,需要加入併發控制;
  • 執行緒會帶來額外的開銷,如cpu排程時間,併發控制開銷。
  • 每個執行緒在自己的工作記憶體互動,記憶體控制不當會造成資料不一致
2.實現執行緒的三種方式
  1. Thread類 (si ruai de)
    1. 自定義執行緒繼承Thread類
    2. 重寫run()方法,編寫執行緒執行體
    3. 建立執行緒物件,呼叫start()方法啟動執行緒
//建立執行緒方式一:繼承thread類,重寫run()方法,呼叫start開啟執行緒
    //總結:注意,執行緒開啟不一定立即執行,由CPU排程執行
public class ThreadDemo01 extends Thread {
    @Override
    public void run() {//run方法執行緒體
        for (int i = 0; i <100; i++) {
            System.out.println("多執行緒被執行了");
        }
    }
​
    
public static void main(String[] args) { //main執行緒,主執行緒 //建立一個執行緒物件 ThreadDemo01 td1 = new ThreadDemo01(); td1.start();//呼叫start方法開啟執行緒 for (int i = 0; i <1000 ; i++) { System.out.println("每天都在學習java"); } } }

2. Runnable介面 (ruan na bo)
  • 自定義執行緒實現Runnable介面
  • 重寫run()方法 ,編寫執行體
  • 啟動執行緒 new Thread(自定義執行緒類).start。採用的靜態代理
public class ThreadDemo03 implements Runnable{
    @Override
    public void run() {
        for (int i = 0; i <200 ; i++) {
            System.out.println("多執行緒被執行了");
        }
    }
​
    public static void main(String[] args) {
        new Thread(new ThreadDemo03()).start();
        for (int i = 0; i <1000 ; i++) {
            System.out.println("每天都在學習java");
        }
    }
}

3.小結
  • 繼承Thread類
    • 子類繼承Thread類具備多執行緒能力
    • 啟動執行緒:子類物件.start()
    • 不建議使用:避免OOP單繼承的侷限性
  • 實現Runnable介面
    • 實現Runnable具有對執行緒能力
    • 啟動執行緒:傳入目標物件+Thread物件.start();
  • 推薦使用:避免單繼承的侷限性,靈活方便,方便同一個物件被對各執行緒使用
例項
public class ThreadDemo04 implements Runnable{
    private static String winner;//勝利者
    @Override
    public void run() {
        for (int i = 0; i <=100 ; i++) {
            //模擬兔子休息
            if(Thread.currentThread().getName().equals("兔子")&& i%20==0){
                try {
                    Thread.sleep(10);
                } catch (InterruptedException e) {
                    e.printStackTrace();
                }
            }
​
            //判斷是否結束比賽
                boolean flag=gameOver(i);
            //如果比賽結束了,停止程式
            if(flag){
                break;
            }
            System.out.println(Thread.currentThread().getName()+"-->跑了"+i+"步");
        }
    }
    //判斷是否完成比賽
    private boolean gameOver(int steps){
      //判斷是否有勝利者
      if(winner!=null){
          return true;
      }  {
          if (steps>=100){
              winner=Thread.currentThread().getName();
              System.out.println("winner is:"+winner);
              return true;
          }
        }
      return false;
    }
    public static void main(String[] args) {
        new Thread(new ThreadDemo04(),"兔子").start();
        new Thread(new ThreadDemo04(),"烏龜").start();
    }
}

3. 實現Callable介面 (目前階段 只做瞭解)
  1. 實現Callable介面 ,需要有返回值型別
  2. 重寫call方法,需要拋異常
  3. 建立目標物件
  4. 建立執行服務
  5. 提交執行:Future<Boolean> result1 = ser.submit(t1)
  6. 獲取結果:result.get();
  7. 關閉服務:ser.shutdownNow();
4.靜態代理
  1. 真實物件和代理物件都要實現同一個介面
  2. 代理物件要代理真實角色
好處:
  1. 代理物件可以做很多真實物件做不了的事情
  2. 真實物件專注做自己的事情
5.Lambda表示式
  1. 避免匿名內部類定義過多
  2. 其實質屬於函數語言程式設計的概念
  3. 可以讓程式碼看起來很簡潔
  4. 去掉了一堆沒有意義的程式碼,只留下核心的邏輯
6.函式式介面 Functional Interface(函式式介面) 定義:
  1. 任何介面,如果只包含唯一一個抽象方法,那麼它就是一個函式式介面。
  2. 對於函式式介面,可以通過lambda表示式來建立該介面的物件。
public class TestLambda1 {
    //2.靜態內部類
    static class Love implements ILove {
​
        public void ILove(int a) {
            System.out.println("i like lambda" + a);
        }
    }
    public static void main(String[] args) {
      Love love=new Love();
      love.ILove(1);
      Love love1=new Love();
      love1.ILove(2);
        class Love implements ILove {
        //3.區域性內部類
            public void ILove(int a) {
                System.out.println("i like lambda" + a);
            }
        }
        Love love2 = new Love();
        love2.ILove(3);
        //4.匿名內部類
        ILove iLove=new ILove() {
            @Override
            public void ILove(int a) {
                System.out.println("i like lambda" + a);
            }
        };
      iLove.ILove(4);
      //5.lambda表示式
        ILove iLove1=(int a)->{
            System.out.println("i like lambda" + a);
        };
        iLove1.ILove(5);
    }
}
//定義一個介面,只有一個方法,函式式介面
interface ILove{
    void ILove(int a);
}
    //1.普通實現
class Love implements ILove {
        @Override
    public void ILove(int a) {
        System.out.println("i like lambda" + a);
    }
}
public class TestLambda2 {
    public static void main(String[] args) {
        YouLove youLove=(a,b)->{
            System.out.println("一句話你說:"+a+b);
        };
        youLove.youLove(10,20);
    }
}
​
interface YouLove{
    void youLove(int a,int b);
}

總結:
  1. lambda表示式只能有一行程式碼的情況下才能簡化為一行,如果有多行,那麼就用程式碼塊包裹
  2. 前提是介面為函式式介面
  3. 多個引數也可以去掉引數型別,要去掉就都去掉,必須加上括號。
7:執行緒休眠 (Sleep)
  1. sleep(時間)指定當前執行緒阻塞的毫秒數;
  2. sleep存在異常InterruptedException
  3. sleep時間到達後執行緒進入就緒狀態
  4. sleep可以模擬網路延時,倒計時等。
  5. 每一個物件都有一個鎖,sleep不會釋放鎖
public class TestSleep1 {
    public static void main(String[] args) {
    tenDown();
    //列印當前系統時間
        Date startTime=new Date(System.currentTimeMillis());//獲取當前系統時間
        while (true){
            try {
                Thread.sleep(1000);
                System.out.println(new SimpleDateFormat("HH:mm:ss").format(startTime));
                startTime=new Date(System.currentTimeMillis());//更新時間
            } catch (InterruptedException e) {
                e.printStackTrace();
            }
        }
    }
    //模擬倒計時
    public static void tenDown(){
        int num=10;
        while (true){
            try {
                Thread.sleep(1000);
                if (num<=0){
                    break;
                }else{
                    System.out.println("倒計時!!!"+num--+"秒");
                }
            } catch (InterruptedException e) {
                e.printStackTrace();
            }
        }
    }
}

8:執行緒禮讓 (yield)
  1. 禮讓執行緒,讓當前正在執行的執行緒暫停,但不阻塞
  2. 將執行緒從執行狀態轉為就緒狀態
  3. 讓cpu重新排程,禮讓不一定成功!看CPU心情。
public class TestYield implements Runnable {
    public static void main(String[] args) {
        TestYield yield=new TestYield();
        new Thread(yield,"a").start();
        new Thread(yield,"b").start();
    }
    @Override
    public void run() {
        System.out.println(Thread.currentThread().getName()+"執行緒開始執行");
        Thread.yield();//禮讓
        System.out.println(Thread.currentThread().getName()+"執行緒停止執行");
    }
}

9:執行緒合併 (join) join合併執行緒,待此執行緒執行完成後,再執行其他執行緒,其他執行緒阻塞
public class TestJoin implements Runnable {
    @Override
    public void run() {
        for (int i = 0; i <100 ; i++) {
            System.out.println("VIP執行緒來插隊了!!!"+i);
        }
    }


        public static void main(String[] args) throws InterruptedException {
           Thread thread= new Thread(new TestJoin());

            for (int i = 0; i <400 ; i++) {
                System.out.println("主執行緒在排隊!!!"+i);
                if (i==100){
                    thread.start();
                    thread.join();

            }
        }
    }
}

10.執行緒優先順序
  1. java提供一個執行緒排程器來監控程式中啟動後進入就緒狀態的所有執行緒,執行緒排程器按照優先順序決定應該排程哪個執行緒來執行。
  2. 執行緒的優先順序用數字來表示,範圍1~10.
  3. 使用下面方式來改變優先順序或獲取優先順序
    1. getPriority().setPriority(int xxx)
  4. 先設定優先順序,再start執行緒!!!
public class TestPriority {
    public static void main(String[] args) {
        MyPriority myPriority = new MyPriority();
        Thread t1 = new Thread(myPriority);
        Thread t2 = new Thread(myPriority);
        Thread t3 = new Thread(myPriority);
        Thread t4 = new Thread(myPriority);
        Thread t5 = new Thread(myPriority);
        Thread t6 = new Thread(myPriority);
        //先設定執行緒優先順序
        t1.setPriority(1);
        t1.start();
        t2.setPriority(3);
        t2.start();
        t3.setPriority(6);
        t3.start();
        t4.setPriority(Thread.MAX_PRIORITY);//  優先順序=10
        t4.start();
        t5.setPriority(Thread.MIN_PRIORITY);// 優先順序=1
        t6.setPriority(9);
        t6.start();

        System.out.println("main");
    }
}
class MyPriority implements Runnable{

    @Override
    public void run() {
        System.out.println(Thread.currentThread().getName()+"---執行緒被執行了!---"+Thread.currentThread().getPriority());
    }
}

11:守護(daemon)執行緒
  • 執行緒分為使用者執行緒和守護執行緒
  • 虛擬機器必須確保使用者執行緒執行完畢
  • 虛擬機器不用等待守護執行緒執行完畢
  • 如,後臺記錄操作日誌,監控記憶體,垃圾回收等待。。。
public class TestDaemon {
    public static void main(String[] args) {
        God god = new God();
        You you=new You();
        Thread thread = new Thread(god);
        thread.setDaemon(true);//預設為flase 為使用者執行緒,  true為守護執行緒
        thread.start();
        new Thread(you).start();
    }
}
class God implements Runnable{

    @Override
    public void run() {
        while (true){
            System.out.println("上帝守護著你-------");
        }
    }
}
class You implements Runnable{

    @Override
    public void run() {
        for (int i = 0; i <36500 ; i++) {
            System.out.println("開心著活著每一天------");
        }
        System.out.println("----goodbye!Beautiful World!!!------");

    }
}

12:執行緒同步機制 執行緒同步
  • 由於同一進城的多個執行緒共享同一塊儲存空間,在帶來方便的同事,也帶來了訪問衝突問題,為了保證資料在方法中被訪問時的正確性,在訪問時加入鎖機制synchronized,當一個執行緒獲得物件的排它鎖,獨佔資源,其他執行緒必須等待,使用後釋放鎖即可,存在以下問題:
    1. 一個執行緒持有鎖會導致其它所有需要此鎖的執行緒掛起;
    2. 在多執行緒競爭下,加鎖,釋放鎖會導致比較多的上下文切換和排程延時,引起效能問題;
    3. 如果一個優先順序高的執行緒等待一個優先順序低的執行緒釋放鎖,會導致優先順序倒置,引起效能問題。
同步塊 Synchronized(Obj){} Obj稱之為同步監視器
  • Obj可以是任何物件,但是推薦使用共享資源作為同步監視器
  • 同步方法中無需指定同步監視器,因為同步方法的同步監視器就是this,就是這個物件本身,或者是class【反射中講解】
同步監視器的執行過程
  1. 第一個執行緒訪問,鎖定同步監視器,執行其中程式碼
  2. 第二個執行緒訪問,發現同步監視器被鎖定,無法訪問
  3. 第一個執行緒訪問完畢,皆出同步監視器
  4. 第二個執行緒訪問,發現同步監視器沒有鎖
死鎖避免方法 產生死鎖的四個必要條件:
  1. 互斥條件:一個資源每次只能被一個程序使用。
  2. 請求與保持條件:一個程序因請求資源而阻塞時,對已獲得的資源保持不妨。
  3. 不剝奪條件:程序已獲得的資源,在未使用完之前,不能強行剝奪。
  4. 迴圈等待條件:若干程序之間形成一種頭尾相接的迴圈等待資源關係。
Lock鎖
  • JDK5.0開始,java提供了更強大的執行緒同步機制——通過顯式定義同步鎖物件來實現同步。同步鎖使用Lock物件充當
  • java.util.concurrent.locks.Lock介面是控制多個執行緒對共享資源進行訪問的工具。鎖提供了對共享資源的獨佔訪問,每次只能有一個執行緒對Lock物件加鎖,執行緒開始訪問共享資源之前應先獲得Lock物件
  • ReentrantLock類實現了Lock,它擁有與synchronized相同的併發性和記憶體語義,在實現執行緒安全的控制中,比較常用的是ReentrantLock,可以顯式加鎖、釋放鎖。
synchronized與Lock的對比
  • Lock是顯式鎖(手動開啟和關閉鎖,別忘記關閉鎖)synchronized是隱式鎖,出了作用域自動釋放
  • Lock只有程式碼塊加鎖,synchronized有程式碼塊鎖和方法鎖
  • 使用Lock鎖,JVM將花費較少的時間來排程執行緒,效能更好。並且具有更好的擴充套件性(提供更多的子類)
  • 優先使用順序:
  • Lock》同步程式碼塊(已經進入了方法體,分配了相應資源)》同步方法(在方法體之外)
public class TestLock {
    public static void main(String[] args) {
        Ticket ticket = new Ticket();
        new Thread(ticket).start();
        new Thread(ticket).start();
        new Thread(ticket).start();
    }

}
class Ticket extends Thread{
    private int ticketNums=10;
    //定義lock鎖
    private final ReentrantLock lock=new ReentrantLock();

    @Override
    public void run() {
        while (true){
            try {
                lock.lock();//加鎖
                if (ticketNums > 0) {
                    try {
                        Thread.sleep(1000);
                    } catch (InterruptedException e) {
                        e.printStackTrace();
                    }
                    System.out.println(ticketNums--);
                } else {
                    break;
                }
            }finally {
                lock.unlock();//減鎖
            }
        }
    }
}

執行緒通訊 應用場景:生產者和消費者問題
  • 假設倉庫中只能存放一件產品,生產者將生產出來的產品放入倉庫,消費者將倉庫中產品取走消費。
  • 如果倉庫中沒有產品,則生產者將產品放入倉庫,否則停止生產並等待,直到倉庫中的產品被消費者取走為止。
  • 如果倉庫中放有產品,則消費者可以將產品取走消費,否則停止消費並等待,直到倉庫中再次放入產品為止。
訊號燈法
package com.xh.dome;

//測試生產者消費者問題:訊號燈法,標誌位解決
public class TestPc {

    public static void main(String[] args) {
        TV tv = new TV();
        new Player(tv).start();
        new Watcher(tv).start();


        String s = new String();
    }
}



//生產者 演員
class Player extends Thread{
    TV tv;
    public Player(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{
    //演員表演的時候觀眾等待 T
    //觀眾觀看時候演員等待 F
    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;
    }
}

執行緒池
  • 背景:經常建立和銷燬、使用量特別大的資源,比如併發情況下的執行緒,對效能影響很大。
  • 思路:提前建立好多個執行緒,放入執行緒池中,使用時直接獲取,使用完放回池中。可以避免頻繁建立銷燬、實現重複利用。類似生活中的公共交通工具。
  • 好處:
    • 提高響應速度(減少了建立新執行緒的時間)
  • 降低資源消耗(重複利用執行緒池中執行緒,不需要每次都建立)
  • 便於執行緒管理(。。。)
    • corePoolSize:核心池的大小
    • maximumPoolSize:最大執行緒數
    • keepAliveTime:執行緒沒有任務時最多保持多長時間後會終止
public class TestPool {
    public static void main(String[] args) {
        //1.建立服務,建立執行緒池
        ExecutorService service= Executors.newFixedThreadPool(10);
        //newFixedThreadPool 引數為:執行緒池大小
        //執行
        service.execute(new MyThread());·
        service.execute(new MyThread());
        service.execute(new MyThread());
        service.execute(new MyThread());
        //2.關閉連線
        service.shutdown();
    }
}
class MyThread implements Runnable{

    @Override
    public void run() {
        System.out.println(Thread.currentThread().getName());
    }
}