亚洲精品中文免费|亚洲日韩中文字幕制服|久久精品亚洲免费|一本之道久久免费

      
      

            <dl id="hur0q"><div id="hur0q"></div></dl>

                JAVA 多線程實(shí)現(xiàn)、線程池創(chuàng)建使用、多線程的異步操作

                目錄

                二、多線程的實(shí)現(xiàn)方式(四種)

                三、線程池的創(chuàng)建使用(五種)

                1、newFixedThreadPool定長線程池

                2、CachedThreadPool可緩存線程池

                3、newSingleThreadExecutor單線程化線程池

                4、newScheduledThreadPool周期性線程池

                無返回值的周期性線程池

                有返回值的周期性線程池

                5、ThreadPoolExecutor(手動創(chuàng)建線程池)

                線程池的submit和execute方法區(qū)別

                一、初認(rèn)多線程

                1、什么是線程

                進(jìn)程是:一個應(yīng)用程序(1個進(jìn)程是一個軟件)。

                線程是:一個進(jìn)程中的執(zhí)行場景/執(zhí)行單元。

                注意: 一個進(jìn)程可以啟動多個線程。

                java主線程: 每個java程序都含有一個線程,那就是主線程(main線程)。Java應(yīng)用程序都是從主類main方法執(zhí)行的,當(dāng)jvm加載代碼,發(fā)現(xiàn)賣弄方法之后,就會啟動一個線程,這個線程就是主線程,負(fù)責(zé)執(zhí)行main方法。如果在主線程里面創(chuàng)建其他線程,就會在主線程和其他線程來回切換,直到其他所有線程結(jié)束才會結(jié)束主線程。

                所謂多線程,就是說一個應(yīng)用程序有多條執(zhí)行路徑,每當(dāng)我們打開一個應(yīng)用程序的時候,就相打開了一個進(jìn)程,而進(jìn)程中執(zhí)行的操作(這就是一條線程對應(yīng)用程序進(jìn)行訪問),就是線程。以迅雷為例,打開迅雷就相當(dāng)于打開一個進(jìn)程,下載文件的操作就是線程,多線程就是同時下載多個文件。

                接口為例,當(dāng)許多人同時調(diào)用一個接口,我們就可以把每一個人看做一條線程去調(diào)用我們的接口。

                二、多線程的實(shí)現(xiàn)方式(四種)

                1、繼承 Thread 類

                通過繼承 Thread 類似實(shí)現(xiàn)多線程的步驟如下:

              1. 創(chuàng)建 MyThread 類,讓其繼承 Thread 類并重寫 run() 方法。
              2. 創(chuàng)建 MyThread 類的實(shí)例對象,即創(chuàng)建一個新線程。
              3. 調(diào)用 start() 方法,啟動線程。
              4. public class MyThread extends Thread { @Override public void run() { System.out.println(“我是通過繼承 Thread 類創(chuàng)建的多線程,我叫” + Thread.currentThread().getName()); }}class TestMyThread { public static void main(String[] args) { MyThread myThread1 = new MyThread(); myThread1.setName(“Thread-1”); MyThread myThread2 = new MyThread(); myThread2.setName(“Thread-2”); MyThread myThread3 = new MyThread(); myThread3.setName(“Thread-3”); myThread1.start(); myThread2.start(); myThread3.start(); }}

                為了演示線程執(zhí)行順序的隨機(jī)性,我特意創(chuàng)建了三個線程,并為每一個線程命名,下面是我運(yùn)行五次程序的執(zhí)行結(jié)果:

                // 第一次我是通過繼承 Thread 類創(chuàng)建的多線程,我叫Thread-2我是通過繼承 Thread 類創(chuàng)建的多線程,我叫Thread-1我是通過繼承 Thread 類創(chuàng)建的多線程,我叫Thread-3// 第二次我是通過繼承 Thread 類創(chuàng)建的多線程,我叫Thread-1我是通過繼承 Thread 類創(chuàng)建的多線程,我叫Thread-3我是通過繼承 Thread 類創(chuàng)建的多線程,我叫Thread-2// 第三次我是通過繼承 Thread 類創(chuàng)建的多線程,我叫Thread-1我是通過繼承 Thread 類創(chuàng)建的多線程,我叫Thread-3我是通過繼承 Thread 類創(chuàng)建的多線程,我叫Thread-2

                從上面的執(zhí)行結(jié)果我們可以看到線程的執(zhí)行順序和代碼中編寫的順序沒有關(guān)系,線程的執(zhí)行順序是具有隨機(jī)性的。

                2、實(shí)現(xiàn) Runnable 接口

                通過實(shí)現(xiàn) Runnable 接口實(shí)現(xiàn)多線程的步驟如下:

                1.創(chuàng)建 MyRunnable 類實(shí)現(xiàn) Runnable 接口。

                2.創(chuàng)建 MyRunnable 類的實(shí)例對象 myRunnable 。

                3.把實(shí)例對象 myRunnable 作為參數(shù)來創(chuàng)建 Thread 類的實(shí)例對象 thread,實(shí)例對象 thread 就是一個新線程。

                4.調(diào)用 start() 方法,啟動線程。、

                public class RunnableTest implements Runnable{ @Override public void run() { for (int i = 0; i < 5; i++) { System.out.println("我是通過實(shí)現(xiàn) Runnable 接口創(chuàng)建的多線程,我叫" + Thread.currentThread().getName()); } } } class Test { public static void main(String[] args) { Thread myThread1 = new Thread(new RunnableTest()); Thread myThread2 = new Thread(new RunnableTest()); myThread1.start(); myThread2.start(); }}

                執(zhí)行結(jié)果如下:

                我是通過實(shí)現(xiàn) Runnable 接口創(chuàng)建的多線程,我叫Thread-1我是通過實(shí)現(xiàn) Runnable 接口創(chuàng)建的多線程,我叫Thread-0我是通過實(shí)現(xiàn) Runnable 接口創(chuàng)建的多線程,我叫Thread-1我是通過實(shí)現(xiàn) Runnable 接口創(chuàng)建的多線程,我叫Thread-0我是通過實(shí)現(xiàn) Runnable 接口創(chuàng)建的多線程,我叫Thread-1我是通過實(shí)現(xiàn) Runnable 接口創(chuàng)建的多線程,我叫Thread-0我是通過實(shí)現(xiàn) Runnable 接口創(chuàng)建的多線程,我叫Thread-1我是通過實(shí)現(xiàn) Runnable 接口創(chuàng)建的多線程,我叫Thread-0我是通過實(shí)現(xiàn) Runnable 接口創(chuàng)建的多線程,我叫Thread-1我是通過實(shí)現(xiàn) Runnable 接口創(chuàng)建的多線程,我叫Thread-0

                注:

                相比于繼承 Thread 類的方法來說,實(shí)現(xiàn) Runnable 接口是一個更好地選擇,因?yàn)?Java 不支持多繼承,但是可以實(shí)現(xiàn)多個接口。

                有一點(diǎn)值得注意的是 Thread 類也實(shí)現(xiàn)了 Runnable 接口,這意味著構(gòu)造函數(shù) Thread(Runnable target) 不僅可以傳入 Runnable 接口的對象,而且可以傳入一個 Thread 類的對象,這樣就可以將一個 Thread 對象中的 run() 方法交由其他線程進(jìn)行調(diào)用。

                3、實(shí)現(xiàn) Callable 接口

                Callable 接口只有一個 call() 方法,源碼如下:

                public interface Callable { V call() throws Exception;}

                從源碼我們可以看到 Callable 接口和 Runnable 接口類似,它們之間的區(qū)別在于 run() 方法沒有返回值,而 call() 方法是有返回值的。

                通過實(shí)現(xiàn) Callable 接口實(shí)現(xiàn)多線程的步驟如下:

                1.創(chuàng)建 MyCallable 類實(shí)現(xiàn) Callable 接口。

                2.創(chuàng)建 MyCallable 類的實(shí)例對象 myCallable。

                3.把實(shí)例對象 myCallable 作為參數(shù)來創(chuàng)建 FutureTask 類的實(shí)例對象 futureTask。

                4.把實(shí)例對象 futureTask 作為參數(shù)來創(chuàng)建 Thread 類的實(shí)例對象 thread,實(shí)例對象 thread 就是一個新線程。

                5.調(diào)用 start() 方法,啟動線程。

                public class CallbaleTest implements Callable { @Override public Integer call() throws Exception { int a = 6; int b = 9; System.out.println(“我是通過實(shí)現(xiàn) Callable 接口創(chuàng)建的多線程,我叫” + Thread.currentThread().getName()); return a + b; } } class TestMyCallable { public static void main(String[] args) throws ExecutionException, InterruptedException { CallbaleTest myCallable = new CallbaleTest(); FutureTask futureTask = new FutureTask(myCallable); Thread thread = new Thread(futureTask); Thread thread1 = new Thread(futureTask); thread.start(); thread1.start(); Integer integer = futureTask.get(); System.out.println(“返回值為:” + integer); } }

                執(zhí)行后的結(jié)果如下:

                我是通過實(shí)現(xiàn) Callable 接口創(chuàng)建的多線程,我叫Thread-0返回值為:15

                注:FutureTask 類提供了一個 get() 方法用來獲取 call() 方法的返回值,但需要注意的是調(diào)用這個方法會導(dǎo)致程序阻塞,必須要等到線程結(jié)束后才會得到返回值。

                4、線程池(下面講)

                三、線程池的創(chuàng)建使用(五種)

                上面講的是通過new Thread等方式創(chuàng)建線程,這種方式的弊端是:

                a. 每次new Thread新建對象性能差。

                b. 線程缺乏統(tǒng)一管理,可能無限制新建線程,相互之間競爭,及可能占用過多系統(tǒng)資源導(dǎo)致死機(jī)或oom。

                c. 缺乏更多功能,如定時執(zhí)行、定期執(zhí)行、線程中斷。

                下面將要介紹的是Jdk提供的四種線程池的好處在于:

                a. 重用存在的線程,減少對象創(chuàng)建、消亡的開銷,性能佳。

                b. 可有效控制最大并發(fā)線程數(shù),提高系統(tǒng)資源的使用率,同時避免過多資源競爭,避免堵塞。

                c. 提供定時執(zhí)行、定期執(zhí)行、單線程、并發(fā)數(shù)控制等功能。

                1、newFixedThreadPool定長線程池

                Executors.newFixedThreadPool:創(chuàng)建一個固定大小的線程池,可控制并發(fā)的線程數(shù),超出的線程會在隊(duì)列中等待。

                import java.util.concurrent.*;public class Test { public static void main(String[] args) { ExecutorService fixedThreadPool = Executors.newFixedThreadPool(2); for (int j = 0; j { for (int i = 0; i < 2; i++) { System.out.println("線程名:" + Thread.currentThread().getName() + " i是:" + i); } }); } fixedThreadPool.shutdown();//關(guān)閉線程池 //shutdownNow();//停止接收新任務(wù),原來的任務(wù)停止執(zhí)行,但是它并不對正在執(zhí)行的任務(wù)做任何保證,有可能它們都會停止,也有可能執(zhí)行完成。 }}

                輸出:

                線程名:pool-1-thread-1 i是:0線程名:pool-1-thread-1 i是:1線程名:pool-1-thread-2 i是:0線程名:pool-1-thread-1 i是:0線程名:pool-1-thread-1 i是:1線程名:pool-1-thread-2 i是:1

                2、CachedThreadPool可緩存線程池

                Executors.newCachedThreadPool:創(chuàng)建一個可緩存的線程池,若線程數(shù)超過處理所需,緩存一段時間后會回收,若線程數(shù)不夠,則新建線程。

                可緩存線程池為無限大,當(dāng)執(zhí)行第二個任務(wù)時第一個任務(wù)已經(jīng)完成,會回收復(fù)用第一個任務(wù)的線程,而不用每次新建線程,可靈活回收空閑線程,若無可回收,則新建線程。

                public class Test { public static void main(String[] args) { ExecutorService cachedThreadPool = Executors.newCachedThreadPool(); for (int j = 0; j { for (int i = 0; i < 2; i++) { System.out.println("線程名:" + Thread.currentThread().getName() + " i是:" + i); } }); } cachedThreadPool.shutdown();//關(guān)閉線程池 }}

                輸出:

                線程名:pool-1-thread-1 i是:0線程名:pool-1-thread-1 i是:1線程名:pool-1-thread-3 i是:0線程名:pool-1-thread-2 i是:0線程名:pool-1-thread-2 i是:1線程名:pool-1-thread-3 i是:1

                使用場景:

                CachedThreadPool 是根據(jù)短時間的任務(wù)量來決定創(chuàng)建的線程數(shù)量的,所以它適合短時間內(nèi)有突發(fā)大量任務(wù)的處理場景。

                3、newSingleThreadExecutor單線程化線程池

                newSingleThreadExecutor線程池你可以理解為特殊的newFixedThreadPool線程池,它只會創(chuàng)建一個線程,并且所有任務(wù)按照指定順序。如果你創(chuàng)建了多個任務(wù),因?yàn)橹粫幸粋€線程,多余的任務(wù)會被阻塞到隊(duì)列里依次執(zhí)行。

                下面的示例循環(huán)3次,每次都是用的一個線程,這個線程會先執(zhí)行第一個循環(huán)的任務(wù),在執(zhí)行第二個循環(huán)的任務(wù),再執(zhí)行第三個循環(huán)的任務(wù),所以輸出的 i 是有序的。

                public class newSingleThreadExecutor { public static void main(String[] args) { ExecutorService singleThreadPool = Executors.newSingleThreadExecutor(); for (int j = 0; j { for (int i = 0; i < 3; i++) { System.out.println("線程名:" + Thread.currentThread().getName() + " i是:" + i); } }); } System.out.println("準(zhǔn)備關(guān)閉線程池"); singleThreadPool.shutdown();//關(guān)閉線程池 } }

                輸出:

                準(zhǔn)備關(guān)閉線程池線程名:pool-1-thread-1 i是:0線程名:pool-1-thread-1 i是:1線程名:pool-1-thread-1 i是:2線程名:pool-1-thread-1 i是:0線程名:pool-1-thread-1 i是:1線程名:pool-1-thread-1 i是:2線程名:pool-1-thread-1 i是:0線程名:pool-1-thread-1 i是:1線程名:pool-1-thread-1 i是:2

                單個線程的線程池有什么意義?

                單個線程的線程池相比于線程來說,它的優(yōu)點(diǎn)有以下 2 個:

                可以復(fù)用線程:即使是單個線程池,也可以復(fù)用線程。

                提供了任務(wù)管理功能:單個線程池也擁有任務(wù)隊(duì)列,在任務(wù)隊(duì)列可以存儲多個任務(wù),這是線程無法實(shí)現(xiàn)的,并且當(dāng)任務(wù)隊(duì)列滿了之后,可以執(zhí)行拒絕策略,這些都是線程不具備的。

                4、newScheduledThreadPool周期性線程池

                周期性線程池用來處理延時任務(wù)或定時任務(wù)。

                無返回值的周期性線程池

                public class newScheduledThreadPool { public static void main(String[] args) { ScheduledExecutorService scheduleThreadPool = Executors.newScheduledThreadPool(3); System.out.println(“測試1”); for (int i = 0; i { System.out.println(“線程名:” + Thread.currentThread().getName() + “已經(jīng)過了3秒”); }, 3, TimeUnit.SECONDS); } System.out.println(“測試2”); scheduleThreadPool.shutdown();//關(guān)閉線程池 } }

                說明:

                我們聲明了3個線程,創(chuàng)建的時候用循環(huán)創(chuàng)建了5個,多出來的2個會阻塞直到前3個線程有執(zhí)行完的再復(fù)用他們的線程;因?yàn)椴捎昧搜訒r3秒輸出,所以會先輸出測試1、測試2,然后等待3秒后再執(zhí)行輸出線程的內(nèi)容。

                輸出:

                測試1測試2線程名:pool-1-thread-2已經(jīng)過了3秒線程名:pool-1-thread-3已經(jīng)過了3秒線程名:pool-1-thread-1已經(jīng)過了3秒線程名:pool-1-thread-3已經(jīng)過了3秒線程名:pool-1-thread-2已經(jīng)過了3秒

                有返回值的周期性線程池

                public static void main(String[] args) { ScheduledExecutorService scheduleThreadPool = Executors.newScheduledThreadPool(3); System.out.println(“測試1”); ScheduledFuture scheduledFuture = scheduleThreadPool.schedule(() -> { return “線程名:” + Thread.currentThread().getName() + “已經(jīng)過了3秒”; }, 3, TimeUnit.SECONDS); System.out.println(“測試2”); try { //獲取線程返回的值并輸出 System.out.println(scheduledFuture.get()); } catch (InterruptedException e) { e.printStackTrace(); } catch (ExecutionException e) { e.printStackTrace(); } scheduleThreadPool.shutdown();//關(guān)閉線程池 } }

                輸出:

                測試1測試2線程名:pool-1-thread-1已經(jīng)過了3秒

                定時線程執(zhí)行

                定時執(zhí)行可以用scheduleAtFixedRate方法進(jìn)行操作,里面的參數(shù)4表示代碼或啟動運(yùn)行后第4秒開始執(zhí)行,3表示每3秒執(zhí)行一次。因?yàn)槲覀冊O(shè)置了3個線程,所以運(yùn)行后線程會在第4秒開始用3個線程每3秒執(zhí)行一次。

                public static void main(String[] args) { ScheduledExecutorService scheduleThreadPool = Executors.newScheduledThreadPool(3); System.out.println(“測試1”); scheduleThreadPool.scheduleAtFixedRate(() -> { System.out.println(“線程名:” + Thread.currentThread().getName() + “已經(jīng)過了3秒”); }, 4, 3, TimeUnit.SECONDS); System.out.println(“測試2”); }

                輸出:

                測試1測試2線程名:pool-1-thread-1已經(jīng)過了3秒線程名:pool-1-thread-1已經(jīng)過了3秒線程名:pool-1-thread-2已經(jīng)過了3秒線程名:pool-1-thread-3已經(jīng)過了3秒……

                5、ThreadPoolExecutor(手動創(chuàng)建線程池)

                上面我們介紹了四種JDK自帶的線程池,但是平常不推薦使用。

                ThreadPoolExecutor 相比于其他創(chuàng)建線程池的優(yōu)勢在于,它可以通過參數(shù)來控制最大任務(wù)數(shù)和拒絕策略,讓線程池的執(zhí)行更加透明和可控,所以在阿里巴巴《Java開發(fā)手冊》是這樣規(guī)定的:

                【強(qiáng)制要求】線程池不允許使用 Executors 去創(chuàng)建,而是通過 ThreadPoolExecutor 的方式,這樣的處理方式讓寫的同學(xué)更加明確線程池的運(yùn)行規(guī)則,規(guī)避資源耗盡的風(fēng)險。

                這一方面是由于jdk中自帶的線程池,都有其局限性,不夠靈活;另外使用ThreadPoolExecutor有助于大家明確線程池的運(yùn)行規(guī)則,創(chuàng)建符合自己的業(yè)務(wù)場景需要的線程池,避免資源耗盡的風(fēng)險。

                需要進(jìn)行線程池的初始化,所以引入以下依賴:

                com.google.guava guava 29.0-jre

                在開始前需要注意線程池的幾個參數(shù):

                (在下面代碼的ThreadPoolExecutor里你會看到這些參數(shù)):

                corePoolSize=> 線程池里的核心線程數(shù)量maximumPoolSize=> 線程池里允許有的最大線程數(shù)量keepAliveTime=> 空閑線程存活時間unit=> keepAliveTime的時間單位,比如分鐘,小時等workQueue=> 緩沖隊(duì)列threadFactory=> 線程工廠用來創(chuàng)建新的線程放入線程池handler=> 線程池拒絕任務(wù)的處理策略,比如拋出異常等策略線程池按以下行為執(zhí)行任務(wù) 1. 當(dāng)線程數(shù)小于核心線程數(shù)時,創(chuàng)建線程。 2. 當(dāng)線程數(shù)大于等于核心線程數(shù),且任務(wù)隊(duì)列未滿時,將任務(wù)放入任務(wù)隊(duì)列。 3. 當(dāng)線程數(shù)大于等于核心線程數(shù),且任務(wù)隊(duì)列已滿 -1 若線程數(shù)小于最大線程數(shù),創(chuàng)建線程 -2 若線程數(shù)等于最大線程數(shù),拋出異常,拒絕任務(wù)

                無返回值的線程創(chuàng)建

                代碼初始化了線程池并用 executorService.execute 分別創(chuàng)建了兩個線程,一個用來輸出本線程的名字,另一個用來異步調(diào)用 printA() 方法。

                public static void main(String[] args) { System.out.println(“開始”); //線程池的初始化 ThreadFactory namedThreadFactory = new ThreadFactoryBuilder().setNameFormat(“demo-pool-%d”).build(); ExecutorService executorService = new ThreadPoolExecutor( 60, 100, 0L, TimeUnit.MILLISECONDS, new LinkedBlockingDeque(1024), namedThreadFactory, new ThreadPoolExecutor.AbortPolicy()); //開啟一個新線程用來輸出線程的名字 executorService.execute(() -> System.out.println(“第1個線程名字” + Thread.currentThread().getName())); //再開啟一個新線執(zhí)行printA() executorService.execute(() -> { System.out.println(“第2個線程名字” + Thread.currentThread().getName()); printA(); }); System.out.println(“完成”); executorService.shutdown(); } public static void printA() { for (int i = 0; i < 3; i++) { System.out.println("打?。篴aaaaaaaaaaaa"); } }

                輸出:

                開始完成第1個線程名字demo-pool-0第2個線程名字demo-pool-1打?。篴aaaaaaaaaaaa打?。篴aaaaaaaaaaaa打?。篴aaaaaaaaaaaa

                有返回值的多線程調(diào)用

                使用submit

                public static void main(String[] args) { System.out.println(“開始”); //線程池的初始化 ThreadFactory namedThreadFactory = new ThreadFactoryBuilder().setNameFormat(“demo-pool-%d”).build(); ThreadPoolExecutor threadPoolExecutor = new ThreadPoolExecutor(5, 10, 0L, TimeUnit.MILLISECONDS, new LinkedBlockingDeque(1024), namedThreadFactory, new ThreadPoolExecutor.AbortPolicy()); //異步調(diào)用對象integerCallableTask中的call()計算1-100的和 Future future = threadPoolExecutor.submit(() -> { int nummber = 100; int sum = 0; for (int i = 0; i <= nummber; i++) { sum += i; } return sum; }); try { //獲取計算的結(jié)果 Integer result = future.get(); System.out.println("和是:" + result); } catch (InterruptedException e) { e.printStackTrace(); } catch (ExecutionException e) { e.printStackTrace(); } System.out.println("完成"); //shutdown():停止接收新任務(wù),原來的任務(wù)繼續(xù)執(zhí)行 //shutdownNow():停止接收新任務(wù),原來的任務(wù)停止執(zhí)行 threadPoolExecutor.shutdown(); }

                輸出:

                開始和是:5050完成

                線程池的submit和execute方法區(qū)別

                1、接收的參數(shù)不一樣

                execute接收的參數(shù)是new Runnable(),重寫run()方法,是沒有返回值的:

                源碼:

                public interface Executor { /** * Executes the given command at some time in the future. The command * may execute in a new thread, in a pooled thread, or in the calling * thread, at the discretion of the {@code Executor} implementation. * * @param command the runnable task * @throws RejectedExecutionException if this task cannot be * accepted for execution * @throws NullPointerException if command is null */ void execute(Runnable command);}

                submit接收的參數(shù)是Callable,重寫call()方法,是有返回值的:

                源碼:

                /** * @throws RejectedExecutionException {@inheritDoc} * @throws NullPointerException {@inheritDoc} */ public Future submit(Callable task) { if (task == null) throw new NullPointerException(); RunnableFuture ftask = newTaskFor(task); execute(ftask); return ftask; }

                2、submit有返回值用于返回多線程計算后的值,而execute沒有返回值

                鄭重聲明:本文內(nèi)容及圖片均整理自互聯(lián)網(wǎng),不代表本站立場,版權(quán)歸原作者所有,如有侵權(quán)請聯(lián)系管理員(admin#wlmqw.com)刪除。
                用戶投稿
                上一篇 2022年7月4日 06:39
                下一篇 2022年7月4日 06:39

                相關(guān)推薦

                聯(lián)系我們

                聯(lián)系郵箱:admin#wlmqw.com
                工作時間:周一至周五,10:30-18:30,節(jié)假日休息