java線程的使用
1. Java多線程概述
下面我們看下Java的多線程
- 作者: 博學(xué)谷狂野架構(gòu)師
- GitHub:GitHub地址 (有我精心準(zhǔn)備的130本電子書PDF)
只分享干貨盗冷、不吹水,讓我們一起加油!??
1.1 java天生就是多線程的
一個(gè)Java程序從main()方法開始執(zhí)行疆柔,然后按照既定的代碼邏輯執(zhí)行丧荐,看似沒有其他線程參與窜管,但實(shí)際上Java程序天生就是多線程程序揩尸,因?yàn)閳?zhí)行main()方法的是一個(gè)名稱為main的線程宛渐。
1.1.1 代碼案例
執(zhí)行下面的代碼
package chapter01;
import java.lang.management.ManagementFactory;
import java.lang.management.ThreadInfo;
import java.lang.management.ThreadMXBean;
public class ThreadDemo {
/**
* 打印出java中所有的線程
* @param args
*/
public static void main(String[] args) {
ThreadMXBean threadMXBean = ManagementFactory.getThreadMXBean();
ThreadInfo[] threadInfos = threadMXBean.dumpAllThreads(false, false);
for (ThreadInfo info : threadInfos) {
System.out.println("[" + info.getThreadId() + "]" + info.getThreadName());
}
System.out.println(Thread.activeCount());
}
}
執(zhí)行后我們會(huì)發(fā)現(xiàn)打印了如下的線程信息界斜,說(shuō)明Java本身就是多線程的
[6] Monitor Ctrl-Break //監(jiān)控Ctrl-Break中斷信號(hào)的
[5] Attach Listener //內(nèi)存dump仿耽,線程dump,類信息統(tǒng)計(jì)各薇,獲取系統(tǒng)屬性等
[4] Signal Dispatcher // 分發(fā)處理發(fā)送給JVM信號(hào)的線程
[3] Finalizer // 調(diào)用對(duì)象finalize方法的線程
[2] Reference Handler//清除Reference的線程
[1] main //main線程项贺,用戶程序入口
1.2 線程的生命周期
Thread類提供了六種狀態(tài)
1.2.1 新建狀態(tài)(NEW)
當(dāng)線程對(duì)象對(duì)創(chuàng)建后,即進(jìn)入了新建狀態(tài)峭判,如:
Thread thread1 = new MyThread();
1.2.2 運(yùn)行狀態(tài)(RUNNABLE)
Java線程中將就緒(ready)和運(yùn)行中(running)兩種狀態(tài)籠統(tǒng)的稱為“運(yùn)行”开缎。
線程對(duì)象創(chuàng)建后,其他線程(比如main線程)調(diào)用了該對(duì)象的start()方法林螃。該狀態(tài)的線程位于可運(yùn)行線程池中奕删,等待被線程調(diào)度選中,獲取CPU的使用權(quán)疗认,此時(shí)處于就緒狀態(tài)(ready)完残。就緒狀態(tài)的線程在獲得CPU時(shí)間片后變?yōu)檫\(yùn)行中狀態(tài)(running)伏钠。
1.2.3 阻塞狀態(tài)(BLOCKED)
處于運(yùn)行狀態(tài)中的線程由于某種原因,暫時(shí)放棄對(duì)CPU的使用權(quán)谨设,停止執(zhí)行熟掂,此時(shí)進(jìn)入阻塞狀態(tài),直到其進(jìn)入到就緒狀態(tài)扎拣,才 有機(jī)會(huì)再次被CPU調(diào)用以進(jìn)入到運(yùn)行狀態(tài)
1.2.4 等待狀態(tài)(WAITING)
進(jìn)入該狀態(tài)的線程需要等待其他線程做出一些特定動(dòng)作(通知或中斷)
1.2.5 超時(shí)等待(TIMED_WAITING)
該狀態(tài)不同于WAITING打掘,它可以在指定的時(shí)間后自行返回。
1.2.6 終止?fàn)顟B(tài)(TERMINATED)
線程執(zhí)行完了或者因異常退出了run()方法鹏秋,該線程結(jié)束生命周
2. 線程的創(chuàng)建方式
創(chuàng)建線程的方式有兩種
2.1 繼承Thread類
我們可以通過(guò)繼承Thread類來(lái)使用Java的多線程
package chapter01.create;
/**
* 創(chuàng)建一個(gè)線程并運(yùn)行
*/
public class threadTest extends Thread {
@Override
public void run() {
System.out.println("線程運(yùn)行");
}
public static void main(String[] args) {
threadTest threadCreate = new threadTest();
threadCreate.start();
}
}
2.2 實(shí)現(xiàn) Runnable 接口
實(shí)現(xiàn)Runnable 接口并交給Thread進(jìn)行運(yùn)行
package chapter01.create;
public class RunnableTest implements Runnable {
@Override
public void run() {
System.out.println("線程運(yùn)行");
}
public static void main(String[] args) {
RunnableTest runnableTest = new RunnableTest();
Thread thread = new Thread(runnableTest);
thread.start();
}
}
2.3 Thread和Runnable的區(qū)別
Thread才是Java里對(duì)線程的唯一抽象尊蚁,Runnable只是對(duì)任務(wù)(業(yè)務(wù)邏輯)的抽象,Thread可以接受任意一個(gè)Runnable的實(shí)例并執(zhí)行侣夷。
2.3.1 注意事項(xiàng)
有些面試官會(huì)說(shuō)實(shí)現(xiàn)線程的方式有三種 Thread横朋、Runnable 以及Callable,但是按照java源碼中Thread類中的注釋說(shuō)的實(shí)現(xiàn)類的防止只有兩種百拓,我們可以看下啊Thread類的源碼
3. 線程的終止方式
下面我們看下線程的終止方式有哪些
3.1 線程自然終止
要么是run執(zhí)行完成了琴锭,要么是拋出了一個(gè)未處理的異常導(dǎo)致線程提前結(jié)束。
3.2 stop終止
暫停衙传、恢復(fù)和停止操作對(duì)應(yīng)在線程Thread的API就是suspend()决帖、resume()和stop(),但是這些API是過(guò)期的蓖捶,也就是不建議使用的 茶敏。
3.2.1 為什么不建議使用
不建議使用的原因主要有:以suspend()方法為例武翎,在調(diào)用后,線程不會(huì)釋放已經(jīng)占有的資源(比如鎖),而是占有著資源進(jìn)入睡眠狀態(tài)浓恳,這樣容易引發(fā)死鎖問(wèn)題党瓮。
同樣萤捆,stop()方法在終結(jié)一個(gè)線程時(shí)不會(huì)保證線程的資源正常釋放叹哭,通常是沒有給予線程完成資源釋放工作的機(jī)會(huì),因此會(huì)導(dǎo)致程序可能工作在不確定狀態(tài)下帝火,正因?yàn)閟uspend()溜徙、resume()和stop()方法帶來(lái)的副作用,這些方法才被標(biāo)注為不建議使用的過(guò)期方法犀填。
3.3 中斷終止
推薦使用中斷的方式來(lái)終止線程
安全的中止則是其他線程通過(guò)調(diào)用某個(gè)線程A的**interrupt()**方法對(duì)其進(jìn)行中斷操作,蠢壹,中斷好比其他線程對(duì)該線程打了個(gè)招呼,“A宏浩,你要中斷了”知残,不代表線程A會(huì)立即停止自己的工作,同樣的A線程完全可以不理會(huì)這種中斷請(qǐng)求,因?yàn)閖ava里的線程是協(xié)作式的求妹,不是搶占式的乏盐,線程通過(guò)檢查自身的中斷標(biāo)志位是否被置為true來(lái)進(jìn)行響應(yīng)。
線程通過(guò)方法**isInterrupted()**來(lái)進(jìn)行判斷是否被中斷制恍,也可以調(diào)用靜態(tài)方法**Thread.interrupted()**來(lái)進(jìn)行判斷當(dāng)前線程是否被中斷父能,不過(guò)Thread.interrupted()會(huì)同時(shí)將中斷標(biāo)識(shí)位改寫為false。
如果一個(gè)線程處于了阻塞狀態(tài)(如線程調(diào)用了thread.sleep净神、thread.join何吝、thread.wait等),則在線程在檢查中斷標(biāo)示時(shí)如果發(fā)現(xiàn)中斷標(biāo)示為true鹃唯,則會(huì)在這些阻塞方法調(diào)用處拋出InterruptedException異常爱榕,并且在拋出異常后會(huì)立即將線程的中斷標(biāo)示位清除,即重新設(shè)置為false坡慌。
3.3.1 代碼案例
package chapter01.stop;
/**
* 使用Runable的中斷
*/
public class ThreadInterrupted implements Runnable {
private int i = 0;
@Override
public void run() {
while (!Thread.currentThread().isInterrupted()) {
i++;
System.out.println("線程正在運(yùn)行");
try {
Thread.sleep(100);
} catch (InterruptedException e) {
e.printStackTrace();
}
if (i > 10) {
Thread.currentThread().interrupt();
}
System.out.println(Thread.currentThread().isInterrupted());
}
}
public static void main(String[] args) {
new Thread(new ThreadInterrupted()).start();
}
}
3.3.2 注意事項(xiàng)
不建議自定義一個(gè)取消標(biāo)志位來(lái)中止線程的運(yùn)行黔酥。
因?yàn)閞un方法里有阻塞調(diào)用時(shí)會(huì)無(wú)法很快檢測(cè)到取消標(biāo)志,線程必須從阻塞調(diào)用返回后洪橘,才會(huì)檢查這個(gè)取消標(biāo)志跪者。這種情況下,使用中斷會(huì)更好熄求,狀態(tài)位如果跨線程改變狀態(tài)必須使用volatile來(lái)保證可見性渣玲。
- 一般的阻塞方法,如sleep等本身就支持中斷的檢查弟晚。
- 檢查中斷位的狀態(tài)和檢查取消標(biāo)志位沒什么區(qū)別忘衍,用中斷位的狀態(tài)還可以避免聲明取消標(biāo)志位,減少資源的消耗指巡。
注意:處于死鎖狀態(tài)的線程無(wú)法被中斷
3.4 狀態(tài)位終止
狀態(tài)位就是用一個(gè)變量來(lái)標(biāo)識(shí)線程的運(yùn)行狀態(tài)淑履,如果需要停止了就就改變狀態(tài)位的狀態(tài)隶垮,但是狀態(tài)位一定要使用 volatile 關(guān)鍵字藻雪,否在可能造成多線程狀態(tài)下的不可見
3.4.1 代碼案例
3.4.1.1 使用volatile
使用volatile可以正常中斷線程
package chapter01.stop;
public class ThreadFlag implements Runnable {
protected long i = 0;
private volatile boolean flag = false;
@Override
public void run() {
while (!flag) {
i++;
if (i > 100000) {
flag = true;
}
System.out.println(i);
}
}
public static void main(String[] args) throws InterruptedException {
ThreadFlag threadFlag = new ThreadFlag();
new Thread(threadFlag).start();
}
}
3.4.1.2 不使用volatile
不使用volatile,會(huì)導(dǎo)致線程不能正常中斷
package chapter01.stop;
public class ThreadInvisible implements Runnable {
protected long i = 0;
/**
* 不加volatile 會(huì)造成多線程的變量不可見狸吞,判斷不會(huì)停止
*/
public boolean flag = false;
@Override
public void run() {
while (!flag) {
i++;
}
}
public static void main(String[] args) throws InterruptedException {
ThreadInvisible threadFlag = new ThreadInvisible();
new Thread(threadFlag).start();
Thread.sleep(1000);
threadFlag.flag = true;
}
}
3. run和start的區(qū)別
Thread類是Java里對(duì)線程概念的抽象勉耀,可以這樣理解:我們通過(guò)new Thread()其實(shí)只是new出一個(gè)Thread的實(shí)例,還沒有操作系統(tǒng)中真正的線程掛起鉤來(lái)蹋偏。只有執(zhí)行了start()方法后便斥,才實(shí)現(xiàn)了真正意義上的啟動(dòng)線程。
**start()**方法讓一個(gè)線程進(jìn)入就緒隊(duì)列等待分配cpu威始,分到cpu后才調(diào)用實(shí)現(xiàn)的run()方法枢纠,start()方法不能重復(fù)調(diào)用,如果重復(fù)調(diào)用會(huì)拋出異常黎棠。
而run方法是業(yè)務(wù)邏輯實(shí)現(xiàn)的地方晋渺,本質(zhì)上和任意一個(gè)類的任意一個(gè)成員方法并沒有任何區(qū)別镰绎,可以重復(fù)執(zhí)行,也可以被單獨(dú)調(diào)用木西。
4. 其他的線程相關(guān)方法
下面我們看下線程的其他方法有哪些
4.1 sleep方法
使當(dāng)前線程(即調(diào)用該方法的線程)暫停執(zhí)行一段時(shí)間畴栖,讓其他線程有機(jī)會(huì)繼續(xù)執(zhí)行,但它并不釋放對(duì)象鎖,也不釋放占用的資源八千。
也就是說(shuō)如果有synchronized同步快吗讶,其他線程仍然不能訪問(wèn)共享數(shù)據(jù),注意該方法要捕捉異常恋捆。
例如有兩個(gè)線程同時(shí)執(zhí)行(沒有synchronized)一個(gè)線程優(yōu)先級(jí)為MAX_PRIORITY照皆,另一個(gè)為MIN_PRIORITY,如果沒有Sleep()方法沸停,只有高優(yōu)先級(jí)的線程執(zhí)行完畢后纵寝,低優(yōu)先級(jí)的線程才能夠執(zhí)行;但是高優(yōu)先級(jí)的線程sleep(500)后星立,低優(yōu)先級(jí)就有機(jī)會(huì)執(zhí)行了爽茴。
總之,sleep()可以使低優(yōu)先級(jí)的線程得到執(zhí)行的機(jī)會(huì)绰垂,當(dāng)然也可以讓同優(yōu)先級(jí)室奏、高優(yōu)先級(jí)的線程有執(zhí)行的機(jī)會(huì)。
4.1.1 代碼案例
package chapter01.method;
import util.ThreadUtils;
import java.util.concurrent.TimeUnit;
public class ThreadSleep {
public static void main(String[] args) {
sleep1();
sleep2();
}
public static void sleep1() {
Thread thread = new Thread(() -> {
System.out.println("xxxxxxxxxxxxxxxxxx");
ThreadUtils.sleep(1, TimeUnit.SECONDS);
});
//thread.setPriority(100);
thread.start();
}
public static void sleep2() {
Thread thread = new Thread(() -> {
System.out.println("xxxxxxxxxxxxxxxxxx");
ThreadUtils.sleep(1, TimeUnit.SECONDS);
});
//thread.set
thread.start();
}
}
4.2 join方法
把指定的線程加入到當(dāng)前線程劲装,可以將兩個(gè)交替執(zhí)行的線程合并為順序執(zhí)行 胧沫。
比如在線程B中調(diào)用了線程A的Join()方法,直到線程A執(zhí)行完畢后占业,才會(huì)繼續(xù)執(zhí)行線程B
注意: t.join()方法只會(huì)使主線程進(jìn)入等待池并等待t線程執(zhí)行完畢后才會(huì)被喚醒绒怨。并不影響同一時(shí)刻處在運(yùn)行狀態(tài)的其他線程
4.2.1 代碼案例
package chapter01.method;
import util.ThreadUtils;
import java.util.concurrent.TimeUnit;
public class ThreadJoin {
public static void main(String[] args) {
Thread thread1 = new Thread(()->{
for(int i=0;i<10;i++) {
System.out.println("111111111111111");
ThreadUtils.sleep(1, TimeUnit.SECONDS);
}
});
Thread thread2 = new Thread(()->{
try {
thread1.join();
} catch (InterruptedException e) {
e.printStackTrace();
}
for(int i=0;i<10;i++){
System.out.println("2222222222222");
ThreadUtils.sleep(1, TimeUnit.SECONDS);
}
});
thread1.start();
thread2.setDaemon(true);
thread2.start();
}
}
4.3 yield方法
yield()應(yīng)該做的是讓當(dāng)前運(yùn)行線程回到可運(yùn)行狀態(tài),以允許具有相同優(yōu)先級(jí)的其他線程獲得運(yùn)行機(jī)會(huì)谦疾。
因此南蹂,使用yield()的目的是讓相同優(yōu)先級(jí)的線程之間能適當(dāng)?shù)妮嗈D(zhuǎn)執(zhí)行。但是念恍,實(shí)際中無(wú)法保證yield()達(dá)到讓步目的六剥,因?yàn)樽尣降木€程還有可能被線程調(diào)度程序再次選中。
yield()是將線程從運(yùn)行狀態(tài)變更為就緒狀態(tài)峰伙,不會(huì)變?yōu)榈却?睡眠/阻塞狀態(tài)疗疟,注意:yeid方法是**不釋放資源的**。
4.3.1 代碼案例
package chapter01.method;
import util.ThreadUtils;
import java.util.concurrent.TimeUnit;
public class ThreadYield {
public static void main(String[] args) {
Thread thread1 = new Thread(()->{
// Thread.yield();
System.out.println("1111111111");
ThreadUtils.sleep(1, TimeUnit.SECONDS);
});
Thread thread2 = new Thread(()->{
System.out.println("22222222222");
ThreadUtils.sleep(1, TimeUnit.SECONDS);
});
thread1.start();
thread2.start();
}
}
5 線程的優(yōu)先級(jí)
在Java線程中瞳氓,通過(guò)一個(gè)整型成員變量priority來(lái)控制優(yōu)先級(jí)策彤,優(yōu)先級(jí)的范圍從1~10
在線程構(gòu)建的時(shí)候可以通過(guò)setPriority(int)方法來(lái)修改優(yōu)先級(jí),默認(rèn)優(yōu)先級(jí)是5,優(yōu)先級(jí)高的線程分配時(shí)間片的數(shù)量要多于優(yōu)先級(jí)低的線程店诗。
設(shè)置線程優(yōu)先級(jí)時(shí)叽赊,針對(duì)頻繁阻塞(休眠或者I/O操作)的線程需要設(shè)置較高優(yōu)先級(jí),而偏重計(jì)算(需要較多CPU時(shí)間或者偏運(yùn)算)的線程則設(shè)置較低的優(yōu)先級(jí)必搞,確保處理器不會(huì)被獨(dú)占必指,在不同的JVM以及操作系統(tǒng)上,線程規(guī)劃會(huì)存在差異恕洲,有些操作系統(tǒng)甚至?xí)雎詫?duì)線程優(yōu)先級(jí)的設(shè)定
5.1 代碼案例
package chapter01.priority;
import util.ThreadUtils;
import java.util.concurrent.TimeUnit;
/**
* 執(zhí)行的時(shí)候優(yōu)先級(jí)越高 越容易執(zhí)行到該線程
*/
public class ThreadPriority {
public static void main(String[] args) {
Thread thread1 = new Thread(() -> {
while (true) {
System.out.println("1111111111111111");
ThreadUtils.sleep(1, TimeUnit.SECONDS);
}
});
Thread thread2 = new Thread(() -> {
while (true) {
System.out.println("22222222222222222");
ThreadUtils.sleep(1, TimeUnit.SECONDS);
}
});
thread1.setPriority(1);
thread2.setPriority(7);
thread1.start();
thread2.start();
}
}
6. 守護(hù)線程
Daemon(守護(hù))線程是一種支持型線程塔橡,因?yàn)樗饕挥米鞒绦蛑泻笈_(tái)調(diào)度以及支持性工作。
這意味著霜第,當(dāng)一個(gè)Java虛擬機(jī)中不存在**非**Daemon線程的時(shí)候葛家,Java虛擬機(jī)將會(huì)退出,可以通過(guò)調(diào)用`Thread.setDaemon(true)`將線程設(shè)置為Daemon線程泌类,我們一般用不上癞谒,比如垃圾回收線程就是Daemon線程
Daemon線程被用作完成支持性工作,但是在Java虛擬機(jī)退出時(shí)Daemon線程中的finally塊并不一定會(huì)執(zhí)行刃榨,在構(gòu)建Daemon線程時(shí)弹砚,不能依靠finally塊中的內(nèi)容來(lái)確保執(zhí)行關(guān)閉或清理資源的邏輯,也可以理解為等程序的**所有的用戶線程結(jié)束**后枢希,守護(hù)線程也將結(jié)束桌吃。
注意:**守護(hù)線程必須在start之前設(shè)置,否則會(huì)報(bào)錯(cuò)**苞轿。
6.1 代碼案例
package chapter01.daemon;
import util.ThreadUtils;
import java.util.concurrent.TimeUnit;
public class ThreadDaemon {
public static void main(String[] args) {
Thread thread1 = new Thread(() -> {
while (true) {
System.out.println("1111111111111");
ThreadUtils.sleep(1, TimeUnit.SECONDS);
}
});
Thread thread2 = new Thread(() -> {
for (int i = 0; i < 10; i++) {
System.out.println("22222222222");
ThreadUtils.sleep(1, TimeUnit.SECONDS);
}
});
/* Thread thread3 = new Thread(() -> {
while (true) {
System.out.println("3333333333333");
ThreadUtils.sleep(1, TimeUnit.SECONDS);
}
});*/
thread1.setDaemon(true);
thread1.start();
thread2.start();
//thread3.start();
}
}
本文由
傳智教育博學(xué)谷狂野架構(gòu)師
教研團(tuán)隊(duì)發(fā)布茅诱。如果本文對(duì)您有幫助,歡迎
關(guān)注
和點(diǎn)贊
搬卒;如果您有任何建議也可留言評(píng)論
或私信
瑟俭,您的支持是我堅(jiān)持創(chuàng)作的動(dòng)力。轉(zhuǎn)載請(qǐng)注明出處契邀!