文章目錄
全部源碼:https://github.com/name365/JavaSE-30Day
轉載自atguigu.com視頻
第8章 多線程
線程的生命週期
-
JDK中用Thread.State類定義了線程的幾種狀態
要想實現多線程,必須在主線程中創建新的線程對象。Java語言使用Thread類及其子類的對象來表示線程,在它的一個完整的生命週期中通常要經歷如下的五種狀態:
- 新建:當一個Thread類或其子類的對象被聲明並創建時,新生的線程對象處於新建狀態
- 就緒:處於新建狀態的線程被start()後,將進入線程隊列等待CPU時間片,此時它已具備了運行的條件,只是沒分配到CPU資源
- 運行:當就緒的線程被調度並獲得CPU資源時,便進入運行狀態,run()方法定義了線程的操作和功能
- 阻塞:在某種特殊情況下,被人爲掛起或執行輸入輸出操作時,讓出CPU並臨時中止自己的執行,進入阻塞狀態
- 死亡:線程完成了它的全部工作或線程被提前強制性地中止或出現異常導致結束
-
線程的生命週期
線程的同步
問題的提出
多個線程執行的不確定性引起執行結果的不穩定
多個線程對賬本的共享,會造成操作的不完整性,會破壞數據。
- 例題——模擬火車站售票程序,開啓三個窗口售票。
class Windows1 implements Runnable{
private int ticket = 100;
@Override
public void run() {
while(true){
if(ticket > 0){
System.out.println(Thread.currentThread().getName() + ":賣票,票號爲: " + ticket);
ticket--;
}else{
break;
}
}
}
}
public class WindowsTest1 {
public static void main(String[] args) {
Windows1 w = new Windows1();
Thread t1 = new Thread(w);
Thread t2 = new Thread(w);
Thread t3 = new Thread(w);
t1.setName("窗口1");
t2.setName("窗口2");
t3.setName("窗口3");
t1.start();
t2.start();
t3.start();
}
}
- 理想狀態
- 極端狀態
同步代碼塊處理實現Runnable的線程安全問題
/**
* 例子:創建三個窗口賣票,總票數爲100張.使用實現Runnable接口的方式
* 1.賣票過程中出現重票、錯票 ---》出現了線程的安全問題
* 2.問題出現的原因:當某個線程操作車票的過程中,尚未操作完成時,其他線程參與進來,也操作車票
* 3.如何解決:當一個線程在操作ticket的時候,其他線程不能參與進來。直到線程a操作完ticket時,其他
* 線程纔可以操作ticket。這種情況即使線程a出現了阻塞,也不能被改變。
* 4.在java中,我們通過同步機制,來解決線程的安全問題。
*
* 方式一:同步代碼塊
* synchronized(同步監視器){
* //需要被同步的代碼
*
* }
* 說明:1.操作共享數據的代碼,即爲需要被同步的代碼 --->不能包含代碼多了,也不能包含代碼少了。
* 2.共享數據:多個線程共同操作的變量。比如:ticket就是共享數據
* 3.同步監視器,俗稱:鎖。任何一個類的對象,都可以來充當鎖。
* 要求:多個線程必須要共用同一把鎖。
*
* 補充:在實現Runnable接口創建多線程的方式中,我們可以考慮使用this充當同步監視器。
*
* 方式二:同步方法
* 如果操作共享數據的代碼完整的聲明在一個方法中,我們不妨將此方法聲明同步的
*
* 5.同步的方式,解決了線程的安全問題。---好處
* 操作同步代碼時,只能有一個線程參與,其他線程等待。相當於是一個單線程的過程,效率低。---侷限性
*
* @author subei
* @create 2020-05-07 18:52
*/
class Windows1 implements Runnable{
private int ticket = 100;
// Object obj = new Object();
// Dog dog = new Dog();
@Override
public void run() {
while(true){
synchronized (this) {//此時的this:唯一的windows1的對象 //方式二:synchronized (dog) {
if (ticket > 0) {
try{
Thread.sleep(100);
}catch (InterruptedException e){
e.printStackTrace();
}
System.out.println(Thread.currentThread().getName() + ":賣票,票號爲: " + ticket);
ticket--;
} else {
break;
}
}
}
}
}
public class WindowsTest1 {
public static void main(String[] args) {
Windows1 w = new Windows1();
Thread t1 = new Thread(w);
Thread t2 = new Thread(w);
Thread t3 = new Thread(w);
t1.setName("窗口1");
t2.setName("窗口2");
t3.setName("窗口3");
t1.start();
t2.start();
t3.start();
}
}
class Dog{
}
- 分析同步原理
同步代碼塊處理繼承Thread類的線程安全問題
/**
* 使用同步代碼塊解決繼承Thread類的方式的線程安全問題
*
* 例子:創建三個c窗口賣票,總票數爲100張
*
*
*
* @author subei
* @create 2020-05-08 11:32
*/
class Windows extends Thread{
private static int ticket = 100;
private static Object obj = new Object();
@Override
public void run() {
while(true){
//正確的
// synchronized (obj) {
synchronized (Windows.class){ //Class clazz = Windows.class
//錯誤的,因爲此時this表示的是t1,t2,t3三個對象
// synchronized (this) {
if (ticket > 0) {
try {
Thread.sleep(100);
} catch (InterruptedException e) {
e.printStackTrace();
}
System.out.println(getName() + ":賣票,票號爲: " + ticket);
ticket--;
} else {
break;
}
}
}
}
}
public class WindowsTest2 {
public static void main(String[] args) {
Windows t1 = new Windows();
Windows t2 = new Windows();
Windows t3 = new Windows();
t1.setName("窗口1");
t2.setName("窗口2");
t3.setName("窗口3");
t1.start();
t2.start();
t3.start();
}
}
同步方法處理實現Runnable的線程安全問題
/**
* 使用同步方法解決實現Runnable接口的線程安全問題
*
* 關於同步方法的總結:
* 1. 同步方法仍然涉及到同步監視器,只是不需要我們顯式的聲明。
* 2. 非靜態的同步方法,同步監視器是:this
* 靜態的同步方法,同步監視器是:當前類本身
*
* @author subei
* @create 2020-05-08 14:27
*/
class Windows3 implements Runnable {
private int ticket = 100;
@Override
public void run() {
while (true) {
show();
}
}
public synchronized void show() { //同步監視器:this
// synchronized (this){
if (ticket > 0) {
try {
Thread.sleep(100);
} catch (InterruptedException e) {
e.printStackTrace();
}
System.out.println(Thread.currentThread().getName() + ":賣票,票號爲: " + ticket);
ticket--;
}
// }
}
}
public class WindowsTest3 {
public static void main(String[] args) {
Windows3 w3 = new Windows3();
Thread t1 = new Thread(w3);
Thread t2 = new Thread(w3);
Thread t3 = new Thread(w3);
t1.setName("窗口1");
t2.setName("窗口2");
t3.setName("窗口3");
t1.start();
t2.start();
t3.start();
}
}
同步方法處理繼承Thread類的線程安全問題
/**
* 使用同步方法處理繼承Thread類的方式中的線程安全問題
*
* @author subei
* @create 2020-05-08 14:42
*/
class Windows4 extends Thread {
private static int ticket = 100;
@Override
public void run() {
while (true) {
show();
}
}
private static synchronized void show(){//同步監視器:Window4.class
//private synchronized void show(){ //同步監視器:t1,t2,t3。此種解決方式是錯誤的
if (ticket > 0) {
try {
Thread.sleep(100);
} catch (InterruptedException e) {
e.printStackTrace();
}
System.out.println(Thread.currentThread().getName() + ":賣票,票號爲:" + ticket);
ticket--;
}
}
}
public class WindowsTest4 {
public static void main(String[] args) {
Windows4 t1 = new Windows4();
Windows4 t2 = new Windows4();
Windows4 t3 = new Windows4();
t1.setName("窗口1");
t2.setName("窗口2");
t3.setName("窗口3");
t1.start();
t2.start();
t3.start();
}
}
線程安全的單例模式之懶漢式
/**
* 使用同步機制將單例模式中的懶漢式改寫爲線程安全的
*
* @author subei
* @create 2020-05-08 16:57
*/
public class BankTest {
}
class Bank{
private Bank(){}
private static Bank instance = null;
public static Bank getInstance(){
//方式一:效率稍差
//快捷鍵:Alt+Shift+Z
// synchronized (Bank.class) {
// if(instance == null){
// instance = new Bank();
// }
// return instance;
// }
//方式二:效率較高
if(instance == null) {
synchronized (Bank.class) {
if (instance == null) {
instance = new Bank();
}
}
}
return instance;
}
}
死鎖的問題
- 例1
/**
* 演示線程的死鎖
*
* 1.死鎖的理解:不同的線程分別佔用對方需要的同步資源不放棄,
* 都在等待對方放棄自己需要的同步資源,就形成了線程的死鎖
* 2.說明:
* 》出現死鎖後,不會出現異常,不會出現提示,只是所有的線程都處於阻塞狀態,無法繼續
* 》我們使用同步時,要避免出現死鎖。
*
* @author subei
* @create 2020-05-08 17:25
*/
public class ThreadTest {
public static void main(String[] args) {
StringBuffer s1 = new StringBuffer();
StringBuffer s2 = new StringBuffer();
new Thread(){
@Override
public void run() {
synchronized (s1){
s1.append("a");
s2.append("1");
try {
Thread.sleep(100);
} catch (InterruptedException e) {
e.printStackTrace();
}
synchronized (s2){
s1.append("b");
s2.append("2");
System.out.println(s1);
System.out.println(s2);
}
}
}
}.start();
new Thread(new Runnable() {
@Override
public void run() {
synchronized (s2){
s1.append("c");
s2.append("3");
try {
Thread.sleep(100);
} catch (InterruptedException e) {
e.printStackTrace();
}
synchronized (s1){
s1.append("d");
s2.append("4");
System.out.println(s1);
System.out.println(s2);
}
}
}
}).start();
}
}
- 例2
class A {
public synchronized void foo(B b) {
System.out.println("當前線程名: " + Thread.currentThread().getName()
+ " 進入了A實例的foo方法"); // ①
try {
Thread.sleep(200);
} catch (InterruptedException ex) {
ex.printStackTrace();
}
System.out.println("當前線程名: " + Thread.currentThread().getName()
+ " 企圖調用B實例的last方法"); // ③
b.last();
}
public synchronized void last() {
System.out.println("進入了A類的last方法內部");
}
}
class B {
public synchronized void bar(A a) {
System.out.println("當前線程名: " + Thread.currentThread().getName()
+ " 進入了B實例的bar方法"); // ②
try {
Thread.sleep(200);
} catch (InterruptedException ex) {
ex.printStackTrace();
}
System.out.println("當前線程名: " + Thread.currentThread().getName()
+ " 企圖調用A實例的last方法"); // ④
a.last();
}
public synchronized void last() {
System.out.println("進入了B類的last方法內部");
}
}
public class DeadLock implements Runnable {
A a = new A();
B b = new B();
public void init() {
Thread.currentThread().setName("主線程");
// 調用a對象的foo方法
a.foo(b);
System.out.println("進入了主線程之後");
}
public void run() {
Thread.currentThread().setName("副線程");
// 調用b對象的bar方法
b.bar(a);
System.out.println("進入了副線程之後");
}
public static void main(String[] args) {
DeadLock dl = new DeadLock();
new Thread(dl).start();
dl.init();
}
}
Lock鎖方式解決線程安全問題
- java.util.concurrent.locks.Lock接口是控制多個線程對共享資源進行訪問的工具。鎖提供了對共享資源的獨佔訪問,每次只能有一個線程對Lock對象加鎖,線程開始訪問共享資源之前應先獲得Lock對象。
- ReentrantLock 類實現了Lock ,它擁有與synchronized 相同的併發性和內存語義,在實現線程安全的控制中,比較常用的是ReentrantLock,可以顯式加鎖、釋放鎖。
- 從JDK 5.0開始,Java提供了更強大的線程同步機制——通過顯式定義同步鎖對象來實現同步。同步鎖使用Lock對象充當。
import java.util.concurrent.locks.ReentrantLock;
/**
* 解決線程安全問題的方式三:lock鎖---》JDK5.0新增
*
* 注意:如果同步代碼有異常,要將unlock()寫入finally語句塊
*
* 1. 面試題:synchronized 與 Lock的異同?
* 相同:二者都可以解決線程安全問題
* 不同:synchronized機制在執行完相應的同步代碼以後,自動的釋放同步監視器
* Lock需要手動的啓動同步(lock()),同時結束同步也需要手動的實現(unlock())
*
* 2.優先使用順序:
* Lock 同步代碼塊(已經進入了方法體,分配了相應資源)同步方法(在方法體之外)
*
* 面試題:如何解決線程安全問題?有幾種方式
*
* @author subei
* @create 2020-05-08 17:49
*/
class Windows implements Runnable{
private int ticket = 100;
//1.實例化ReentrantLock
private ReentrantLock lock = new ReentrantLock();
@Override
public void run() {
while(true){
try{
//調用鎖定方法:lock()
lock.lock();
if(ticket > 0){
try {
Thread.sleep(100);
} catch (InterruptedException e) {
e.printStackTrace();
}
System.out.println(Thread.currentThread().getName() + ":售票,票號爲: " + ticket);
ticket --;
}else{
break;
}
}finally {
//3.調用解鎖方法:unlock()
lock.unlock();
}
}
}
}
public class LockTest {
public static void main(String[] args) {
Windows w = new Windows();
Thread t1 = new Thread(w);
Thread t2 = new Thread(w);
Thread t3 = new Thread(w);
t1.setName("窗口1");
t2.setName("窗口2");
t3.setName("窗口3");
t1.start();
t2.start();
t3.start();
}
}
- 練習
/**
* 銀行有一個賬戶。
* 有兩個儲戶分別向同一個賬戶存3000元,每次存1000,存3次。
* 每次存完打印賬戶餘額。
*
* 分析:
* 1.是否是多線程問題?是,兩個儲戶線程
* 2.是否有共享數據?有,賬戶(或賬戶餘額)
* 3.是否有線程安全問題?有
* 4.需要考慮如何解決線程安全問題?同步機制:有三種方式。
*
* @author subei
* @create 2020-05-08 18:23
*/
class Account{
private double balance;
public Account(double balance){
this.balance = balance;
}
//存錢
public synchronized void deposit(double amt){
if(amt > 0){
try {
Thread.sleep(1000);
} catch (InterruptedException e) {
e.printStackTrace();
}
balance += amt;
System.out.println(Thread.currentThread().getName() + ":" + "存錢成功,當前餘額:" + balance);
}
}
}
class Customer extends Thread{
private Account acct;
public Customer(Account acct){
this.acct = acct;
}
@Override
public void run() {
for(int i = 0;i < 3;i++){
acct.deposit(1000);
}
}
}
public class AccountTest {
public static void main(String[] args) {
Account acct = new Account(0);
Customer c1 = new Customer(acct);
Customer c2 = new Customer(acct);
c1.setName("甲");
c2.setName("乙");
c1.start();
c2.start();
}
}
線程的通信
/**
* 線程通信的例子:使用兩個線程打印1-100。線程1, 線程2 交替打印
*
* 涉及到的三個方法:
* wait():一旦執行此方法,當前線程就進入阻塞狀態,並釋放同步監視器。
* notify():一旦執行此方法,就會喚醒被wait的一個線程。如果有多個線程被wait,就喚醒優先級高的那個。
* notifyAll():一旦執行此方法,就會喚醒所有被wait的線程。
*
* 說明:
* 1.wait(),notify(),notifyAll()三個方法必須使用在同步代碼塊或同步方法中。
* 2.wait(),notify(),notifyAll()三個方法的調用者必須是同步代碼塊或同步方法中的同步監視器。
* 否則,會出現IllegalMonitorStateException異常
* 3.wait(),notify(),notifyAll()三個方法是定義在java.lang.Object類中。
*
* @author subei
* @create 2020-05-08 18:50
*/
class Number implements Runnable{
private int number = 1;
public Object obj = new Object();
@Override
public void run() {
while (true){
synchronized (obj) {
obj.notify();
if(number <= 100){
try {
Thread.sleep(10);
} catch (InterruptedException e) {
e.printStackTrace();
}
System.out.println(Thread.currentThread().getName() + ":" + number);
number++;
try {
//使得調用如下wait()方法的線程進入阻塞狀態
obj.wait();
} catch (InterruptedException e) {
e.printStackTrace();
}
}else{
break;
}
}
}
}
}
public class CommunicationTest {
public static void main(String[] args) {
Number number = new Number();
Thread t1 = new Thread(number);
Thread t2 = new Thread(number);
t1.setName("線程1");
t2.setName("線程2");
t1.start();
t2.start();
}
}
sleep()和wait()的異同
/**
* 面試題:sleep() 和 wait()的異同?
* 1.相同點:一旦執行方法,都可以使得當前的線程進入阻塞狀態。
* 2.不同點:1)兩個方法聲明的位置不同:Thread類中聲明sleep() , Object類中聲明wait()
* 2)調用的要求不同:sleep()可以在任何需要的場景下調用。 wait()必須使用在同步代碼塊或同步方法中
* 3)關於是否釋放同步監視器:如果兩個方法都使用在同步代碼塊或同步方法中,sleep()不會釋放鎖,wait()會釋放鎖。
*
* @author shkstart
* @create 2019-02-15 下午 4:21
*/
經典例題:生產者/消費者問題
/**
* 線程通信的應用:經典例題:生產者/消費者問題
*
* 生產者(Productor)將產品交給店員(Clerk),而消費者(Customer)從店員處取走產品,
* 店員一次只能持有固定數量的產品(比如:20),如果生產者試圖生產更多的產品,
* 店員會叫生產者停一下,如果店中有空位放產品了再通知生產者繼續生產;
* 如果店中沒有產品了,店員會告訴消費者等一下,
* 如果店中有產品了再通知消費者來取走產品。
*
* 分析:
* 1.是否是多線程的問題?是,生產者的線程,消費者的線程
* 2.是否有共享數據的問題?是,店員、產品、產品數
* 3.如何解決線程的安全問題?同步機制,有三種方法
* 4.是否涉及線程的通信?是
*
* @author subei
* @create 2020-05-08 19:23
*/
class Clerk{
private int productCount = 0;
//生產產品
public synchronized void produceProduct() {
if(productCount < 20){
productCount++;
System.out.println(Thread.currentThread().getName() + ": 開始生產第" + productCount + "個產品");
notify();
}else{
//等待
try {
wait();
} catch (InterruptedException e) {
e.printStackTrace();
}
}
}
//消費產品
public synchronized void consumeProduct() {
if(productCount > 0){
System.out.println(Thread.currentThread().getName() + ":開始消費第" + productCount + "個產品");
productCount--;
notify();
}else{
//等待
try {
wait();
} catch (InterruptedException e) {
e.printStackTrace();
}
}
}
}
class Producer extends Thread{//生產者
private Clerk clerk;
public Producer(Clerk clerk){
this.clerk = clerk;
}
@Override
public void run() {
System.out.println(getName() + ": 開始生產產品......");
while(true){
try {
Thread.sleep(10);
} catch (InterruptedException e) {
e.printStackTrace();
}
clerk.produceProduct();
}
}
}
class Consumer extends Thread{ //消費者
private Clerk clerk;
public Consumer(Clerk clerk){
this.clerk = clerk;
}
@Override
public void run() {
System.out.println(getName() + ": 開始消費產品......");
while(true){
try {
Thread.sleep(20);
} catch (InterruptedException e) {
e.printStackTrace();
}
clerk.consumeProduct();
}
}
}
public class ProductTest {
public static void main(String[] args) {
Clerk clerk = new Clerk();
Producer p1 = new Producer(clerk);
p1.setName("生產者1");
Consumer c1 = new Consumer(clerk);
c1.setName("消費者1");
Consumer c2 = new Consumer(clerk);
c2.setName("消費者2");
p1.start();
c1.start();
c2.start();
}
}
JDK5.0新增線程創建方式
創建多線程的方式三:實現Callable接口
import java.util.concurrent.Callable;
import java.util.concurrent.ExecutionException;
import java.util.concurrent.FutureTask;
/**
* 創建多線程的方式三:實現Callable接口 ---> JDK 5.0新增
*
* 如何理解實現Callable接口的方式創建多線程比實現Runnable接口創建多線程方式強大?
* 1.call()可以有返回值的。
* 2.call()可以拋出異常,被外面的操作捕獲,獲取異常的信息
* 3.Callable是支持泛型的
* 4.需要藉助FutureTask類,比如獲取返回結果
*
* @author subei
* @create 2020-05-08 20:31
*/
//1.創建一個實現Callable的實現類
class NumThread implements Callable{
//2.實現call方法,將此線程需要執行的操作聲明在call()中
@Override
public Object call() throws Exception {
int sum = 0;
for(int i = 1;i <= 100;i++){
if(i % 2 == 0){
System.out.println(i);
sum += i;
}
}
return sum;
}
}
public class ThreadNew {
public static void main(String[] args) {
//3.創建Callable接口實現類的對象
NumThread numThread = new NumThread();
//4.將此Callable接口實現類的對象作爲傳遞到FutureTask構造器中,創建FutureTask的對象
FutureTask futureTask = new FutureTask(numThread);
//5.將FutureTask的對象作爲參數傳遞到Thread類的構造器中,創建Thread對象,並調用start()
new Thread(futureTask).start();
try {
//6.獲取Callable中call方法的返回值
//get()返回值即爲FutureTask構造器參數Callable實現類重寫的call()的返回值。
Object sum = futureTask.get();
System.out.println("總和爲:" + sum);
} catch (InterruptedException e) {
e.printStackTrace();
} catch (ExecutionException e) {
e.printStackTrace();
}
}
}
- Future接口
- 可以對具體Runnable、Callable任務的執行結果進行取消、查詢是否完成、獲取結果等。
- FutrueTask是Futrue接口的唯一的實現類
- FutureTask 同時實現了Runnable, Future接口。它既可以作爲Runnable被線程執行,又可以作爲Future得到Callable的返回值
使用線程池的好處
背景:經常創建和銷燬、使用量特別大的資源,比如併發情況下的線程,對性能影響很大。
思路:提前創建好多個線程,放入線程池中,使用時直接獲取,使用完放回池中。可以避免頻繁創建銷燬、實現重複利用。類似生活中的公共交通工具。
好處:
- 提高響應速度(減少了創建新線程的時間)
- 降低資源消耗(重複利用線程池中線程,不需要每次都創建)
- 便於線程管理
- corePoolSize:核心池的大小
- maximumPoolSize:最大線程數
- keepAliveTime:線程沒有任務時最多保持多長時間後會終止
- …
創建多線程的方式四:使用線程池
import java.util.concurrent.ExecutorService;
import java.util.concurrent.Executors;
import java.util.concurrent.ThreadPoolExecutor;
/**
* 創建多線程的方式四:使用線程池
*
* 好處:
* 1.提高響應速度(減少了創建新線程的時間)
* 2.降低資源消耗(重複利用線程池中線程,不需要每次都創建)
* 3.便於線程管理
* corePoolSize:核心池的大小
* maximumPoolSize:最大線程數
* keepAliveTime:線程沒有任務時最多保持多長時間後會終止
*
* 面試題:創建多線程有幾種方式?四種!
*
* @author subei
* @create 2020-05-08 21:05
*/
class NumberThread implements Runnable{
@Override
public void run() {
for(int i = 0;i <= 100;i++){
if(i % 2 == 0){
System.out.println(Thread.currentThread().getName() + ":" + i);
}
}
}
}
class NumberThread1 implements Runnable{
@Override
public void run() {
for(int i = 0;i <= 100;i++){
if(i % 2 != 0){
System.out.println(Thread.currentThread().getName() + ":" + i);
}
}
}
}
public class ThreadPool {
public static void main(String[] args) {
//1. 提供指定線程數量的線程池
ExecutorService service = Executors.newFixedThreadPool(10);
ThreadPoolExecutor service1 = (ThreadPoolExecutor) service;
//設置線程池的屬性
// System.out.println(service.getClass());
// service1.setCorePoolSize(15);
// service1.setKeepAliveTime();
//2.執行指定的線程的操作。需要提供實現Runnable接口或Callable接口實現類的對象
service.execute(new NumberThread()); //適合適用於Runable
service.execute(new NumberThread1()); //適合適用於Runable
// service.submit(Callable callable); //適合適用於Callable
//3.關閉連接池
service.shutdown();
}
}
-
線程池相關API
- JDK 5.0起提供了線程池相關API:ExecutorService和Executors
-
ExecutorService:真正的線程池接口。常見子類ThreadPoolExecutor
- void execute(Runnable command) :執行任務/命令,沒有返回值,一般用來執行Runnable
- Future submit(Callable task):執行任務,有返回值,一般又來執行Callable
- void shutdown() :關閉連接池
-
Executors:工具類、線程池的工廠類,用於創建並返回不同類型的線程池
- Executors.newCachedThreadPool():創建一個可根據需要創建新線程的線程池
- Executors.newFixedThreadPool(n); 創建一個可重用固定線程數的線程池
- Executors.newSingleThreadExecutor() :創建一個只有一個線程的線程池
- Executors.newScheduledThreadPool(n):創建一個線程池,它可安排在給定延遲後運行命令或者定期地執行。
整個Java全棧系列都是筆者自己敲的筆記。寫作不易,如果可以,點個讚唄!✌