JAVA中關(guān)于多線程的使用詳解
多線程基礎(chǔ)
1 概述
現(xiàn)代操作系統(tǒng)(Windows,macOS,Linux)都可以執(zhí)行多任務(wù)。多任務(wù)就是同時運行多個任務(wù)。例如:播放音樂的同時,瀏覽器可以進行文件下載,同時可以進行QQ消息的收發(fā)。
CPU執(zhí)行代碼都是一條一條順序執(zhí)行的,但是,即使是單核CPU,也可以同時運行多個任務(wù)。因為操作系統(tǒng)執(zhí)行多任務(wù)實際上就是讓CPU對多個任務(wù)輪流交替執(zhí)行。
操作系統(tǒng)輪流讓多個任務(wù)交替執(zhí)行,例如,讓瀏覽器執(zhí)行0.001秒,讓QQ執(zhí)行0.001秒,再讓音樂播放器執(zhí)行0.001秒。在用戶使用的體驗看來,CPU就是在同時執(zhí)行多個任務(wù)。
1 進程與線程
程序:程序是含有指令和數(shù)據(jù)的文件,被存儲在磁盤或其他的數(shù)據(jù)存儲設(shè)備中,可以理解為程序是包含靜態(tài)代碼的文件。例如:瀏覽器軟件、音樂播放器軟件等軟件的安裝目錄和文件。
進程:進程是程序的一次執(zhí)行過程,是系統(tǒng)運行程序的基本單位。
線程:某些進程內(nèi)部還需要同時執(zhí)行多個子任務(wù)。例如,我們在使用WPS時,WPS可以讓我們一邊打字,一邊進行拼寫檢查,同時還可以在后臺進行自動保存和上傳云文檔,我們把子任務(wù)稱為線程。線程是進程劃分成的更小的運行單位。
進程和線程的關(guān)系就是:一個進程可以包含一個或多個線程,但至少會有一個主線程。
2 線程基本概念
單線程:單線程就是進程中只有一個線程。單線程在程序執(zhí)行時,所走的程序路徑按照連續(xù)順序排下來,前面的必須處理好,后面的才會執(zhí)行
多線程:由一個以上的線程組成的程序稱為多線程程序。Java中,一定是從主線程開始執(zhí)行(main方法),然后在主線程的某個位置創(chuàng)建并啟動新的線程。
多線程的應(yīng)用場景
- 軟件中的耗時操作,拷貝的遷移文件,加載大量資源的時候
- 所有的后臺服務(wù)器
- 所有的聊天軟件
3 線程的創(chuàng)建方式
3. 1 方式一:繼承java.lang.Thread類(線程子類)
public class Demo01 {
public static void main(String[] args) {
//1.創(chuàng)建一個Thread的子類,重寫run方法
//2.創(chuàng)建子類對象
//3.調(diào)用start方法啟動
MyThread myThread = new MyThread();
myThread.start();
for (int i = 0; i < 10; i++) {
System.out.println("主線程執(zhí)行任務(wù):"+i);
}
}
}
class MyThread extends Thread {
@Override
public void run() {
for (int i = 0; i < 10; i++) {
System.out.println(getName()+"正在執(zhí)行任務(wù):"+i);
}
}
}3. 2 方式二:實現(xiàn)java.lang.Runnable接口(線程執(zhí)行類)
public class Demo02 {
public static void main(String[] args) {
//方式2:java.lang.Runnable 接口實現(xiàn)多線程
//1.創(chuàng)建Runnable的實現(xiàn)類,重寫run方法
//2.創(chuàng)建Runnable的實現(xiàn)類對象r1
//3.創(chuàng)建Thread類的對象,將r1作為構(gòu)造方法的參數(shù)進行創(chuàng)建
//4.調(diào)用start方法啟動線程
MyRun myRun = new MyRun();
Thread t1 = new Thread(myRun);
t1.start();
for (int i = 0; i < 10; i++) {
System.out.println("主線程執(zhí)行任務(wù):"+i);
}
}
}
class MyRun implements Runnable {
public void run() {
for (int i = 0; i < 10; i++) {
System.out.println(Thread.currentThread().getName()+"線程執(zhí)行任務(wù):"+i);
}
}
}3. 3 方式三:實現(xiàn)java.util.concurrent.Callable接口,允許子線程返回結(jié)果、拋出異常
public class Demo03 {
public static void main(String[] args) throws ExecutionException, InterruptedException {
//方式3:Callable接口方式實現(xiàn)多線程
//步驟:
//1.創(chuàng)建Callable的實現(xiàn)類,重寫call方法
//2.創(chuàng)建Callable的實現(xiàn)類對象
//3.創(chuàng)建FutureTask對象,用來進行結(jié)果的管理操作
//4.創(chuàng)建Thread類的對象,將步驟3的對象作為參數(shù)傳遞
//5.啟動線程
MyCallable callable1 = new MyCallable(1,10);
FutureTask<Integer> futureTask1 = new FutureTask<Integer>(callable1);
Thread thread1=new Thread(futureTask1);
thread1.setName("線程1");
thread1.start();
MyCallable callable2 = new MyCallable(2,11);
FutureTask<Integer> futureTask2 = new FutureTask<Integer>(callable2);
Thread thread2=new Thread(futureTask2);
thread2.setName("線程2");
thread2.start();
System.out.println("子線程C1和為:"+futureTask1.get());
System.out.println("子線程C2和為:"+futureTask2.get());
for (int i=1;i<=10;i++) {
System.out.println("主線程:"+i);
}
}
}
class MyCallable implements Callable<Integer> {
int begin,end;
public MyCallable(int begin,int end) {
this.begin = begin;
this.end = end;
}
@Override
public Integer call() throws Exception {
int sum = 0;
for (int i = begin; i <= end; i++) {
sum += i;
System.out.println(Thread.currentThread().getName()+"正在進行加"+i+"操作");
}
return sum;
}
}3. 4 方式四:線程池
public class Demo04 {
public static void main(String[] args) {
//使用線程池創(chuàng)建線程對象
ExecutorService ex= Executors.newCachedThreadPool();
ex.execute(new MyRun());
ex.execute(new MyRun());
}
}總結(jié):
- Thread 編程比較簡單,可以直接使用Thread類中的方法 可擴展性差
- Runnable 拓展性強,實現(xiàn)該接口后還可以繼承其他的類再實現(xiàn)其他的接口
- Callable 拓展性強,實現(xiàn)該接口還可以繼承其他類,可以有返回值
4 線程所擁有的方法
4.1 getName()獲取線程名
public class Demo01 {
public static void main(String[] args) {
//獲取當(dāng)前線程
Thread t1=Thread.currentThread();
System.out.println("線程對象: "+t1);
//獲取線程名 getName(),如果沒有為線程命名,系統(tǒng)會默認指定線程名,命名規(guī)則是Thread-N的形式
System.out.println("線程名:"+t1.getName());
}
}4.2 setName()設(shè)置線程名
public class Demo02 {
public static void main(String[] args) {
//給線程設(shè)置名字,setName()
//讓當(dāng)前線程休眠:Thread.sleep(long ,min) 參數(shù)為毫秒值
MyThread t1 = new MyThread();
t1.setName("土豆");
t1.start();
//使用構(gòu)造方法設(shè)置線程名
MyThread t2 = new MyThread("洋芋");
t2.start();
//Runnable作為參數(shù)
Runnable runnable = new Runnable() {
@Override
public void run() {
System.out.println(Thread.currentThread().getName()+"線程正在啟動");
try {
Thread.sleep(1000);
} catch (InterruptedException e) {
e.printStackTrace();
}
System.out.println(Thread.currentThread().getName()+"線程運行結(jié)束");
}
};
Thread thread = new Thread(runnable);
thread.setName("馬鈴薯");
thread.start();
}
}4.3 sleep()線程休眠
public class MyThread extends Thread {
public MyThread(){}
public MyThread(String name) {
super(name);
}
@Override
public void run() {
System.out.println("當(dāng)前線程為:"+getName());
for(int i=1; i<=5; i++){
try {
//休眠1秒
Thread.sleep(1000);
} catch (InterruptedException e) {
e.printStackTrace();
}finally {
System.out.println(i);
}
}
}
}4.4 setPriority(long newPriority) 設(shè)置線程優(yōu)先級
public class Demo03 {
public static void main(String[] args) {
Runnable runnable = new Runnable() {
public void run() {
for (char i = 'a'; i <= 'g'; i++) {
System.out.println(Thread.currentThread().getName() + ": " + i);
}
}
};
Runnable runnable2 = new Runnable() {
public void run() {
for (char i = 'A'; i <= 'J'; i++) {
System.out.println(Thread.currentThread().getName() + ": " + i);
}
}
};
Thread t1=new Thread(runnable,"線程1");
Thread t2=new Thread(runnable2,"線程2");
//設(shè)置線程優(yōu)先級 1~10,
//注意:優(yōu)先級高的線程搶占到資源的概率較大,但不一定優(yōu)先搶占到資源
t1.setPriority(1);
t2.setPriority(10);
t1.start();
t2.start();
//獲取線程優(yōu)先級
System.out.println(t1.getName()+"的優(yōu)先級為"+t1.getPriority());
System.out.println(t2.getName()+"的優(yōu)先級為"+t2.getPriority());
System.out.println(Thread.currentThread().getName()+"的優(yōu)先級為"+Thread.currentThread().getPriority());
}
}4.5 setDaemon設(shè)置守護線程
public class Demo04 {
public static void main(String[] args) {
Thread t1 = new Thread("線程1") {
public void run() {
for (char i = 'a'; i < 'z'; i++) {
System.out.println(getName()+":"+i);
}
}
};
Thread t2 = new Thread("線程2") {
public void run() {
for (char i = 'A'; i < 'Z'; i++) {
System.out.println(getName()+":"+i);
}
}
};
//t2線程設(shè)置為守護線程
//細節(jié): 當(dāng)其他的非守護線程執(zhí)行完畢后,守護線程會陸陸續(xù)續(xù)的執(zhí)行結(jié)束
t2.setDaemon(true);
t1.start();
t2.start();
}
}4.6 yield()線程禮讓
public class Demo05 {
public static void main(String[] args) {
Thread t1 = new Thread("線程1") {
public void run() {
for (char i = 'a'; i < 'z'; i++) {
System.out.println(getName()+":"+i);
Thread.yield();
}
}
};
Thread t2 = new Thread("線程2") {
public void run() {
Thread.yield();
for (char i = 'A'; i < 'Z'; i++) {
System.out.println(getName()+":"+i);
}
}
};
t1.start();
t2.start();
}
}4.7 join()線程插隊
public class Demo06 {
public static void main(String[] args) throws InterruptedException {
Thread t1=new Thread(){
public void run(){
System.out.println("進到"+getName()+"之中");
for(int i=1;i<=100;i++){
System.out.println(i);
}
System.out.println("結(jié)束");
}
};
t1.start();
t1.join(); //線程的插隊,插入當(dāng)前的線程的前面
//主線程執(zhí)行的任務(wù)
for(char i='A';i<='z';i++){
System.out.println("main:"+i);
}
}
}4.8 interrupt()線程的中斷
public class Demo07 {
public static void main(String[] args) {
Thread t1 = new Thread("線程1"){
public void run(){
//獲取當(dāng)前系統(tǒng)時間
long startTime = System.currentTimeMillis();
System.out.println("進入到"+getName()+"線程中");
try {
Thread.sleep(3000);
} catch (InterruptedException e) {
System.out.println("中斷"+getName()+"線程");
e.printStackTrace();
}
System.out.println("結(jié)束"+getName()+"線程");
long endTime = System.currentTimeMillis();
System.out.println(getName()+"運行時間:"+(endTime-startTime));
}
};
t1.start();
//讓主線程休眠
System.out.println("main線程進入");
try {
Thread.sleep(1000);
} catch (InterruptedException e) {
throw new RuntimeException(e);
}
//main主線程修改t1線程的中斷狀態(tài)=true
//t1線程檢測中斷狀態(tài)=true,則拋出InterruptedException,子線程執(zhí)行結(jié)束
t1.interrupt();
}
}5 多線程同步
Synchronized同步鎖,簡單來說,使用Synchronized關(guān)鍵字將一段代碼邏輯,用一把鎖給鎖起來,只有獲得了這把鎖的線程才訪問。并且同一時刻, 只有一個線程能持有這把鎖, 這樣就保證了同一時刻只有一個線程能執(zhí)行被鎖住的代碼,從而確保代碼的線程安全。
看不懂?沒有關(guān)系,我也看不懂,簡單來說就是,如果有多個線程訪問同一個資源,如果在不加鎖的情況下,資源會混亂,比如有三個線程同時訪問一個數(shù)number,并且修改他的值,那么這個值最終大概會達不到想要的結(jié)果,產(chǎn)生數(shù)據(jù)混亂,比如兩個線程分別對一個數(shù)進行操作,其中一個加100次1,另外一個減100次1,預(yù)想的結(jié)果應(yīng)該還是這個數(shù)本身,但如果不加鎖,那么他極有可能變?yōu)閯e的值。
synchronized 關(guān)鍵字的用法
* 1.加鎖:
* 鎖對象可以是任意類型的對象,必須要保證多條線程共用一個鎖對象
* synchronized(鎖對象){
* 操作的共享代碼
* }
*
* 某條線程獲取到了鎖資源,鎖關(guān)閉,當(dāng)里面的任務(wù)執(zhí)行完成,鎖釋放
* 默認情況下,鎖是打開狀態(tài)
*
* 2.鎖方法:
* synchronized加到方法上,變成了鎖方法,注意鎖不能自己指定
* 修飾符 synchronized 返回值類型 方法名(){
* 操作的共享代碼
* }
* 鎖:同步鎖不能自己指定,普通方法鎖是當(dāng)前對象1 修飾實例方法:
當(dāng)使用synchronized 修飾實例方法時, 以下兩種寫法作用和意義相同:
方法1:
public class ShouMai extends Thread {
public static int ticket = 1000;
public static Object lock = new Object();
public ShouMai(String name) {
super(name);
}
//請加鎖,保證線程安全
@Override
public void run() {
while (true) {
synchronized (ShouMai.class) {
if (ticket > 0) {
System.out.println(getName() + "正在售賣第" + (1000 - --ticket) + "張票");
} else {
System.out.println(getName() + ":票已售罄");
break;
}
}
}
}
}方式2:
public class ShouMai1 extends Thread {
public static int ticket = 1000;
public ShouMai1(String name) {
super(name);
}
//加鎖,保證線程安全
@Override
public void run() {
while (true) {
if(!shoumai()) break;
}
}
public synchronized static boolean shoumai() {
if (ticket > 0) {
System.out.println(Thread.currentThread().getName() + "正在售賣第" + (1000 - --ticket) + "張票");
return true;
} else {
System.out.println(Thread.currentThread().getName() + ":票已售罄");
return false;
}
}
}2 修飾靜態(tài)方法
public class ShouMai2Imp implements Runnable {
public static int ticket=1000;
@Override
public void run() {
while (true){
if(!shoumai()) break;
}
}
public synchronized static boolean shoumai(){
if (ticket<=0) return false;
System.out.println(Thread.currentThread().getName() + "正在售賣第" + (1000 - --ticket) + "張票");
return true;
}
}3 修飾代碼塊
synchronized(自定義對象) {
//臨界區(qū)
}lock鎖的使用
在java中,可以是使用Object.lock()方法進行加鎖,Object.unlock()方法進行解鎖
public class ShouMai3 extends Thread {
public static int ticket = 200;
private static Lock lock = new ReentrantLock();
public ShouMai3(String name) {
super(name);
}
@Override
public void run() {
while (true) {
lock.lock();
try {
if (ticket > 0) {
System.out.println(getName() + "正在售賣第" + (200 - --ticket) + "張票");
} else {
System.out.println(getName() + ":票已售罄");
break;
}
Thread.sleep(100);
} catch (InterruptedException e) {
e.printStackTrace();
} finally {
lock.unlock();
}
}
}
}6 線程的狀態(tài)
在整個線程的生命周期中,線程的狀態(tài)有以下6種:
New:新建狀態(tài),新創(chuàng)建的線程,此時尚未調(diào)用start()方法;Runnable:運行狀態(tài),運行中的線程,已經(jīng)調(diào)用start()方法,線程正在或即將執(zhí)行run()方法;Blocked:阻塞狀態(tài),運行中的線程,在等待競爭鎖時,被阻塞,暫不執(zhí)行;Waiting:等待狀態(tài),運行中的線程,因為join()等方法調(diào)用,進入等待;Timed Waiting:計時等待狀態(tài),運行中的線程,因為執(zhí)行sleep(等待毫秒值)join(等待毫秒值)等方法,進入計時等待;Terminated:終止狀態(tài),線程已終止,因為run()方法執(zhí)行完畢。
當(dāng)線程啟動后,它可以在Runnable、Blocked、Waiting和Timed Waiting這幾個狀態(tài)之間切換,直到最后變成Terminated狀態(tài),線程終止
1 NEW
新建狀態(tài),新創(chuàng)建的線程,此時尚未調(diào)用start()方法
public class Demo01 {
public static void main(String[] args) {
Thread thread=new Thread();
System.out.println(thread.getState());
}
}2 RUNNABLE
運行狀態(tài),運行中的線程,已經(jīng)調(diào)用start()方法,線程正在或即將執(zhí)行run方法
public class Demo02 {
public static void main(String[] args) {
Thread t1=new Thread(){
@Override
public void run() {
for (int i = 0; i < 10; i++) {
System.out.println("正在執(zhí)行任務(wù)");
}
}
};
//啟動線程
t1.start();
System.out.println(t1.getState());
}
}3 Terminated
終止狀態(tài),線程已終止,因為run()方法執(zhí)行完畢
public class Demo03 {
public static void main(String[] args) throws InterruptedException {
Thread thread = new Thread(new Runnable() {
@Override
public void run() {
System.out.println("正在執(zhí)行任務(wù)");
}
});
//啟動線程
thread.start();
//等待子線程完成任務(wù)
Thread.sleep(500);
//獲取thread線程狀態(tài)
System.out.println(thread.getState());
}
}4 BLOCKED與TIMED_WAITING
- BLOCKED 阻塞狀態(tài),運行中的線程,在等待競爭鎖時,被阻塞,暫不執(zhí)行
- TIMED_WAITING 計時等待狀態(tài),運行中的線程,因為執(zhí)行sleep(等待毫秒值)
sleep() 不釋放鎖資源
wait() 釋放鎖資源 -- Object中的方法,wait()調(diào)用時對象必須要和鎖對象一樣
public class Demo04 {
public static void main(String[] args) throws InterruptedException {
Object lock = new Object();
Runnable runnable = new Runnable() {
@Override
public void run() {
synchronized (lock) {
//如果某條線程獲取到鎖資源,任務(wù)一直執(zhí)行,不釋放鎖資源
while (true) {
//死循環(huán)
try {
Thread.sleep(1000);
// lock.wait(1000);
} catch (InterruptedException e) {
e.printStackTrace();
}
break;
}
}
}
};
Thread thread1 = new Thread(runnable,"線程1");
Thread thread2 = new Thread(runnable,"線程2");
thread1.start();
thread2.start();
//讓主線程休眠500毫秒,讓t1和t2同時競爭一個鎖資源
Thread.sleep(500);
System.out.println(thread1.getName()+":"+thread1.getState());
System.out.println(thread2.getName()+":"+thread2.getState());
}
}5 WAITING
等待狀態(tài),運行中的線程,因為join()等方法調(diào)用,進入等待;
public class Demo05 {
public static void main(String[] args) throws InterruptedException {
Object lock = new Object();
Runnable runnable = new Runnable() {
@Override
public void run() {
synchronized (lock) {
try {
lock.wait();
} catch (InterruptedException e) {
throw new RuntimeException(e);
}
}
}
};
Thread thread = new Thread(runnable);
thread.start();
Thread.sleep(500);
System.out.println(thread.getState());
}
}public class Demo06 {
public static void main(String[] args) throws InterruptedException {
Thread outThread = new Thread(new Runnable() {
public void run() {
System.out.println("外部線程啟動");
//在內(nèi)部又創(chuàng)建了一個線程
Thread innerThread = new Thread(new Runnable() {
@Override
public void run() {
System.out.println("內(nèi)部線程啟動");
//innerThread任務(wù)一直執(zhí)行
while (true){
try {
Thread.sleep(2000);
break;
} catch (InterruptedException e) {
throw new RuntimeException(e);
}
}
}
});
innerThread.start();
try {
innerThread.join(); //innerThread插隊,插隊插到outThread前面,outThread此時處于等待innerThread完成任務(wù)狀態(tài),所以outThread等待
} catch (InterruptedException e) {
throw new RuntimeException(e);
}
}
});
outThread.start(); //線程啟動
//讓主線程休眠,目的時為了讓outThread運行起來
Thread.sleep(100);
//獲取outThread的狀態(tài)
System.out.println(outThread.getState());
}
}7 線程池
概念
線程池內(nèi)部維護了若干個線程,沒有任務(wù)的時候,這些線程都處于等待空閑狀態(tài)。如果有新的線程任務(wù),就分配一個空閑線程執(zhí)行。如果所有線程都處于忙碌狀態(tài),線程池會創(chuàng)建一個新線程進行處理或者放入隊列(工作隊列)中等待。
┌─────┐ execute ┌──────────────────┐ │Task1│─────────>│ThreadPool │ ├─────┤ │┌───────┐┌───────┐│ │Task2│ ││Thread1││Thread2││ ├─────┤ │└───────┘└───────┘│ │Task3│ │┌───────┐┌───────┐│ ├─────┤ ││Thread3││Thread4││ │Task4│ │└───────┘└───────┘│ ├─────┤ └──────────────────┘ │Task5│ ├─────┤ │Task6│ └─────┘ ...
線程池常用方法
- 執(zhí)行無返回值的線程任務(wù):
void execute(Runnable command); - 提交有返回值的線程任務(wù):
Future<T> submit(Callable<T> task); - 關(guān)閉線程池:
void shutdown();或shutdownNow(); - 等待線程池關(guān)閉:
boolean awaitTermination(long timeout, TimeUnit unit);
執(zhí)行線程任務(wù)
execute()只能提交Runnable類型的任務(wù),沒有返回值,而submit()既能提交Runnable類型任務(wù)也能提交Callable類型任務(wù),可以返回Future類型結(jié)果,用于獲取線程任務(wù)執(zhí)行結(jié)果。
execute()方法提交的任務(wù)異常是直接拋出的,而submit()方法是捕獲異常,當(dāng)調(diào)用Future的get()方法獲取返回值時,才會拋出異常。
線程池分類
Java標準庫提供的幾種常用線程池,創(chuàng)建這些線程池的方法都被封裝到Executors工具類中。
- FixedThreadPool:線程數(shù)固定的線程池,使用
Executors.newFixedThreadPool()創(chuàng)建; - CachedThreadPool:線程數(shù)根據(jù)任務(wù)動態(tài)調(diào)整的線程池,使用
Executors.newCachedThreadPool()創(chuàng)建; - SingleThreadExecutor:僅提供一個單線程的線程池,使用
Executors.newSingleThreadExecutor()創(chuàng)建; - ScheduledThreadPool:能實現(xiàn)定時、周期性任務(wù)的線程池,使用
Executors.newScheduledThreadPool()創(chuàng)建;
FixedThreadPool
public class Demo01 {
public static void main(String[] args) throws InterruptedException {
//1.獲取線程池對象
ExecutorService executorService= Executors.newFixedThreadPool(3);
//2.提交任務(wù)給線程池對象
executorService.execute(new MyRunnable("任務(wù)1"));
executorService.execute(new MyRunnable("任務(wù)2"));
executorService.execute(new MyRunnable("任務(wù)3"));
Thread.sleep(100); //主線程休眠
executorService.execute(new MyRunnable("任務(wù)4"));
//3.線程池的關(guān)閉方法 線程池在程序結(jié)束的時候要關(guān)閉。使用shutdown()方法關(guān)閉線程池的時候,它會等待正在執(zhí)行的任務(wù)先完成,然后再關(guān)閉。shutdownNow()會立刻停止正在執(zhí)行的任務(wù);
executorService.shutdown();
// executorService.shutdownNow();
/*
當(dāng)使用awaitTermination()方法時,主線程會處于一種等待的狀態(tài),按照指定的timeout檢查線程池。
第一個參數(shù)指定的是時間,第二個參數(shù)指定的是時間單位(當(dāng)前是秒)。返回值類型為boolean型。
如果等待的時間超過指定的時間,但是線程池中的線程運行完畢,awaitTermination()返回true。
如果等待的時間超過指定的時間,但是線程池中的線程未運行完畢,awaitTermination()返回false。
如果等待時間沒有超過指定時間,則繼續(xù)等待。
*/
while (!executorService.awaitTermination(1, TimeUnit.SECONDS)){
System.out.println("還沒關(guān)閉線程池");
};
System.out.println("已經(jīng)關(guān)閉線程池");
}
}
class MyRunnable implements Runnable{
public String str;
public MyRunnable(String str) {
this.str = str;
}
@Override
public void run() {
System.out.println(Thread.currentThread().getName()+" 開始執(zhí)行"+str);
try {
Thread.sleep(1000);
} catch (InterruptedException e) {
throw new RuntimeException(e);
}
// for (int i = 0; i < 10; i++) {
// System.out.println(Thread.currentThread().getName()+":"+i);
// }
System.out.println(Thread.currentThread().getName()+" 結(jié)束"+str);
}
}CachedThreadPool
public class Demo02 {
public static void main(String[] args) throws InterruptedException {
//創(chuàng)建一個線程數(shù)量無上限的線程池對象
ExecutorService executorService= Executors.newCachedThreadPool();
for (int i = 1; i <= 100; i++) {
executorService.execute(new MyRunnable("任務(wù)"+i));
}
Thread.sleep(2000);
executorService.execute(new MyRunnable("最后一個任務(wù)"));
executorService.shutdown();
}
}SingleThreadExecutor
public class Demo03 {
public static void main(String[] args) {
ExecutorService executorService= Executors.newSingleThreadExecutor();
//循環(huán)的提交任務(wù)
for (int i = 1; i < 10; i++) {
executorService.execute(new MyRunnable("任務(wù)"+i));
}
executorService.shutdown();
}
}ScheduledThreadPool
public class Demo04 {
public static void main(String[] args) {
ScheduledExecutorService executorService= Executors.newScheduledThreadPool(2);
Runnable runnable = new Runnable() {
@Override
public void run() {
System.out.println(Thread.currentThread().getName()+"開始執(zhí)行");
try {
Thread.sleep(3000);
} catch (InterruptedException e) {
throw new RuntimeException(e);
}
System.out.println(Thread.currentThread().getName()+"執(zhí)行結(jié)束");
}
};
//2.提交任務(wù) --延時多久進行任務(wù)的執(zhí)行
//schedule(Runnable command,long delay, TimeUnit unit) 任務(wù)對象 延遲時間 時間單位
// executorService.schedule(runnable,2,TimeUnit.SECONDS);
//scheduleAtFixedRate 延時1秒首次執(zhí)行此任務(wù),每隔2秒進行此任務(wù)的執(zhí)行
executorService.scheduleAtFixedRate(runnable, 1, 2, TimeUnit.SECONDS);
//上一次任務(wù)執(zhí)行完畢后,等待固定的時間間隔,再進行下一次任務(wù)
// executorService.scheduleWithFixedDelay(runnable, 2, 3, TimeUnit.SECONDS);
}
}自定義線程池
在《阿里巴巴java開發(fā)手冊》中指出了線程資源必須通過線程池提供,不允許在應(yīng)用中自行顯示的創(chuàng)建線程,這樣一方面是線程的創(chuàng)建更加規(guī)范,可以合理控制開辟線程的數(shù)量;另一方面線程的細節(jié)管理交給線程池處理,優(yōu)化了資源的開銷。而線程池不允許使用Executors去創(chuàng)建,而要通過ThreadPoolExecutor方式。jdk中Executor框架雖然提供了如newFixedThreadPool()、newSingleThreadExecutor()、newCachedThreadPool()等創(chuàng)建線程池的方法,但都有其局限性,不夠靈活;另外由于前面幾種方法內(nèi)部也是通過new ThreadPoolExecutor方式實現(xiàn),使用ThreadPoolExecutor有助于大家明確線程池的運行規(guī)則,創(chuàng)建符合自己的業(yè)務(wù)場景需要的線程池,避免資源耗盡的風(fēng)險。
必須為線程池中的線程,按照業(yè)務(wù)規(guī)則,進行命名。可以在創(chuàng)建線程池時,使用自定義線程工廠規(guī)范線程命名方式,避免線程使用默認名稱。
如何創(chuàng)建一個線程池?
public ThreadPoolExecutor(int corePoolSize, 核心線程數(shù) >=0
int maximumPoolSize, 最大線程數(shù)(核心+臨時) >corePoolSize
long keepAliveTime, 臨時線程存活時間
TimeUnit unit, 存活的時間單位
BlockingQueue<Runnable> workQueue, 等待隊列
ThreadFactory threadFactory, 線程工廠
RejectedExecutionHandler handler) 拒絕策略
拒絕策略
ThreadPoolExecutor.AbortPolicy() 默認拒絕策略--拋異常
ThreadPoolExecutor.DiscardOldestPolicy() 丟棄等待時間最久的任務(wù)
ThreadPoolExecutor.DiscardPolicy() 丟棄當(dāng)前的任務(wù)
ThreadPoolExecutor.CallerRunsPolicy() 讓當(dāng)前的線程進行任務(wù)的執(zhí)行ExecutorService executorService = new ThreadPoolExecutor(3, 5, 60, TimeUnit.SECONDS, new ArrayBlockingQueue<Runnable>(3), Executors.defaultThreadFactory(), new ThreadPoolExecutor.CallerRunsPolicy());
//提交任務(wù)
executorService.submit(new MyRunnable("任務(wù)1")); //提交一個任務(wù)給executorService對象
executorService.submit(new MyRunnable("任務(wù)2"));
executorService.submit(new MyRunnable("任務(wù)3"));
executorService.submit(new MyRunnable("任務(wù)4")); //核心線程已滿,進入等待隊列
executorService.submit(new MyRunnable("任務(wù)5"));
executorService.submit(new MyRunnable("任務(wù)6"));
executorService.submit(new MyRunnable("任務(wù)7")); //核心線程已滿,等待隊列已滿,進入臨時線程
executorService.submit(new MyRunnable("任務(wù)8"));
executorService.submit(new MyRunnable("任務(wù)9")); //核心線程已滿,等待隊列已滿,臨時線程已滿,拋出異常
executorService.shutdown();自定義線程工廠與拒絕策略
自定義線程工廠
class MyThreadFactory implements ThreadFactory {
//線程池名稱前綴
String namePrefix;
public MyThreadFactory(String namePrefix) {
this.namePrefix = namePrefix;
}
//具備原子性的Integer類型
private AtomicInteger threadNumber = new AtomicInteger(1);
@Override
public Thread newThread(Runnable r) {
return new Thread(r,namePrefix+"線程"+threadNumber.getAndIncrement());
}
}主要實現(xiàn)ThreadFactory接口中的newThread方法,我寫的這個方法也就是對線程池與線程池中的線程進行了重命名
自定義拒絕策略
class MyRejectedExecutionHandler implements RejectedExecutionHandler {
@Override
public void rejectedExecution(Runnable r, ThreadPoolExecutor executor) {
System.out.println("哎呦,達到能執(zhí)行任務(wù)的上限了,(寶?寶)做不到啊");
}
}在這里呢我自定義了一個拒絕策略,當(dāng)有一個線程進入到線程池后,核心線程,線程隊列,臨時線程等都沒有空閑位置時,會直接輸出一段話,線程不會進入線程池之中。
線程池的狀態(tài)
線程池的狀態(tài)分為:RUNNING , SHUTDOWN , STOP , TIDYING , TERMINATED
RUNNING:運行狀態(tài),線程池被一旦被創(chuàng)建,就處于RUNNING狀態(tài),并且線程池中的任務(wù)數(shù)為0。該狀態(tài)的線程池會接收新任務(wù),并處理工作隊列中的任務(wù)。
- 調(diào)用線程池的
shutdown()方法,可以切換到SHUTDOWN關(guān)閉狀態(tài); - 調(diào)用線程池的
shutdownNow()方法,可以切換到STOP停止狀態(tài);
SHUTDOWN :關(guān)閉狀態(tài),該狀態(tài)的線程池不會接收新任務(wù),但會處理工作隊列中的任務(wù);當(dāng)工作隊列為空時,并且線程池中執(zhí)行的任務(wù)也為空時,線程池進入TIDYING狀態(tài);
STOP:停止狀態(tài),該狀態(tài)的線程不會接收新任務(wù),也不會處理阻塞隊列中的任務(wù),而且會中斷正在運行 的任務(wù);線程池中執(zhí)行的任務(wù)為空,進入TIDYING狀態(tài);
TIDYING :整理狀態(tài),該狀態(tài)表明所有的任務(wù)已經(jīng)運行終止,記錄的任務(wù)數(shù)量為0;terminated()執(zhí)行完畢,進入TERMINATED狀態(tài);
TERMINATED : 終止狀態(tài),該狀態(tài)表示線程池徹底關(guān)閉。

線程池的優(yōu)點
- 提高響應(yīng)的速度
- 提高線程的可管理性
- 降低資源消耗(線程執(zhí)行完任務(wù),不銷毀,可以執(zhí)行其他的任務(wù))
線程池核心代碼
線程池相關(guān)的接口和實現(xiàn)類
線程池技術(shù),是由2個核心接口和一組實現(xiàn)類組成。

Executor接口作為線程池技術(shù)中的頂層接口,它的作用是用來定義線程池中,用于提交并執(zhí)行線程任務(wù)的核心方法:exuecte()方法。未來線程池中所有的線程任務(wù),都將由exuecte()方法來執(zhí)行。
ExecutorService接口繼承了Executor接口,擴展了awaitTermination()、submit()、shutdown()等專門用于管理線程任務(wù)的方法。
ExecutorService接口的抽象實現(xiàn)類AbstractExecutorService,為不同的線程池實現(xiàn)類,提供submit()、invokeAll()等部分方法的公共實現(xiàn)。但是由于在不同線程池中的核心方法exuecte()執(zhí)行策略不同,所以在AbstractExecutorService并未提供該方法的具體實現(xiàn)。
AbstractExecutorService有兩個常見的子類ThreadPoolExecutor和ForkJoinPool,用于實現(xiàn)不同的線程池。
ThreadPoolExecutor線程池通過Woker工作線程、BlockingQueue阻塞工作隊列 以及 拒絕策略實現(xiàn)了一個標準的線程池;
ForkJoinPool是一個基于分治思想的線程池實現(xiàn)類,通過分叉(fork)合并(join)的方式,將一個大任務(wù)拆分成多個小任務(wù),并且為每個工作線程提供一個工作隊列,減少競爭,實現(xiàn)并行的線程任務(wù)執(zhí)行方式,所以ForkJoinPool適合計算密集型場景,是ThreadPoolExecutor線程池的一種補充。
ScheduledThreadPoolExecutor類是ThreadPoolExecutor類的子類,按照時間周期執(zhí)行線程任務(wù)的線程池實現(xiàn)類,通常用于作業(yè)調(diào)度相關(guān)的業(yè)務(wù)場景。由于該線程池的工作隊列使用DelayedWorkQueue,這是一個按照任務(wù)執(zhí)行時間進行排序的優(yōu)先級工作隊列,所以這也是ScheduledThreadPoolExecutor線程池能按照時間周期來執(zhí)行線程任務(wù)的主要原因。
工作線程Worker類
每個Woker類的對象,都代表線程池中的一個工作線程。
當(dāng)ThreadPoolExecutor線程池,通過exeute()方法執(zhí)行1個線程任務(wù)時,會調(diào)用addWorker()方法創(chuàng)建一個Woker工作線程對象。并且,創(chuàng)建好的Worker工作線程對象,會被添加到一個HashSet<Worker> workders工作線程集合,統(tǒng)一由線程池進行管理。


通過閱讀源代碼,可以看出Worker類是ThreadPoolExecutor類中定義的一個私有內(nèi)部類,保存了每個Worker工作線程要執(zhí)行的Runnable線程任務(wù)和Thread線程對象。

當(dāng)創(chuàng)建Worker工作線程時,會通過構(gòu)造方法保存Runnable線程任務(wù),同時使用ThreadFactory線程工廠,為該工作線程創(chuàng)建一個Thread線程對象。通過這樣的操作,每個Worker工作線程對象,都將綁定一個真正的Thread線程。

另外,當(dāng)Thread線程被JVM調(diào)度執(zhí)行時,線程將會自動執(zhí)行Worker工作線程對象的run()方法,通過調(diào)用runWorker()方法,最終實現(xiàn)Woker工作線程中所保存的Runnable線程任務(wù)的執(zhí)行。

值得重視的是:當(dāng)Worker工作線程,在第一次執(zhí)行完成線程任務(wù)后,這個Worker工作線程并不會銷毀,而是會以循環(huán)的方式,通過線程池的getTask()方法,獲取阻塞工作隊列中新的Runnable線程任務(wù),并通過當(dāng)前Worker工作線程中所綁定Thread線程,完成新線程任務(wù)的執(zhí)行,從而實現(xiàn)了線程池的中Thread線程的重復(fù)使用。

核心方法:execute()方法
ThreadPoolExecutor線程池中,會通過execute(Runnable command)方法執(zhí)行Runnable類型的線程任務(wù)。
完整實現(xiàn)了Executor接口定義execute()方法,這個方法作用是執(zhí)行一個Runnable類型的線程任務(wù)。整體的執(zhí)行流程是:
- 首先,通過
AtomicInteger類型的ctl對象,獲取線程池的狀態(tài)和工作線程數(shù); - 然后,判斷當(dāng)前線程池中的工作線程數(shù);
- 如果,工作線程的數(shù)量小于核心線程數(shù),則通過
addWorker()方法,創(chuàng)建新的Worker工作線程,并添加至workers工作線程集合; - 如果,工作線程的數(shù)量大于核心線程數(shù),并且線程池處于
RUNNING狀態(tài),那么,線程池會將Runnable類型的線程任務(wù),緩存至workQueue阻塞工作隊列,等待某個空閑工作線程獲取并執(zhí)行該任務(wù); - 如果,
workQueue工作隊列緩存線程任務(wù)失敗,代表工作隊列已滿。那么,線程池會重新通過addWorker()方法,嘗試創(chuàng)建新的工作線程; - 這次創(chuàng)建時,會判斷工作線程數(shù)是否超出最大線程數(shù)。如果沒有超出,會創(chuàng)建新的工作線程;如果已經(jīng)超出,則返回
false,代表創(chuàng)建失??; - 如果創(chuàng)建失敗,線程池執(zhí)行拒絕策略;
public class ThreadPoolExecutor
{
// 線程池執(zhí)行Runnable線程任務(wù)
public void execute(Runnable command) {
if (command == null)
throw new NullPointerException();
// 獲取線程池的狀態(tài)和工作線程數(shù)
int c = ctl.get();
// 工作線程的數(shù)量小于核心線程數(shù)
if (workerCountOf(c) < corePoolSize) {
// 創(chuàng)建新的Worker工作線程
if (addWorker(command, true))
return;
// 創(chuàng)建失敗,重新獲取線程池的狀態(tài)和工作線程數(shù)
c = ctl.get();
}
// 如果線程池處于RUNNING狀態(tài),緩存線程任務(wù)至工作隊列
if (isRunning(c) && workQueue.offer(command)) {
// 任務(wù)緩存成功
// 重新獲取線程池的狀態(tài)和工作線程數(shù)
int recheck = ctl.get();
// 如果線程池不是處于RUNNING狀態(tài),則刪除任務(wù)
if (! isRunning(recheck) && remove(command))
// 執(zhí)行拒絕策略
reject(command);
// 如果工作線程數(shù)等于零
// 通過addWorker()方法檢查線程池狀態(tài)和工作隊列
else if (workerCountOf(recheck) == 0)
addWorker(null, false);
}
// 如果緩存線程任務(wù)至工作隊列
// 嘗試創(chuàng)建新的工作線程
// 創(chuàng)建時,判斷工作線程數(shù)是否超出最大線程數(shù)
// 如果沒有超出,創(chuàng)建成功
// 如果已經(jīng)超出,創(chuàng)建失敗
else if (!addWorker(command, false))
// 執(zhí)行拒絕策略
reject(command);
}
}核心方法:addWorker()方法
在execute()方法的執(zhí)行過程中,會通過addWorker()方法創(chuàng)建一個工作線程,用于執(zhí)行當(dāng)前線程任務(wù)。
閱讀源代碼,會發(fā)現(xiàn),這個方法的整個執(zhí)行過程可以分為兩個部分:檢查線程池的狀態(tài)和工作線程數(shù)量、創(chuàng)建并執(zhí)行工作線程。
1 檢查線程池的狀態(tài)和工作線程數(shù)量
private boolean addWorker(Runnable firstTask, boolean core) {
// 第1部分:檢查線程池的狀態(tài)和工作線程數(shù)量
// 循環(huán)檢查線程池的狀態(tài),直到符合創(chuàng)建工作線程的條件,通過retry標簽break退出
retry:
for (;;) {
// 通過ctl對象,獲取當(dāng)前線程池的運行狀態(tài)
int c = ctl.get();
int rs = runStateOf(c);
// 如果線程池處于開始關(guān)閉的狀態(tài)(獲取線程任務(wù)為空,同時工作隊列不等于空)
// 則工作線程創(chuàng)建失敗
if (rs >= SHUTDOWN &&
! (rs == SHUTDOWN &&
firstTask == null &&
! workQueue.isEmpty()))
return false;
// 檢查工作線程數(shù)量
for (;;) {
// 通過ctl對象,獲取當(dāng)前線程池中工作線程數(shù)量
int wc = workerCountOf(c);
// 工作線程數(shù)量如果超出最大容量或者核心線程數(shù)(最大線程數(shù))
// 則工作線程創(chuàng)建失敗
if (wc >= CAPACITY ||
wc >= (core ? corePoolSize : maximumPoolSize))
return false;
// 通過ctl對象,將當(dāng)前工作線程數(shù)量+1,并通過retry標簽break退出外層循環(huán)
if (compareAndIncrementWorkerCount(c))
break retry;
// 再次獲取線程池狀態(tài),檢查是否發(fā)生變化
c = ctl.get(); // Re-read ctl
if (runStateOf(c) != rs)
continue retry;
// else CAS failed due to workerCount change; retry inner loop
}
}
// 第2部分:創(chuàng)建并執(zhí)行工作線程....
}2 創(chuàng)建并執(zhí)行工作線程
private boolean addWorker(Runnable firstTask, boolean core) {
// 第1部分:檢查線程池的狀態(tài)和工作線程數(shù)量....
// 第2部分:創(chuàng)建并執(zhí)行工作線程....
boolean workerStarted = false; // 工作線程是否已經(jīng)啟動
boolean workerAdded = false; // 工作線程是否已經(jīng)保存
Worker w = null;
try {
// 創(chuàng)建新工作線程,并通過線程工廠創(chuàng)建Thread線程
w = new Worker(firstTask);
// 獲取新工作線程的Thread線程對象,用于啟動真正的線程
final Thread t = w.thread;
if (t != null) {
// 獲取線程池的ReentrantLock主鎖對象
// 確保在添加和啟動線程時的同步與安全
final ReentrantLock mainLock = this.mainLock;
mainLock.lock();
try {
// 檢查線程池狀態(tài)
int rs = runStateOf(ctl.get());
if (rs < SHUTDOWN ||
(rs == SHUTDOWN && firstTask == null)) {
// 檢查Thread線程對象的狀態(tài)是否已經(jīng)處于啟動狀態(tài)
if (t.isAlive())
throw new IllegalThreadStateException();
// 保存工作線程
workers.add(w);
// 記錄線程池曾經(jīng)達到過的最大工作線程數(shù)量
int s = workers.size();
if (s > largestPoolSize)
largestPoolSize = s;
workerAdded = true;
}
} finally {
mainLock.unlock();
}
// 添加工作線程后,正式啟動線程
if (workerAdded) {
t.start();
workerStarted = true;
}
}
} finally {
if (! workerStarted)
addWorkerFailed(w);
}
// 返回線程啟動狀態(tài)
return workerStarted;
}到此這篇關(guān)于JAVA中關(guān)于多線程的學(xué)習(xí)和使用的文章就介紹到這了,更多相關(guān)java多線程使用內(nèi)容請搜索腳本之家以前的文章或繼續(xù)瀏覽下面的相關(guān)文章希望大家以后多多支持腳本之家!
相關(guān)文章
Java實現(xiàn)對兩個List快速去重并排序操作示例
這篇文章主要介紹了Java實現(xiàn)對兩個List快速去重并排序操作,結(jié)合實例形式較為詳細的分析了Java針對list的遍歷、去重、排序相關(guān)操作技巧與注意事項,需要的朋友可以參考下2018-07-07
SpringBoot-MyBatis-plus實體類中常用的注解用法
這篇文章主要介紹了SpringBoot-MyBatis-plus實體類中常用的注解用法,具有很好的參考價值,希望對大家有所幫助,如有錯誤或未考慮完全的地方,望不吝賜教2025-06-06
java POI 如何實現(xiàn)Excel單元格內(nèi)容換行
這篇文章主要介紹了java POI 如何實現(xiàn)Excel單元格內(nèi)容換行的操作,具有很好的參考價值,希望對大家有所幫助。如有錯誤或未考慮完全的地方,望不吝賜教2021-07-07
Intellij idea使用Statistic統(tǒng)計代碼行數(shù)的方法
這篇文章主要介紹了Intellij idea使用Statistic統(tǒng)計代碼行數(shù)的方法,本文通過圖文并茂的形式給大家介紹的非常詳細,對大家的學(xué)習(xí)或工作具有一定的參考借鑒價值,需要的朋友可以參考下2021-04-04

