日韩性视频-久久久蜜桃-www中文字幕-在线中文字幕av-亚洲欧美一区二区三区四区-撸久久-香蕉视频一区-久久无码精品丰满人妻-国产高潮av-激情福利社-日韩av网址大全-国产精品久久999-日本五十路在线-性欧美在线-久久99精品波多结衣一区-男女午夜免费视频-黑人极品ⅴideos精品欧美棵-人人妻人人澡人人爽精品欧美一区-日韩一区在线看-欧美a级在线免费观看

歡迎訪問 生活随笔!

生活随笔

當前位置: 首頁 > 编程语言 > java >内容正文

java

【Java】线程池、Lambda表达式

發布時間:2024/3/13 java 39 豆豆
生活随笔 收集整理的這篇文章主要介紹了 【Java】线程池、Lambda表达式 小編覺得挺不錯的,現在分享給大家,幫大家做個參考.

🔎這里是【Java】,關注我學習Java不迷路
👍如果對你有幫助,給博主一個免費的點贊以示鼓勵
歡迎各位🔎點贊👍評論收藏??

👀專欄介紹

【Java】 目前主要更新Java,一起學習一起進步。

👀本期介紹

本期主要介紹線程池、Lambda表達式

文章目錄

文章目錄

1.1 線程間通信

1.2 等待喚醒機制

1.3 生產者與消費者問題

第二章 線程池

2.1 線程池思想概述

2.2 線程池概念

2.3 線程池的使用

第三章 Lambda表達式

3.1 函數式編程思想概述

3.2 冗余的Runnable代碼

傳統寫法

代碼分析

3.3 編程思想轉換

做什么,而不是怎么做

生活舉例

3.4 體驗Lambda的更優寫法

3.5 回顧匿名內部類

使用實現類

使用匿名內部類

匿名內部類的好處與弊端

語義分析

3.6 Lambda標準格式

3.7 練習:使用Lambda標準格式(無參無返回)

題目

解答

3.8 Lambda的參數和返回值

傳統寫法

代碼分析

Lambda寫法

3.9 練習:使用Lambda標準格式(有參有返回)

題目

解答

3.10 Lambda省略格式

可推導即可省略

省略規則

3.11 練習:使用Lambda省略格式

題目

解答

3.12 Lambda的使用前提

1.1 線程間通信

概念:多個線程在處理同一個資源,但是處理的動作(線程的任務)卻不相同。

比如:線程A用來生成包子的,線程B用來吃包子的,包子可以理解為同一資源,線程A與線程B處理的動作,一個是生產,一個是消費,那么線程A與線程B之間就存在線程通信問題。

為什么要處理線程間通信:

多個線程并發執行時, 在默認情況下CPU是隨機切換線程的,當我們需要多個線程來共同完成一件任務,并且我們希望他們有規律的執行, 那么多線程之間需要一些協調通信,以此來幫我們達到多線程共同操作一份數據。

如何保證線程間通信有效利用資源:

多個線程在處理同一個資源,并且任務不同時,需要線程通信來幫助解決線程之間對同一個變量的使用或操作。 就是多個線程在操作同一份數據時, 避免對同一共享變量的爭奪。也就是我們需要通過一定的手段使各個線程能有效的利用資源。而這種手段即—— 等待喚醒機制。

1.2 等待喚醒機制

什么是等待喚醒機制

這是多個線程間的一種協作機制。談到線程我們經常想到的是線程間的競爭(race),比如去爭奪鎖,但這并不是故事的全部,線程間也會有協作機制。就好比在公司里你和你的同事們,你們可能存在在晉升時的競爭,但更多時候你們更多是一起合作以完成某些任務。

就是在一個線程進行了規定操作后,就進入等待狀態(wait()), 等待其他線程執行完他們的指定代碼過后 再將其喚醒(notify());在有多個線程進行等待時, 如果需要,可以使用 notifyAll()來喚醒所有的等待線程。

wait/notify 就是線程間的一種協作機制。

等待喚醒中的方法

等待喚醒機制就是用于解決線程間通信的問題的,使用到的3個方法的含義如下:

  • wait:線程不再活動,不再參與調度,進入 wait set 中,因此不會浪費 CPU 資源,也不會去競爭鎖了,這時的線程狀態即是 WAITING。它還要等著別的線程執行一個特別的動作,也即是“通知(notify)”在這個對象上等待的線程從wait set 中釋放出來,重新進入到調度隊列(ready queue)中

  • notify:則選取所通知對象的 wait set 中的一個線程釋放;例如,餐館有空位置后,等候就餐最久的顧客最先入座。

  • notifyAll:則釋放所通知對象的 wait set 上的全部線程。

  • 注意:

    哪怕只通知了一個等待的線程,被通知線程也不能立即恢復執行,因為它當初中斷的地方是在同步塊內,而此刻它已經不持有鎖,所以她需要再次嘗試去獲取鎖(很可能面臨其它線程的競爭),成功后才能在當初調用 wait 方法之后的地方恢復執行。

    總結如下:

    • 如果能獲取鎖,線程就從 WAITING 狀態變成 RUNNABLE 狀態;

    • 否則,從 wait set 出來,又進入 entry set,線程就從 WAITING 狀態又變成 BLOCKED 狀態

    調用wait和notify方法需要注意的細節

  • wait方法與notify方法必須要由同一個鎖對象調用。因為:對應的鎖對象可以通過notify喚醒使用同一個鎖對象調用的wait方法后的線程。

  • wait方法與notify方法是屬于Object類的方法的。因為:鎖對象可以是任意對象,而任意對象的所屬類都是繼承了Object類的。

  • wait方法與notify方法必須要在同步代碼塊或者是同步函數中使用。因為:必須要通過鎖對象調用這2個方法。

  • 1.3 生產者與消費者問題

    等待喚醒機制其實就是經典的“生產者與消費者”的問題。

    就拿生產包子消費包子來說等待喚醒機制如何有效利用資源:

    包子鋪線程生產包子,吃貨線程消費包子。當包子沒有時(包子狀態為false),吃貨線程等待,包子鋪線程生產包子(即包子狀態為true),并通知吃貨線程(解除吃貨的等待狀態),因為已經有包子了,那么包子鋪線程進入等待狀態。接下來,吃貨線程能否進一步執行則取決于鎖的獲取情況。如果吃貨獲取到鎖,那么就執行吃包子動作,包子吃完(包子狀態為false),并通知包子鋪線程(解除包子鋪的等待狀態),吃貨線程進入等待。包子鋪線程能否進一步執行則取決于鎖的獲取情況。

    代碼演示:

    包子資源類:

    public class BaoZi {String ?pier ;String ?xianer ;boolean ?flag = false ;//包子資源 是否存在 包子資源狀態 }

    吃貨線程類:

    public class ChiHuo extends Thread{private BaoZi bz; ?public ChiHuo(String name,BaoZi bz){super(name);this.bz = bz;}@Overridepublic void run() {while(true){synchronized (bz){if(bz.flag == false){//沒包子try {bz.wait();} catch (InterruptedException e) {e.printStackTrace();}}System.out.println("吃貨正在吃"+bz.pier+bz.xianer+"包子");bz.flag = false;bz.notify();}}} }

    包子鋪線程類:

    public class BaoZiPu extends Thread { ?private BaoZi bz; ?public BaoZiPu(String name,BaoZi bz){super(name);this.bz = bz;} ?@Overridepublic void run() {int count = 0;//造包子while(true){//同步synchronized (bz){if(bz.flag == true){//包子資源 存在try { ?bz.wait(); ?} catch (InterruptedException e) {e.printStackTrace();}} ?// 沒有包子 造包子System.out.println("包子鋪開始做包子");if(count%2 == 0){// 冰皮 五仁bz.pier = "冰皮";bz.xianer = "五仁";}else{// 薄皮 牛肉大蔥bz.pier = "薄皮";bz.xianer = "牛肉大蔥";}count++; ?bz.flag=true;System.out.println("包子造好了:"+bz.pier+bz.xianer);System.out.println("吃貨來吃吧");//喚醒等待線程 (吃貨)bz.notify();}}} }

    測試類:

    public class Demo {public static void main(String[] args) {//等待喚醒案例BaoZi bz = new BaoZi(); ?ChiHuo ch = new ChiHuo("吃貨",bz);BaoZiPu bzp = new BaoZiPu("包子鋪",bz); ?ch.start();bzp.start();} }

    執行效果:

    包子鋪開始做包子 包子造好了:冰皮五仁 吃貨來吃吧 吃貨正在吃冰皮五仁包子 包子鋪開始做包子 包子造好了:薄皮牛肉大蔥 吃貨來吃吧 吃貨正在吃薄皮牛肉大蔥包子 包子鋪開始做包子 包子造好了:冰皮五仁 吃貨來吃吧 吃貨正在吃冰皮五仁包子

    第二章 線程池

    2.1 線程池思想概述

    我們使用線程的時候就去創建一個線程,這樣實現起來非常簡便,但是就會有一個問題:

    如果并發的線程數量很多,并且每個線程都是執行一個時間很短的任務就結束了,這樣頻繁創建線程就會大大降低系統的效率,因為頻繁創建線程和銷毀線程需要時間。

    那么有沒有一種辦法使得線程可以復用,就是執行完一個任務,并不被銷毀,而是可以繼續執行其他的任務?

    在Java中可以通過線程池來達到這樣的效果。今天我們就來詳細講解一下Java的線程池。

    2.2 線程池概念

    • 線程池:其實就是一個容納多個線程的容器,其中的線程可以反復使用,省去了頻繁創建線程對象的操作,無需反復創建線程而消耗過多資源。

    由于線程池中有很多操作都是與優化資源相關的,我們在這里就不多贅述。我們通過一張圖來了解線程池的工作原理:

    合理利用線程池能夠帶來三個好處:

  • 降低資源消耗。減少了創建和銷毀線程的次數,每個工作線程都可以被重復利用,可執行多個任務。

  • 提高響應速度。當任務到達時,任務可以不需要的等到線程創建就能立即執行。

  • 提高線程的可管理性。可以根據系統的承受能力,調整線程池中工作線線程的數目,防止因為消耗過多的內存,而把服務器累趴下(每個線程需要大約1MB內存,線程開的越多,消耗的內存也就越大,最后死機)。

  • 2.3 線程池的使用

    Java里面線程池的頂級接口是java.util.concurrent.Executor,但是嚴格意義上講Executor并不是一個線程池,而只是一個執行線程的工具。真正的線程池接口是java.util.concurrent.ExecutorService。

    要配置一個線程池是比較復雜的,尤其是對于線程池的原理不是很清楚的情況下,很有可能配置的線程池不是較優的,因此在java.util.concurrent.Executors線程工廠類里面提供了一些靜態工廠,生成一些常用的線程池。官方建議使用Executors工程類來創建線程池對象。

    Executors類中有個創建線程池的方法如下:

    • public static ExecutorService newFixedThreadPool(int nThreads):返回線程池對象。(創建的是有界線程池,也就是池中的線程個數可以指定最大數量)

    獲取到了一個線程池ExecutorService 對象,那么怎么使用呢,在這里定義了一個使用線程池對象的方法如下:

    • public Future<?> submit(Runnable task):獲取線程池中的某一個線程對象,并執行

      Future接口:用來記錄線程任務執行完畢后產生的結果。線程池創建與使用。

    使用線程池中線程對象的步驟:

  • 創建線程池對象。

  • 創建Runnable接口子類對象。(task)

  • 提交Runnable接口子類對象。(take task)

  • 關閉線程池(一般不做)。

  • Runnable實現類代碼:

    public class MyRunnable implements Runnable {@Overridepublic void run() {System.out.println("我要一個教練");try {Thread.sleep(2000);} catch (InterruptedException e) {e.printStackTrace();}System.out.println("教練來了: " + Thread.currentThread().getName());System.out.println("教我游泳,交完后,教練回到了游泳池");} }

    線程池測試類:

    public class ThreadPoolDemo {public static void main(String[] args) {// 創建線程池對象ExecutorService service = Executors.newFixedThreadPool(2);//包含2個線程對象// 創建Runnable實例對象MyRunnable r = new MyRunnable(); ?//自己創建線程對象的方式// Thread t = new Thread(r);// t.start(); ---> 調用MyRunnable中的run() ?// 從線程池中獲取線程對象,然后調用MyRunnable中的run()service.submit(r);// 再獲取個線程對象,調用MyRunnable中的run()service.submit(r);service.submit(r);// 注意:submit方法調用結束后,程序并不終止,是因為線程池控制了線程的關閉。// 將使用完的線程又歸還到了線程池中// 關閉線程池//service.shutdown();} }

    第三章 Lambda表達式

    3.1 函數式編程思想概述

    在數學中,函數就是有輸入量、輸出量的一套計算方案,也就是“拿什么東西做什么事情”。相對而言,面向對象過分強調“必須通過對象的形式來做事情”,而函數式思想則盡量忽略面向對象的復雜語法——強調做什么,而不是以什么形式做

    面向對象的思想:

    做一件事情,找一個能解決這個事情的對象,調用對象的方法,完成事情.

    函數式編程思想:

    只要能獲取到結果,誰去做的,怎么做的都不重要,重視的是結果,不重視過程

    3.2 冗余的Runnable代碼

    傳統寫法

    當需要啟動一個線程去完成任務時,通常會通過java.lang.Runnable接口來定義任務內容,并使用java.lang.Thread類來啟動該線程。代碼如下:

    public class Demo01Runnable {public static void main(String[] args) {// 匿名內部類Runnable task = new Runnable() {@Overridepublic void run() { // 覆蓋重寫抽象方法System.out.println("多線程任務執行!");}};new Thread(task).start(); // 啟動線程} }

    本著“一切皆對象”的思想,這種做法是無可厚非的:首先創建一個Runnable接口的匿名內部類對象來指定任務內容,再將其交給一個線程來啟動。

    代碼分析

    對于Runnable的匿名內部類用法,可以分析出幾點內容:

    • Thread類需要Runnable接口作為參數,其中的抽象run方法是用來指定線程任務內容的核心;

    • 為了指定run的方法體,不得不需要Runnable接口的實現類;

    • 為了省去定義一個RunnableImpl實現類的麻煩,不得不使用匿名內部類;

    • 必須覆蓋重寫抽象run方法,所以方法名稱、方法參數、方法返回值不得不再寫一遍,且不能寫錯;

    • 而實際上,似乎只有方法體才是關鍵所在

    3.3 編程思想轉換

    做什么,而不是怎么做

    我們真的希望創建一個匿名內部類對象嗎?不。我們只是為了做這件事情而不得不創建一個對象。我們真正希望做的事情是:將run方法體內的代碼傳遞給Thread類知曉。

    傳遞一段代碼——這才是我們真正的目的。而創建對象只是受限于面向對象語法而不得不采取的一種手段方式。那,有沒有更加簡單的辦法?如果我們將關注點從“怎么做”回歸到“做什么”的本質上,就會發現只要能夠更好地達到目的,過程與形式其實并不重要。

    生活舉例

    當我們需要從北京到上海時,可以選擇高鐵、汽車、騎行或是徒步。我們的真正目的是到達上海,而如何才能到達上海的形式并不重要,所以我們一直在探索有沒有比高鐵更好的方式——搭乘飛機。

    而現在這種飛機(甚至是飛船)已經誕生:2014年3月Oracle所發布的Java 8(JDK 1.8)中,加入了Lambda表達式的重量級新特性,為我們打開了新世界的大門。

    3.4 體驗Lambda的更優寫法

    借助Java 8的全新語法,上述Runnable接口的匿名內部類寫法可以通過更簡單的Lambda表達式達到等效:

    public class Demo02LambdaRunnable {public static void main(String[] args) {new Thread(() -> System.out.println("多線程任務執行!")).start(); // 啟動線程} }

    這段代碼和剛才的執行效果是完全一樣的,可以在1.8或更高的編譯級別下通過。從代碼的語義中可以看出:我們啟動了一個線程,而線程任務的內容以一種更加簡潔的形式被指定。

    不再有“不得不創建接口對象”的束縛,不再有“抽象方法覆蓋重寫”的負擔,就是這么簡單!

    3.5 回顧匿名內部類

    Lambda是怎樣擊敗面向對象的?在上例中,核心代碼其實只是如下所示的內容:

    () -> System.out.println("多線程任務執行!")

    為了理解Lambda的語義,我們需要從傳統的代碼起步。

    使用實現類

    要啟動一個線程,需要創建一個Thread類的對象并調用start方法。而為了指定線程執行的內容,需要調用Thread類的構造方法:

    • public Thread(Runnable target)

    為了獲取Runnable接口的實現對象,可以為該接口定義一個實現類RunnableImpl:

    public class RunnableImpl implements Runnable {@Overridepublic void run() {System.out.println("多線程任務執行!");} }

    然后創建該實現類的對象作為Thread類的構造參數:

    public class Demo03ThreadInitParam {public static void main(String[] args) {Runnable task = new RunnableImpl();new Thread(task).start();} }

    使用匿名內部類

    這個RunnableImpl類只是為了實現Runnable接口而存在的,而且僅被使用了唯一一次,所以使用匿名內部類的語法即可省去該類的單獨定義,即匿名內部類:

    public class Demo04ThreadNameless {public static void main(String[] args) {new Thread(new Runnable() {@Overridepublic void run() {System.out.println("多線程任務執行!");}}).start();} }

    匿名內部類的好處與弊端

    一方面,匿名內部類可以幫我們省去實現類的定義;另一方面,匿名內部類的語法——確實太復雜了!

    語義分析

    仔細分析該代碼中的語義,Runnable接口只有一個run方法的定義:

    • public abstract void run();

    即制定了一種做事情的方案(其實就是一個函數):

    • 無參數:不需要任何條件即可執行該方案。

    • 無返回值:該方案不產生任何結果。

    • 代碼塊(方法體):該方案的具體執行步驟。

    同樣的語義體現在Lambda語法中,要更加簡單:

    () -> System.out.println("多線程任務執行!")
    • 前面的一對小括號即run方法的參數(無),代表不需要任何條件;

    • 中間的一個箭頭代表將前面的參數傳遞給后面的代碼;

    • 后面的輸出語句即業務邏輯代碼。

    3.6 Lambda標準格式

    Lambda省去面向對象的條條框框,格式由3個部分組成:

    • 一些參數

    • 一個箭頭

    • 一段代碼

    Lambda表達式的標準格式為:

    (參數類型 參數名稱) -> { 代碼語句 }

    格式說明:

    • 小括號內的語法與傳統方法參數列表一致:無參數則留空;多個參數則用逗號分隔。

    • ->是新引入的語法格式,代表指向動作。

    • 大括號內的語法與傳統方法體要求基本一致。

    3.7 練習:使用Lambda標準格式(無參無返回)

    題目

    給定一個廚子Cook接口,內含唯一的抽象方法makeFood,且無參數、無返回值。如下:

    public interface Cook {void makeFood(); }

    在下面的代碼中,請使用Lambda的標準格式調用invokeCook方法,打印輸出“吃飯啦!”字樣:

    public class Demo05InvokeCook {public static void main(String[] args) {// TODO 請在此使用Lambda【標準格式】調用invokeCook方法}private static void invokeCook(Cook cook) {cook.makeFood();} }

    解答

    public static void main(String[] args) {invokeCook(() -> {System.out.println("吃飯啦!");}); }

    備注:小括號代表Cook接口makeFood抽象方法的參數為空,大括號代表makeFood的方法體。

    3.8 Lambda的參數和返回值

    需求:使用數組存儲多個Person對象對數組中的Person對象使用Arrays的sort方法通過年齡進行升序排序

    下面舉例演示java.util.Comparator<T>接口的使用場景代碼,其中的抽象方法定義為:

    • public abstract int compare(T o1, T o2);

    當需要對一個對象數組進行排序時,Arrays.sort方法需要一個Comparator接口實例來指定排序的規則。假設有一個Person類,含有String name和int age兩個成員變量:

    public class Person { private String name;private int age;// 省略構造器、toString方法與Getter Setter }

    傳統寫法

    如果使用傳統的代碼對Person[]數組進行排序,寫法如下:

    import java.util.Arrays; import java.util.Comparator;public class Demo06Comparator {public static void main(String[] args) {// 本來年齡亂序的對象數組Person[] array = {new Person("古力娜扎", 19),new Person("迪麗熱巴", 18),new Person("馬爾扎哈", 20) };// 匿名內部類Comparator<Person> comp = new Comparator<Person>() {@Overridepublic int compare(Person o1, Person o2) {return o1.getAge() - o2.getAge();}};Arrays.sort(array, comp); // 第二個參數為排序規則,即Comparator接口實例for (Person person : array) {System.out.println(person);}} }

    這種做法在面向對象的思想中,似乎也是“理所當然”的。其中Comparator接口的實例(使用了匿名內部類)代表了“按照年齡從小到大”的排序規則。

    代碼分析

    下面我們來搞清楚上述代碼真正要做什么事情。

    • 為了排序,Arrays.sort方法需要排序規則,即Comparator接口的實例,抽象方法compare是關鍵;

    • 為了指定compare的方法體,不得不需要Comparator接口的實現類;

    • 為了省去定義一個ComparatorImpl實現類的麻煩,不得不使用匿名內部類;

    • 必須覆蓋重寫抽象compare方法,所以方法名稱、方法參數、方法返回值不得不再寫一遍,且不能寫錯;

    • 實際上,只有參數和方法體才是關鍵

    Lambda寫法

    import java.util.Arrays;public class Demo07ComparatorLambda {public static void main(String[] args) {Person[] array = {new Person("古力娜扎", 19),new Person("迪麗熱巴", 18),new Person("馬爾扎哈", 20) };Arrays.sort(array, (Person a, Person b) -> {return a.getAge() - b.getAge();});for (Person person : array) {System.out.println(person);}} }

    3.9 練習:使用Lambda標準格式(有參有返回)

    題目

    給定一個計算器Calculator接口,內含抽象方法calc可以將兩個int數字相加得到和值:

    public interface Calculator {int calc(int a, int b); }

    在下面的代碼中,請使用Lambda的標準格式調用invokeCalc方法,完成120和130的相加計算:

    public class Demo08InvokeCalc {public static void main(String[] args) {// TODO 請在此使用Lambda【標準格式】調用invokeCalc方法來計算120+130的結果?}private static void invokeCalc(int a, int b, Calculator calculator) {int result = calculator.calc(a, b);System.out.println("結果是:" + result);} }

    解答

    public static void main(String[] args) {invokeCalc(120, 130, (int a, int b) -> {return a + b;}); }

    備注:小括號代表Calculator接口calc抽象方法的參數,大括號代表calc的方法體。

    3.10 Lambda省略格式

    可推導即可省略

    Lambda強調的是“做什么”而不是“怎么做”,所以凡是可以根據上下文推導得知的信息,都可以省略。例如上例還可以使用Lambda的省略寫法:

    public static void main(String[] args) {invokeCalc(120, 130, (a, b) -> a + b); }

    省略規則

    在Lambda標準格式的基礎上,使用省略寫法的規則為:

  • 小括號內參數的類型可以省略;

  • 如果小括號內有且僅有一個參,則小括號可以省略;

  • 如果大括號內有且僅有一個語句,則無論是否有返回值,都可以省略大括號、return關鍵字及語句分號。

  • 備注:掌握這些省略規則后,請對應地回顧本章開頭的多線程案例。

    3.11 練習:使用Lambda省略格式

    題目

    仍然使用前文含有唯一makeFood抽象方法的廚子Cook接口,在下面的代碼中,請使用Lambda的省略格式調用invokeCook方法,打印輸出“吃飯啦!”字樣:

    public class Demo09InvokeCook {public static void main(String[] args) {// TODO 請在此使用Lambda【省略格式】調用invokeCook方法}private static void invokeCook(Cook cook) {cook.makeFood();} }

    解答

    public static void main(String[] args) {invokeCook(() -> System.out.println("吃飯啦!")); }

    3.12 Lambda的使用前提

    Lambda的語法非常簡潔,完全沒有面向對象復雜的束縛。但是使用時有幾個問題需要特別注意:

  • 使用Lambda必須具有接口,且要求接口中有且僅有一個抽象方法。 無論是JDK內置的Runnable、Comparator接口還是自定義的接口,只有當接口中的抽象方法存在且唯一時,才可以使用Lambda。

  • 使用Lambda必須具有上下文推斷。 也就是方法的參數或局部變量類型必須為Lambda對應的接口類型,才能使用Lambda作為該接口的實例。

  • 備注:有且僅有一個抽象方法的接口,稱為“函數式接口”。

    總結

    以上是生活随笔為你收集整理的【Java】线程池、Lambda表达式的全部內容,希望文章能夠幫你解決所遇到的問題。

    如果覺得生活随笔網站內容還不錯,歡迎將生活随笔推薦給好友。