Java多線程基礎入門 參考:b站-狂神-多線程詳解 練習與演示代碼見gitee:https://gitee.com/yuhaozhee/java-learning-record ...
線程簡介
任務?
程式?
進程 Process?執行程式的一次執行過程,是一個動態的概念。是系統資源分配的單位
線程 Thread?一個進程中可以包含若幹個線程,進程中至少有一個線程。線程是CPU調度和執行的單位
模擬多線程
線程實現(重點)
線程的創建
一、Thread類
1、使用方法
1.自定義線程類繼承Thread類
2.重寫run()方法,編寫線程執行體
3.創建線程對象,調用start()方法啟動線程
2、代碼示例
//創建線程方式一:繼承Thread類,重寫run()方法,調用start()開啟線程
public class TestThread1 extends Thread{
@Override
public void run() {
for (int i = 0; i < 10; i++) {
System.out.println("Thread----:"+i);
}
}
public static void main(String[] args) {
//main線程,主線程
//創建一個線程對象
TestThread1 testThread1=new TestThread1();
//調用方法開啟線程
testThread1.start();
for (int i = 0; i < 10; i++) {
System.out.println("Main--------:"+i);
}
}
}
3、運行結果
Main--------:0
Main--------:1
Thread----:0
Thread----:1
Main--------:2
Thread----:2
Main--------:3
Main--------:4
Main--------:5
Main--------:6
Thread----:3
Main--------:7
Main--------:8
Main--------:9
Thread----:4
Thread----:5
Thread----:6
Thread----:7
Thread----:8
Thread----:9
4、小結
線程開啟不一定立即執行,而是由CPU選擇調度執行
5、案例
下載圖片
//練習Thread,實現多線程同步下載圖片
public class TestThread2 extends Thread{
private String url;
private String name;
public TestThread2(String url, String name) {
this.url = url;
this.name = name;
}
@Override
public void run() {
WebDownloader webDownloader = new WebDownloader();
webDownloader.downloader(url,name);
System.out.println("下載了文件名為:"+name);
}
public static void main(String[] args) {
TestThread2 testThread2_1 = new TestThread2("https://img2.baidu.com/it/u=287632534,2172937882&fm=253&fmt=auto&app=138&f=JPEG?w=504&h=309","CSGO1.jpg");
TestThread2 testThread2_2 = new TestThread2("https://img0.baidu.com/it/u=3909898413,2777003333&fm=253&fmt=auto&app=120&f=JPEG?w=1422&h=800","CSGO2.jpg");
TestThread2 testThread2_3 = new TestThread2("https://img0.baidu.com/it/u=1130153521,1400899467&fm=253&fmt=auto&app=120&f=JPEG?w=1422&h=800","CSGO3.jpg");
testThread2_1.start();
testThread2_2.start();
testThread2_3.start();
}
}
class WebDownloader{
public void downloader(String url,String name) {
try {
FileUtils.copyURLToFile(new URL(url),new File(name));
} catch (IOException e) {
e.printStackTrace();
System.out.println("IO異常");
}
}
}
二、Runnable介面
1、使用方法
1.定義TestThread3類實現Runnable介面
2.實現run()方法,編寫線程執行體
3.創建線程對象,傳入testThread3對象,調用start()方法啟動線程
代理方法
2、代碼示例
public class TestThread3 implements Runnable{
@Override
public void run() {
for (int i = 0; i < 10; i++) {
System.out.println("run:"+i);
}
}
public static void main(String[] args) {
TestThread3 testThread3=new TestThread3();
new Thread(testThread3).start();
for (int i = 0; i < 10; i++) {
System.out.println("main:"+i);
}
}
}
3、運行結果
main:0
run:0
main:1
run:1
main:2
run:2
main:3
run:3
main:4
run:4
main:5
run:5
main:6
run:6
main:7
run:7
main:8
run:8
main:9
run:9
4、對比
繼承Thread類
子類繼承Thread類具備多線程能力
通過子類對象.start() 啟動
不建議使用:避免OOP單繼承局限性
實現Runnable介面
實現介面Runnable具有多線程能力
傳入目標對象+Thread對象.start() 啟動 代理模式 一份資源多個代理
StartThread station=new StartThread();
new Thread(station,"小明").start();
new Thread(station,"小紅").start();
new Thread(station,"小剛").start();
建議使用:避免單繼承局限性,靈活方便,方便同一個對象被多個線程使用
5、初識併發問題
購買車票場景問題
代碼
//多個線程同時操作同一個對象
//買火車票的例子
public class TestThread4 implements Runnable{
private int ticketNums=10;
@Override
public void run() {
while (true){
if (ticketNums<=0){
break;
}
try {
Thread.sleep(200);
} catch (InterruptedException e) {
e.printStackTrace();
}
System.out.println(Thread.currentThread().getName()+":拿到了第"+ticketNums--+"張票");
}
}
public static void main(String[] args) {
TestThread4 testThread4=new TestThread4();
new Thread(testThread4,"1號線程").start();
new Thread(testThread4,"2號線程").start();
new Thread(testThread4,"3號線程").start();
}
}
結果
1號線程:拿到了第10張票
3號線程:拿到了第9張票
2號線程:拿到了第8張票
2號線程:拿到了第6張票
3號線程:拿到了第7張票
1號線程:拿到了第6張票
1號線程:拿到了第5張票
2號線程:拿到了第5張票
3號線程:拿到了第4張票
3號線程:拿到了第3張票
2號線程:拿到了第1張票
1號線程:拿到了第2張票
車票重覆購買:多個線程同時操作同一個資源的情況下,線程不安全,數據紊亂
龜兔賽跑問題
//模擬龜兔賽跑
public class Race implements Runnable{
private static String winner;
@Override
public void run() {
for (int i = 0; i <=100; i++) {
if (Thread.currentThread().getName()=="兔子"&&i%10==0){
try {
Thread.sleep(2 );
} 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;
}else {
if (steps==100){
winner=Thread.currentThread().getName();
System.out.println("winner is"+winner);
return true;
}
}
return false;
}
public static void main(String[] args) {
Race race=new Race();
new Thread(race,"兔子").start();
new Thread(race,"烏龜").start();
}
}
三、Callable介面(瞭解即可)
1、使用方法
1.實現Callable介面,需要返回值類型
2.實現call方法,需要拋出異常
3.創建目標對象
4.創建執行服務:ExecutorService ser=Executors.newFixedThreadPool(1);
5.提交執行:Future
6.獲取結果:boolean r1=result1.get();
7.關閉服務:ser.shutdownNow();
2、代碼示例
public class TestCallable implements Callable<Boolean> {
//實現call方法,需要拋出異常
@Override
public Boolean call() throws Exception {
WebDownloader webDownloader = new WebDownloader();
webDownloader.downloader(url,name);
System.out.println("下載了文件名為:"+name);
return true;
}
private String url;
private String name;
public TestCallable(String url, String name) {
this.url = url;
this.name = name;
}
public static void main(String[] args) {
//創建目標對象
TestCallable testThread2_1 = new TestCallable("https://img2.baidu.com/it/u=287632534,2172937882&fm=253&fmt=auto&app=138&f=JPEG?w=504&h=309","CSGO11.jpg");
TestCallable testThread2_2 = new TestCallable("https://img0.baidu.com/it/u=3909898413,2777003333&fm=253&fmt=auto&app=120&f=JPEG?w=1422&h=800","CSGO21.jpg");
TestCallable testThread2_3 = new TestCallable("https://img0.baidu.com/it/u=1130153521,1400899467&fm=253&fmt=auto&app=120&f=JPEG?w=1422&h=800","CSGO31.jpg");
//創建執行服務
ExecutorService ser= Executors.newFixedThreadPool(3);
//提交執行
Future<Boolean> submit1 = ser.submit(testThread2_1);
Future<Boolean> submit2 = ser.submit(testThread2_2);
Future<Boolean> submit3 = ser.submit(testThread2_3);
//獲取結果
try {
boolean rs1 = submit1.get();
boolean rs2 = submit2.get();
boolean rs3 = submit3.get();
} catch (InterruptedException e) {
e.printStackTrace();
} catch (ExecutionException e) {
e.printStackTrace();
}
//關閉服務
ser.shutdownNow();
}
}
靜態代理模式
public class StacticProxy {
public static void main(String[] args) {
WeddingCompany weddingCompany=new WeddingCompany(new You());
weddingCompany.HappyMarry();
}
}
interface Marry{
void HappyMarry();
}
//真實角色
class You implements Marry{
@Override
public void HappyMarry() {
System.out.println("marry");
}
}
//代理角色
class WeddingCompany implements Marry{
private Marry target;
public WeddingCompany(Marry target) {
this.target = target;
}
@Override
public void HappyMarry() {
before();
this.target.HappyMarry();
after();
}
private void after() {
System.out.println("收尾款");
}
private void before() {
System.out.println("佈置現場");
}
}
代理對象可以做很多真實對象做不了的事情,真實對象專註做自己的事情
Lambda表達式
函數式編程
為什麼要使用Lambda表達式?避免匿名內部類過多,讓代碼看起來簡潔,去掉無意義的代碼只留下核心的邏輯
什麼是函數式介面?任何介面只包含唯一一個抽象方法,則它就是一個函數式介面
public interface Runnable{
public abstract void run();
}
對於函數式介面,我們可以通過lambda表達式來創建該介面的對象
推導過程
//推導Lambda表達式
public class TestLambda1 {
//3.靜態內部類
static class Like2 implements ILike{
@Override
public void lambda() {
System.out.println("I Like Lambda2");
}
}
public static void main(String[] args) {
ILike like=new Like();
like.lambda();
like=new Like2();
like.lambda();
//4.局部內部類
class Like3 implements ILike{
@Override
public void lambda() {
System.out.println("I Like Lambda3");
}
}
like=new Like3();
like.lambda();
//5.匿名內部類
like=new ILike() {
@Override
public void lambda() {
System.out.println("I Like Lambda,匿名內部類");
}
};
like.lambda();
//6.用Lambda簡化
like=()->{
System.out.println("Lambda!!!");
};
like.lambda();
}
}
//1.定義一個介面
interface ILike{
void lambda();
}
//2.實現類
class Like implements ILike{
@Override
public void lambda() {
System.out.println("I Like Lambda");
}
}
線程狀態
一、五大狀態
線程有五大狀態:創建(new)、就緒(start)、阻塞(sleep、wait)、運行(執行)、死亡
創建:Thread t=new Thread(),線程對象一旦創建就進入到了新生狀態
就緒:創建後,調用start()方法,線程立即進入就緒狀態,但不意味著立即調度執行
阻塞:當調用sleep()、wait()或同步鎖定時,線程進入阻塞狀態,就是代碼不往下執行,阻塞事件解除後,重新進入就緒狀態,等待CPU調度執行
運行:進入運行狀態,線程才真正執行線程體的代碼塊
死亡:線程中斷或者結束,一旦進入死亡狀態,就不能再次啟動
- Thread.State 線程狀態
NEW: 尚未啟動的線程處於此狀態
RUNNABLE:在Java虛擬機中執行的線程處於此狀態
BLOCKED:被阻塞等待監視器鎖定的線程處於此狀態
WAITING:正在等待另一個線程執行特定動作的線程處於此狀態
TIMED_WAITING:正在等待另一個線程執行動作達到指定等待時間的線程處於此狀態
TERMINATED:已退出的線程處於此狀態
二、線程方法
//更改線程的優先順序
setPriority(int newPriority)
//指定毫秒後讓線程休眠
static void sleep(long millis)
//等待該線程終止
void join()
//暫停正在執行的線程,運行其他的
static void yield()
//中斷線程(不要用這個方式)
void interrupt()
//測試線程是否處於活動狀態
boolean isAlive()
1、線程停止
不推薦JDK提供的stop()、destroy()方法(已廢棄)
推薦讓線程自己停下來,建議使用一個標誌位來終止線程
//測試標誌位flag
public class TestStop implements Runnable {
private boolean flag = true;
@Override
public void run() {
int i = 0;
while (flag==true) {
System.out.println("Thread is running..." + i);
i++;
}
}
public void stop() {
this.flag = false;
}
public static void main(String[] args) {
TestStop testStop = new TestStop();
new Thread(testStop).start();
System.out.println("Start");
for (int i = 0; i < 100000; i++) {
System.out.println("main is running..." + i);
if (i == 90000) {
testStop.stop();
break;
}
}
}
}
2、線程禮讓
作用是讓當前運行的線程暫停,但不阻塞,只是進入就緒狀態
讓CPU重新調度,有可能原本的進程再次運行進去,禮讓不一定成功
public class TestYield{
public static void main(String[] args) {
MyYield myYield=new MyYield();
new Thread(myYield,"A").start();
new Thread(myYield,"B").start();
}
}
class MyYield implements Runnable{
@Override
public void run() {
System.out.println(Thread.currentThread().getName()+" is running...");
Thread.yield();
System.out.println(Thread.currentThread().getName()+" is stop...");
}
}
案例有問題,不一定正確,實際可能與調度演算法有關
3、Join
Join合併線程,待此線程完成後,再執行其他線程,其他線程阻塞,類似”插隊“
public class TestJoin implements Runnable{
public static void main(String[] args) throws InterruptedException {
TestJoin testJoin=new TestJoin();
Thread thread = new Thread(testJoin);
thread.start();
for (int i = 0; i < 300; i++) {
if (i==200){
thread.join();
}
System.out.println("main..."+i);
}
}
@Override
public void run() {
for (int i = 0; i < 100; i++) {
System.out.println("vip..."+i);
}
}
}
4、線程狀態
- Thread.State 線程狀態
NEW: 尚未啟動的線程處於此狀態
RUNNABLE:在Java虛擬機中執行的線程處於此狀態
BLOCKED:被阻塞等待監視器鎖定的線程處於此狀態
WAITING:正在等待另一個線程執行特定動作的線程處於此狀態
TIMED_WAITING:正在等待另一個線程執行動作達到指定等待時間的線程處於此狀態
TERMINATED:已退出的線程處於此狀態
public class TestState {
public static void main(String[] args) throws InterruptedException {
Thread thread = new Thread(()->{
for (int i = 0; i < 5; i++) {
try {
Thread.sleep(1000);
} catch (InterruptedException e) {
e.printStackTrace();
}
}
System.out.println(".......");
});
//觀察狀態
Thread.State state = thread.getState();
System.out.println(state);//new
thread.start();
state = thread.getState();
System.out.println(state);//run
while (state!=Thread.State.TERMINATED){
Thread.sleep(100);
state = thread.getState();//更新線程狀態
System.out.println(state);
}
}
}
5、線程優先順序
Java提供一個線程調度器來監控監視程式中啟動後進入到就緒狀態的所有線程,線程調度器按照優先順序決定應該調度哪個線程來執行
-
線程的優先順序用數字表示,範圍從1~10
-
通過getPriority().setPriority(int xxx)
public class TestPriority {
public static void main(String[] args) {
MyPriority myPriority = new MyPriority();
Thread thread1 = new Thread(myPriority,"thread1");
Thread thread2 = new Thread(myPriority,"thread2");
Thread thread3 = new Thread(myPriority,"thread3");
Thread thread4 = new Thread(myPriority,"thread4");
Thread thread5 = new Thread(myPriority,"thread5");
Thread thread6 = new Thread(myPriority,"thread6");
thread1.start();
thread2.setPriority(1);
thread2.start();
thread3.setPriority(4);
thread3.start();
thread4.setPriority(Thread.MAX_PRIORITY);
thread4.start();
thread5.setPriority(-1);
thread5.start();
thread6.setPriority(11);
thread6.start();
}
}
class MyPriority implements Runnable{
@Override
public void run() {
System.out.println(Thread.currentThread().getName()+"-->"+Thread.currentThread().getPriority());
}
}
優先順序高的不一定先跑
6、守護(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);
thread.start();
new Thread(you).start();
}
}
//上帝
class God implements Runnable{
@Override
public void run() {
while(true){
System.out.println("god bless u");
}
}
}
//你
class You implements Runnable{
@Override
public void run() {
for (int i = 0; i < 365; i++) {
System.out.println("live...");
}
System.out.println("gg...");
}
}
線程同步(重點)
併發:多個線程操作同一個資源
排隊:多個線程訪問同一個對象,並且某些線程還想修改這個對象,這時我們就需要線程同步,線程同步其實是一種等待機制,多個需要同時訪問此對象的線程進入這個對象的等待池形成隊列,等待前麵線程使用完畢,下一個線程再使用
由於同一個進程的多個線程共用一塊存儲空間,在帶來方便的同時,也帶來了訪問衝突問題,為了保證數據在方法中被訪問時的正確性,在訪問時加入鎖機制 synchronized,當一個線程獲得對象的排他鎖,獨占資源,其他線程必須等待,使用後釋放鎖即可,存在以下問題
- 一個線程持有鎖會導致其他所有需要此鎖的線程被掛起
- 在多線程競爭下,加鎖,釋放鎖會導致比較多的上下文切換和調度延時,引起性能問題
- 如果一個優先順序高的線程等待一個優先順序低的線程釋放鎖,會導致優先順序倒置,引起性能問題
購買車票場景問題2
public class UnsafeBuyTicket {
public static void main(String[] args) {
BuyTicket station=new BuyTicket();
new Thread(station,"小明").start();
new Thread(station,"小紅").start();
new Thread(station,"小剛").start();
}
}
class BuyTicket implements Runnable{
private int ticketNums=10;
boolean flag=true;
@Override
public void run() {
//買票
while (flag){
try {
buy();
} catch (InterruptedException e) {
e.printStackTrace();
}
}
}
public void buy() throws InterruptedException {
if (ticketNums<=0){
flag=false;
return;
}
Thread.sleep(100);
System.out.println(Thread.currentThread().getName()+"拿到了"+ticketNums--);
}
public void stop() {
flag=false;
}
}
存在重覆購票的情況,線程不安全
此時需要引入線程同步機制
同步方法
由於我們可以通過private關鍵字來保證數據對象只能被方法訪問,所以我們只需要對方法提出一套機制,這套機制就是synchronized關鍵字,它包括兩個用法:synchronized方法和synchronized塊
1.synchronized方法
public synchronized void method(int args){}
synchronized方法控制對“對象”的訪問,每個對象對應一把鎖,每個synchronized方法都必須獲得調用該方法的對象的鎖才能執行,否則線程會阻塞,方法一旦執行,就獨占該鎖,直到方法返回才釋放鎖,後面被阻塞的線程才能獲得這個鎖,繼續執行
缺陷:若將一個大的方法聲明為synchronized將會影響效率
2.synchronized塊
同步塊
synchronized(互斥資源){}
Obj稱之為同步監視器
- Obj可以是任何對象,但是推薦使用共用資源作為同步監視器
- 同步方法中無需指定同步監視器,因為同步方法的同步監視器就是this,就是這個對象本身,或者是class
同步監視器的執行過程
1.第一個線程訪問,鎖定同步監視器,執行其中代碼
2.第二個線程訪問,發現同步監視器被鎖定,無法訪問
3.第一個線程訪問完畢,解鎖同步監視器
4.第二個線程訪問,發現同步監視器沒有鎖,然後鎖定並訪問
3.Lock鎖
可重用鎖(ReentrantLock)
public class TestLock {
public static void main(String[] args) {
TestLock2 testLock2=new TestLock2();
new Thread(testLock2).start();
new Thread(testLock2).start();
new Thread(testLock2).start();
}
}
public class TestLock2 implements Runnable{
int ticketNums=10;
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();
}
}
}
}
synchronized與Lock的對比
- Lock是顯式鎖(手動開啟和關閉鎖,try開啟,finally關閉),synchronized是隱式鎖,出了作用域自動釋放
- Lock只有代碼塊鎖,synchronized有代碼塊鎖和方法鎖
- 使用Lock鎖,JVM將使用較少時間來調度線程,性能更好,並且具有更好的擴展性(提供更多子類)
- 優先使用順序:Lock->同步代碼塊(已經進入了方法體,分配了相應資源)->同步方法(在方法體外)
死鎖
多個線程各自占有一些共用資源,並且互相等待其他線程占用的資源才能運行,而導致兩個或者多個線程都在等待對方釋放資源,都停止執行的情景。某一個同步塊同時擁有“兩個以上對象的鎖”時,就可能發生“死鎖問題”
public class DeadLock {
public static void main(String[] args) {
Makeup g1=new Makeup(0,"灰姑娘");
Makeup g2=new Makeup(1,"白雪公主");
g1.start();
g2.start();
}
}
//口紅
class Lipstick{
}
//鏡子
class Mirror{
}
class Makeup extends Thread{
static Lipstick lipstick=new Lipstick();
static Mirror mirror=new Mirror();
int choice;
String girlName;
public Makeup(int choice, String girlName) {
this.choice = choice;
this.girlName = girlName;
}
@Override
public void run() {
try {
makeup();
} catch (InterruptedException e) {
e.printStackTrace();
}
}
private void makeup() throws InterruptedException {
if (choice==0){
synchronized (lipstick){
System.out.println(this.girlName+"獲得口紅");
Thread.sleep(500);
synchronized (mirror){
System.out.println(this.girlName+"獲得鏡子");
Thread.sleep(500);
}
}
}else {
synchronized (mirror){
System.out.println(this.girlName+"獲得鏡子");
Thread.sleep(500);
synchronized (lipstick){
System.out.println(this.girlName+"獲得口紅");
Thread.sleep(500);
}
}
}
}
}
產生死鎖的四個必要條件
1、互斥條件:一個資源每次只能被一個進程使用。
2、請求與保持條件:一個進程因請求資源而阻塞時,對已獲得的資源保持不放
3、不剝奪條件:進程已獲得的資源,在未使用完之前,不能強行剝奪
4、迴圈等待條件:若幹進程之間形成一種頭尾相接的迴圈等待資源關係
只要破壞其中任意一個或多個就可以避免死鎖發生
線程通信問題
生產者消費者問題
- 倉庫中只能存放一件物品,生產者將生產出來的產品放入倉庫,消費者將倉庫中產品取走消費
- 如果倉庫中沒有產品,則生產者將產品放入倉庫,否則停止生產並等待,直到倉庫中的產品被消費者取走為止
- 如果倉庫中放有產品,則消費者可以將產品取走消費,否則停止消費並等待,直到倉庫再次放入產品為止
這是一個線程同步問題,生產者和消費者共用一個資源,並且生產者和消費者之間互相依賴,互為條件
關鍵方法wait()、notify()
wait:線程釋放鎖等待
notify:喚醒一個等待狀態的線程
管程法
public class TestPC {
public static void main(String[] args) {
SynContainer container =new SynContainer();
new Producer(container).start();
new Consumer(container).start();
}
}
class Producer extends Thread{
SynContainer container;
public Producer(SynContainer container) {
this.container = container;
}
@Override
public void run() {
for (int i = 0; i < 100; i++) {
try {
container.push(new Chicken(i));
} catch (InterruptedException e) {
e.printStackTrace();
}
System.out.println("生產了"+i+"只雞");
}
}
}
class Consumer extends Thread{
SynContainer container;
public Consumer(SynContainer container) {
this.container = container;
}
@Override
public void run() {
for (int i = 0; i < 100; i++) {
try {
Thread.sleep(1000);
} catch (InterruptedException e) {
e.printStackTrace();
}
System.out.println("消費了"+i+"只雞");
try {
container.pop();
} catch (InterruptedException e) {
e.printStackTrace();
}
}
}
}
class Chicken{
int id;
public Chicken(int id) {
this.id = id;
}
}
class SynContainer{
Chicken[] chickens=new Chicken[10];
int count=0;
public synchronized void push(Chicken chicken) throws InterruptedException {
if (chickens.length==count){
this.wait();
}
chickens[count]=chicken;
count++;
this.notifyAll();
}
public synchronized Chicken pop() throws InterruptedException {
if (count==0){
this.wait();
}
count--;
Chicken chicken = chickens[count];
this.notifyAll();
return chicken;
}
}
信號燈法
線程池
背景:經常創建和銷毀、使用量特別大的資源,比如併發情況下的線程,對性能影響很大
思路:提前創建好多個線程,放入線程池中,使用時直接獲取,使用完放回池中。可以避免頻繁創建銷毀,實現重覆利用。類似生活中的公共交通工具
好處
1.提高響應速度(減少了創建新線程的時間)
2.降低資源消耗(重覆利用線程池中線程,不需要每次都創建)
3.便於線程管理
(1)corePoolSize:核心池大小
(2)maximumPoolSize:最大線程數
(3)keepAliveTime:線程沒有任務時最多保持多長時間後會終止
線程池相關API
ExecutorService和Executors
1.ExecutorService:真正的線程池介面。常見子類ThreadPoolExecutor
(1)void execute(Runnable command):執行任務/命令,沒有返回值,一般用來執行Runnable
(2)
(3)void shutdown():關閉線程池
2.Executors:工具類、線程池的工廠類,用於創建並返回不同類型的線程池