徹底搞懂Java多線程(三)
Java線程池
線程的缺點(diǎn):
1.線程的創(chuàng)建它會(huì)開辟本地方法棧、JVM棧、程序計(jì)數(shù)器私有的內(nèi)存,同時(shí)消耗的時(shí)候需要銷毀以上三個(gè)區(qū)域,因此頻繁的創(chuàng)建和銷毀線程比較消耗系統(tǒng)的資源。
2.在任務(wù)量遠(yuǎn)遠(yuǎn)大于線程可以處理的任務(wù)量的時(shí)候,不能很好的拒絕任務(wù)。
所以就有了線程池:
使用池化的而技術(shù)來管理和使用線程。
線程池的優(yōu)點(diǎn)
1.可以避免頻繁的創(chuàng)建和銷毀線程
2.可以更好的管理線程的個(gè)數(shù)和資源的個(gè)數(shù)。
3.線程池?fù)碛懈嗟墓δ埽热缇€程池可以進(jìn)行定時(shí)任務(wù)的執(zhí)行。
4.線程池可以更友好的拒絕不能處理的任務(wù)。
線程池的6種創(chuàng)建方式
一共有7種創(chuàng)建方式
創(chuàng)建方式一:
創(chuàng)建固定個(gè)數(shù)的線程池:
package ThreadPoolDemo;
import java.util.concurrent.ExecutorService;
import java.util.concurrent.Executors;
/**
* user:ypc;
* date:2021-06-13;
* time: 10:24;
*/
public class ThreadPoolDemo1 {
public static void main(String[] args) {
//創(chuàng)建一個(gè)固定個(gè)數(shù)的線程池
ExecutorService executorService = Executors.newFixedThreadPool(10);
//執(zhí)行任務(wù)
for (int i = 0; i < 10; i++) {
executorService.execute(new Runnable() {
@Override
public void run() {
System.out.println("線程名" + Thread.currentThread().getName());
}
});
}
}
}

那么如果執(zhí)行次數(shù)大于10次呢?
線程池不會(huì)創(chuàng)建新的線程,它會(huì)復(fù)用之前的線程。


那么如果只執(zhí)行兩個(gè)任務(wù)呢?它創(chuàng)建了是10個(gè)線程還是兩個(gè)線程呢?
我們可以使用Jconsole來看一看:


結(jié)果是只有2個(gè)線程被創(chuàng)建。
創(chuàng)建方式二:
創(chuàng)建帶有緩存的線程池:
適用于短期有大量的任務(wù)的時(shí)候使用
public class ThreadPoolDemo2 {
public static void main(String[] args) {
//創(chuàng)建帶緩存的線程池
ExecutorService executorService = Executors.newCachedThreadPool();
for (int i = 0; i < 100; i++) {
executorService.execute(new Runnable() {
@Override
public void run() {
System.out.println(Thread.currentThread().getName());
}
});
}
}
}

方式三:
創(chuàng)建執(zhí)行定時(shí)任務(wù)的線程池
package ThreadPoolDemo;
import java.util.Date;
import java.util.concurrent.Executors;
import java.util.concurrent.ScheduledExecutorService;
import java.util.concurrent.TimeUnit;
/**
* user:ypc;
* date:2021-06-13;
* time: 11:32;
*/
public class ThreadPoolDemo3 {
public static void main(String[] args) {
ScheduledExecutorService scheduledExecutorService = Executors.newScheduledThreadPool(2);
System.out.println("執(zhí)行定時(shí)任務(wù)前的時(shí)間:" + new Date());
scheduledExecutorService.scheduleAtFixedRate(new Runnable() {
@Override
public void run() {
System.out.println("執(zhí)行任務(wù)的時(shí)間:" + new Date());
}
},1,2, TimeUnit.SECONDS);
}
}

執(zhí)行任務(wù)的四個(gè)參數(shù)的意義:
參數(shù)1:延遲執(zhí)行的任務(wù)
參數(shù)2:延遲一段時(shí)間后執(zhí)行
參數(shù)3:定時(shí)任務(wù)執(zhí)行的頻率
參數(shù)4:配合前兩個(gè)參數(shù)使用,是2、3參數(shù)的時(shí)間單位
還有兩種執(zhí)行的方法:
只會(huì)執(zhí)行一次的方法:


第三種的執(zhí)行方式:


那么這種的執(zhí)行方式和第一種的執(zhí)行方式有什么區(qū)別呢?
當(dāng)在兩種執(zhí)行的方式中分別加上sleep()之后:

方式一:

方式三:

結(jié)論很明顯了:
第一種方式是以上一個(gè)任務(wù)的開始時(shí)間+定時(shí)的時(shí)間作為當(dāng)前任務(wù)的開始時(shí)間
第三種方式是以上一個(gè)任務(wù)的結(jié)束時(shí)間來作為當(dāng)前任務(wù)的開始時(shí)間。
創(chuàng)建方式四:
package ThreadPoolDemo;
import java.util.Date;
import java.util.concurrent.Executors;
import java.util.concurrent.ScheduledExecutorService;
import java.util.concurrent.TimeUnit;
/**
* user:ypc;
* date:2021-06-13;
* time: 12:38;
*/
public class ThreadPoolDemo4 {
public static void main(String[] args) {
//創(chuàng)建單個(gè)執(zhí)行任務(wù)的線程池
ScheduledExecutorService scheduledExecutorService
= Executors.newSingleThreadScheduledExecutor();
System.out.println("執(zhí)行任務(wù)之前" + new Date());
scheduledExecutorService.scheduleWithFixedDelay(new Runnable() {
@Override
public void run() {
System.out.println("我是SingleThreadSchedule"+ new Date());
}
},3,1, TimeUnit.SECONDS);
}
}

創(chuàng)建方式五:
創(chuàng)建單個(gè)線程的線程池
package ThreadPoolDemo;
import java.util.concurrent.ExecutorService;
import java.util.concurrent.Executors;
/**
* user:ypc;
* date:2021-06-13;
* time: 12:55;
*/
public class ThreadPoolDemo5 {
public static void main(String[] args) {
//創(chuàng)建單個(gè)線程的線程池
ExecutorService executorService = Executors.newSingleThreadExecutor();
for (int i = 0; i < 20; i++) {
executorService.execute(new Runnable() {
@Override
public void run() {
System.out.println("線程名 " + Thread.currentThread().getName());
}
});
}
}
}

創(chuàng)建單個(gè)線程池的作用是什么?
1.可以避免頻繁創(chuàng)建和銷毀線程所帶來的性能的開銷
2.它有任務(wù)隊(duì)列,可以存儲(chǔ)多余的任務(wù)
3.可以更好的管理任務(wù)
4.當(dāng)有大量的任務(wù)不能處理的時(shí)候,可以友好的執(zhí)行拒絕策略
創(chuàng)建方式六:
創(chuàng)建異步線程池根據(jù)當(dāng)前CPU來創(chuàng)建對(duì)應(yīng)個(gè)數(shù)的線程池
package ThreadPoolDemo;
import java.util.concurrent.ExecutorService;
import java.util.concurrent.Executors;
/**
* user:ypc;
* date:2021-06-13;
* time: 13:12;
*/
public class ThreadPoolDemo6 {
public static void main(String[] args) {
ExecutorService executorService = Executors.newWorkStealingPool();
for (int i = 0; i < 10; i++) {
executorService.execute(new Runnable() {
@Override
public void run() {
System.out.println("線程名" + Thread.currentThread().getName());
}
});
}
}
}

運(yùn)行結(jié)果為什么什么都沒有呢?
看下面的異步與同步的區(qū)別就知道了。
加上這個(gè)

就可以輸出結(jié)果了

線程池的第七種創(chuàng)建方式
前六種的創(chuàng)建方式有什么問題呢?
1.線程的數(shù)量不可控(比如帶緩存的線程池)
2.工作任務(wù)量不可控(默認(rèn)的任務(wù)隊(duì)列的大小時(shí)Integer.MAX_VALUE),任務(wù)比較大肯會(huì)導(dǎo)致內(nèi)存的溢出。
所以就可以使用下面的創(chuàng)建線程池的方式了:
package ThreadPoolDemo;
import java.util.concurrent.LinkedBlockingDeque;
import java.util.concurrent.ThreadFactory;
import java.util.concurrent.ThreadPoolExecutor;
import java.util.concurrent.TimeUnit;
/**
* user:ypc;
* date:2021-06-13;
* time: 15:05;
*/
public class ThreadPoolDemo7 {
private static int threadId = 0;
public static void main(String[] args) {
ThreadFactory threadFactory = new ThreadFactory() {
@Override
public Thread newThread(Runnable r) {
Thread thread = new Thread(r);
thread.setName("我是threadPool-" + ++threadId);
return thread;
}
};
ThreadPoolExecutor threadPoolExecutor = new ThreadPoolExecutor(3, 3, 100,
TimeUnit.MILLISECONDS, new LinkedBlockingDeque<>(12),
threadFactory, new ThreadPoolExecutor.AbortPolicy());
for (int i = 0; i < 15; i++) {
threadPoolExecutor.execute(new Runnable() {
@Override
public void run() {
System.out.println(Thread.currentThread().getName());
}
});
}
}
}

參數(shù)說明:

- 參數(shù)一:核心線程數(shù)|線程池正常情況下的線程 數(shù)量
- 參數(shù)二:最大線程數(shù)|當(dāng)有大量的任務(wù)的時(shí)候可以創(chuàng)建的最多的線程數(shù)
- 參數(shù)三:最大線程的存活時(shí)間
- 參數(shù)四:配合參數(shù)三一起使用的表示參數(shù)三的時(shí)間單位
- 參數(shù)五:任務(wù)隊(duì)列
- 參數(shù)六:線程工廠
- 參數(shù)七:決絕策略
注意事項(xiàng):最大的線程數(shù)要大于等于核心的線程數(shù)


五種拒絕策略


為什么拒絕策略可以舍棄最新的任務(wù)或者最舊的任務(wù)呢?
因?yàn)長(zhǎng)inkedBlockingDeque時(shí)FIFO的。
第五種:自定義的拒絕策略


ThreadPoolExecutor的執(zhí)行方式

package ThreadPoolDemo;
import java.util.concurrent.*;
/**
* user:ypc;
* date:2021-06-13;
* time: 16:58;
*/
public class ThreadPoolDemo9 {
public static void main(String[] args) throws ExecutionException, InterruptedException {
ThreadPoolExecutor threadPoolExecutor = new ThreadPoolExecutor(3, 4, 100,
TimeUnit.MILLISECONDS, new LinkedBlockingDeque<>(10), new ThreadPoolExecutor.DiscardOldestPolicy());
//線程池的執(zhí)行方式一
threadPoolExecutor.execute(new Runnable() {
@Override
public void run() {
System.out.println("使用了execute()執(zhí)行了線程池");
}
});
//線程池的執(zhí)行方式二
Future<String> futureTask =
threadPoolExecutor.submit(new Callable<String>() {
@Override
public String call() throws Exception {
return "使用submit(new Callable<>())執(zhí)行了線程池";
}
});
System.out.println(futureTask.get());
}
}
無返回值的執(zhí)行方式

有返回值的執(zhí)行方式

ThreadPoolExecutor的執(zhí)行流程
當(dāng)任務(wù)量小于核心線程數(shù)的時(shí)候,ThreadPoolExecutor會(huì)創(chuàng)建線程來執(zhí)行任務(wù)
當(dāng)任務(wù)量大于核心的線程數(shù)的時(shí)候,并且沒有空閑的線程時(shí)候,且當(dāng)線程池的線程數(shù)小于最大線程數(shù)的時(shí)候,此時(shí)會(huì)將任務(wù)存
放到任務(wù)隊(duì)列中
如果任務(wù)隊(duì)列也被存滿了,且最大線程數(shù)大于線程池的線程數(shù)的時(shí)候,會(huì)創(chuàng)建新的線程來執(zhí)行任務(wù)。
如果線程池的線程數(shù)等于最大的線程數(shù),并且任務(wù)隊(duì)列也已經(jīng)滿了,就會(huì)執(zhí)行拒絕策略。👇

線程池的終止
shutdown()
線程池的任務(wù)會(huì)執(zhí)行完
shutdownNow()
立即終止線程池,線程池的任務(wù)不會(huì)執(zhí)行完
線程池的狀態(tài)

異步、同步
1.Java 線程 同步與異步
多線程并發(fā)時(shí),多個(gè)線程同時(shí)請(qǐng)求同一個(gè)資源,必然導(dǎo)致此資源的數(shù)據(jù)不安全,A線程修改了B線程的處理的數(shù)據(jù),而B線程又修改了A線程處理的數(shù)理。顯然這是由于全局資源造成的,有時(shí)為了解決此問題,優(yōu)先考慮使用局部變量,退而求其次使用同步代碼塊,出于這樣的安全考慮就必須犧牲系統(tǒng)處理性能,加在多線程并發(fā)時(shí)資源掙奪最激烈的地方,這就實(shí)現(xiàn)了線程的同步機(jī)制
同步
A線程要請(qǐng)求某個(gè)資源,但是此資源正在被B線程使用中,因?yàn)橥綑C(jī)制存在,A線程請(qǐng)求不到,怎么辦,A線程只能等待下去
異步
A線程要請(qǐng)求某個(gè)資源,但是此資源正在被B線程使用中,因?yàn)闆]有同步機(jī)制存在,A線程仍然請(qǐng)求的到,A線程無需等待同步的方式:
1.發(fā)送請(qǐng)求
2.等待執(zhí)行完成
3.有結(jié)果的返回
異步的方式
1.發(fā)請(qǐng)求
2.執(zhí)行完成
3.另一個(gè)線程異步處理
4.處理完成之后返回回調(diào)結(jié)果
顯然,同步最最安全,最保險(xiǎn)的。而異步不安全,容易導(dǎo)致死鎖,這樣一個(gè)線程死掉就會(huì)導(dǎo)致整個(gè)進(jìn)程崩潰,使用異步的機(jī)制,性能會(huì)有所提升
線程工廠
設(shè)想這樣一種場(chǎng)景,我們需要一個(gè)線程池,并且對(duì)于線程池中的線程對(duì)象,賦予統(tǒng)一的線程優(yōu)先級(jí)、統(tǒng)一的名稱、甚至進(jìn)行統(tǒng)一的業(yè)務(wù)處理或和業(yè)務(wù)方面的初始化工作,這時(shí)工廠方法就是最好用的方法了
package ThreadPoolDemo;
import java.util.concurrent.ExecutorService;
import java.util.concurrent.Executors;
import java.util.concurrent.ThreadFactory;
/**
* user:ypc;
* date:2021-06-13;
* time: 11:12;
*/
public class ThreadFactoryDemo {
public static void main(String[] args) {
MyThreadFactory myThreadFactory = new MyThreadFactory();
ExecutorService executorService = Executors.newFixedThreadPool(10,myThreadFactory);
for (int i = 0; i < 10; i++) {
executorService.execute(new Runnable() {
@Override
public void run() {
System.out.println("使用線程工廠設(shè)置的線程名:"+ Thread.currentThread().getName() +
" 使用線程工廠設(shè)置的線程的優(yōu)先級(jí)" + Thread.currentThread().getPriority());
}
});
}
}
private static int count = 0;
static class MyThreadFactory implements ThreadFactory{
@Override
public Thread newThread(Runnable r) {
Thread thread = new Thread(r);
thread.setPriority(8);
thread.setName("thread--" + count++);
return thread;
}
}
}

總結(jié)
本篇文章就到這里了,希望可以對(duì)你有所幫助,也希望您能夠多多關(guān)注腳本之家的更多內(nèi)容!
相關(guān)文章
SpringBoot中間件ORM框架實(shí)現(xiàn)案例詳解(Mybatis)
這篇文章主要介紹了SpringBoot中間件ORM框架實(shí)現(xiàn)案例詳解(Mybatis),本篇文章提煉出mybatis最經(jīng)典、最精簡(jiǎn)、最核心的代碼設(shè)計(jì),來實(shí)現(xiàn)一個(gè)mini-mybatis,從而熟悉并掌握ORM框架的涉及實(shí)現(xiàn),需要的朋友可以參考下2023-07-07
Struts2中圖片以base64方式上傳至數(shù)據(jù)庫
這篇文章主要介紹了Struts2中圖片以base64方式上傳至數(shù)據(jù)庫的實(shí)現(xiàn)代碼,代碼分為前臺(tái)和后臺(tái)兩段,非常不錯(cuò),具有參考借鑒價(jià)值,需要的朋友可以參考下2016-09-09
不知道面試會(huì)不會(huì)問Lambda怎么用(推薦)
這篇文章主要介紹了Lambda表達(dá)式用法,文中通過示例代碼介紹的非常詳細(xì),對(duì)大家的學(xué)習(xí)或者工作具有一定的參考學(xué)習(xí)價(jià)值,需要的朋友們下面隨著小編來一起學(xué)習(xí)學(xué)習(xí)吧2019-04-04
Spring BPP中如何優(yōu)雅的創(chuàng)建動(dòng)態(tài)代理Bean詳解
這篇文章主要給大家介紹了關(guān)于Spring BPP中如何優(yōu)雅的創(chuàng)建動(dòng)態(tài)代理Bean的相關(guān)資料,文中通過示例代碼介紹的非常詳細(xì),對(duì)大家的學(xué)習(xí)或者工作具有一定的參考學(xué)習(xí)價(jià)值,需要的朋友們下面來一起學(xué)習(xí)學(xué)習(xí)吧2019-03-03
springboot項(xiàng)目打成war包部署到tomcat遇到的一些問題
這篇文章主要介紹了springboot項(xiàng)目打成war包部署到tomcat遇到的一些問題,需要的朋友可以參考下2017-06-06
帶你了解Java數(shù)據(jù)結(jié)構(gòu)和算法之哈希表
這篇文章主要為大家介紹了Java數(shù)據(jù)結(jié)構(gòu)和算法之哈希表,具有一定的參考價(jià)值,感興趣的小伙伴們可以參考一下,希望能夠給你帶來幫助2022-01-01
Java KindEditor粘貼圖片自動(dòng)上傳到服務(wù)器功能實(shí)現(xiàn)
這篇文章主要介紹了Java KindEditor粘貼圖片自動(dòng)上傳到服務(wù)器功能實(shí)現(xiàn),本文給大家介紹的非常詳細(xì),對(duì)大家的學(xué)習(xí)或工作具有一定的參考借鑒價(jià)值,需要的朋友可以參考下2023-04-04

