Java高并發(fā)系列——線程的基本操作
線程的基本操作
新建線程
start方法是啟動(dòng)一個(gè)線程,run方法只會(huì)在當(dāng)前線程中串行的執(zhí)行run方法中的代碼。
我們可以通過(guò)繼承Thread類,然后重寫(xiě)run方法,來(lái)自定義一個(gè)線程。但考慮java是單繼承的,從擴(kuò)展性上來(lái)說(shuō),我們實(shí)現(xiàn)一個(gè)接口來(lái)自定義一個(gè)線程更好一些,java中剛好提供了Runnable接口來(lái)自定義一個(gè)線程。實(shí)現(xiàn)Runnable接口是比較常見(jiàn)的做法,也是推薦的做法。
終止線程——stop()方法已廢棄
stop方法為何會(huì)被廢棄而不推薦使用?stop方法過(guò)于暴力,強(qiáng)制把正在執(zhí)行的方法停止了。
大家是否遇到過(guò)這樣的場(chǎng)景:聽(tīng)著歌寫(xiě)著代碼突然斷電了。線程正在運(yùn)行過(guò)程中,被強(qiáng)制結(jié)束了,可能會(huì)導(dǎo)致一些意想不到的后果。可以給大家發(fā)送一個(gè)通知,告訴大家保存一下手頭的工作,將電腦關(guān)閉。
線程中斷——interrupt()正確的中斷線程方法
線程中斷并不會(huì)使線程立即退出,而是給線程發(fā)送一個(gè)通知,告知目標(biāo)線程,有人希望你退出了!至于目標(biāo)線程接收到通知之后如何處理,則完全由目標(biāo)線程自己決定,這點(diǎn)很重要,如果中斷后,線程立即無(wú)條件退出,我們又會(huì)到stop方法的老問(wèn)題。
Thread提供了3個(gè)與線程中斷有關(guān)的方法,這3個(gè)方法容易混淆,大家注意下:
public void interrupt() //中斷線程
public boolean isInterrupted() //判斷線程是否被中斷
public static boolean interrupted() //判斷線程是否被中斷,并清除當(dāng)前中斷狀態(tài)
interrupt()方法是一個(gè)實(shí)例方法,它通知目標(biāo)線程中斷,也就是設(shè)置中斷標(biāo)志位為true,中斷標(biāo)志位表示當(dāng)前線程已經(jīng)被中斷了。
isInterrupted()方法也是一個(gè)實(shí)例方法,它判斷當(dāng)前線程是否被中斷(通過(guò)檢查中斷標(biāo)志位)。
interrupted()是一個(gè)靜態(tài)方法,返回boolean類型,也是用來(lái)判斷當(dāng)前線程是否被中斷,但是同時(shí)會(huì)清除當(dāng)前線程的中斷標(biāo)志位的狀態(tài)。
Q:通過(guò)變量控制和線程自帶的interrupt方法來(lái)中斷線程有什么區(qū)別呢?
A:如果一個(gè)線程調(diào)用了sleep方法,一直處于休眠狀態(tài),通過(guò)變量控制,是不能中斷線程么,因?yàn)榇藭r(shí)線程處于睡眠狀態(tài)無(wú)法執(zhí)行變量控制語(yǔ)句,此時(shí)只能使用線程提供的interrupt方法來(lái)中斷線程了。
實(shí)例:
public static void main(String[] args) throws InterruptedException {
Thread t1 = new Thread() {
@Override
public void run() {
while (true) {
try {
TimeUnit.SECONDS.sleep(20);
} catch (InterruptedException e) {
//sleep方法由于中斷而拋出異常之后,線程的中斷標(biāo)志會(huì)被清除(置為false),所以在異常中需要執(zhí)行this.interrupt()方法,將中斷標(biāo)志位置為true
this.interrupt();
System.out.println("exception:"+ e.getMessage());
}
System.out.println(Thread.currentThread().getName() + " in the end");
break;
}
}
};
t1.setName("interrupt thread");
t1.start();
TimeUnit.SECONDS.sleep(1);
//調(diào)用interrupt()方法之后,線程的sleep方法將會(huì)拋出 InterruptedException: sleep interrupted異常。
t1.interrupt();
}
錯(cuò)誤寫(xiě)法:

注意:sleep方法由于中斷而拋出異常之后,線程的中斷標(biāo)志會(huì)被清除(置為false),所以在異常中需要執(zhí)行this.interrupt()方法,將中斷標(biāo)志位置為true
等待(wait)和通知(notify)
為了支持多線程之間的協(xié)作,JDK提供了兩個(gè)非常重要的方法:等待wait()方法和通知notify()方法。這2個(gè)方法并不是在Thread類中的,而是在Object類中定義的。這意味著所有的對(duì)象都可以調(diào)用者兩個(gè)方法。
(即只有這個(gè)對(duì)象是被當(dāng)成鎖來(lái)作為多線程之間的協(xié)作對(duì)象,那么在同步代碼塊中,線程之間就是通過(guò)等待wait()方法和通知notify()方法協(xié)作。)
public final void wait() throws InterruptedException;
public final native void notify();
如果一個(gè)線程調(diào)用了object.wait()方法,那么它就會(huì)進(jìn)出object對(duì)象的等待隊(duì)列。這個(gè)隊(duì)列中,可能會(huì)有多個(gè)線程,因?yàn)橄到y(tǒng)可能運(yùn)行多個(gè)線程同時(shí)等待某一個(gè)對(duì)象。當(dāng)object.notify()方法被調(diào)用時(shí),它就會(huì)從這個(gè)隊(duì)列中隨機(jī)選擇一個(gè)線程,并將其喚醒。這里希望大家注意一下,這個(gè)選擇是不公平的,并不是先等待線程就會(huì)優(yōu)先被選擇,這個(gè)選擇完全是隨機(jī)的。 nofiyAll()方法,它和notify()方法的功能類似,不同的是,它會(huì)喚醒在這個(gè)等待隊(duì)列中所有等待的線程,而不是隨機(jī)選擇一個(gè)。
這里強(qiáng)調(diào)一點(diǎn),Object.wait()方法并不能隨便調(diào)用。它必須包含在對(duì)應(yīng)的synchronize語(yǔ)句塊中,無(wú)論是wait()方法或者notify()方法都需要首先獲取目標(biāo)獨(dú)享的一個(gè)監(jiān)視器。
等待wait()方法和通知notify()方法工作過(guò)程:

wait()方法和nofiy()方法的工作流程細(xì)節(jié):

圖中其中T1和T2表示兩個(gè)線程。T1在正確執(zhí)行wait()方法前,必須獲得object對(duì)象的監(jiān)視器。而wait()方法在執(zhí)行后,會(huì)釋放這個(gè)監(jiān)視器。這樣做的目的是使其他等待在object對(duì)象上的線程不至于因?yàn)門1的休眠而全部無(wú)法正常執(zhí)行。
線程T2在notify()方法調(diào)用前,也必須獲得object對(duì)象的監(jiān)視器。所幸,此時(shí)T1已經(jīng)釋放了這個(gè)監(jiān)視器,因此,T2可以順利獲得object對(duì)象的監(jiān)視器。接著,T2執(zhí)行了notify()方法嘗試喚醒一個(gè)等待線程,這里假設(shè)喚醒了T1。T1在被喚醒后,要做的第一件事并不是執(zhí)行后續(xù)代碼,而是要嘗試重新獲得object對(duì)象的監(jiān)視器,而這個(gè)監(jiān)視器也正是T1在wait()方法執(zhí)行前所持有的那個(gè)。如果暫時(shí)無(wú)法獲得,則T1還必須等待這個(gè)監(jiān)視器。當(dāng)監(jiān)視器順利獲得后,T1才可以在真正意義上繼續(xù)執(zhí)行。
注意:Object.wait()方法和Thread.sleeep()方法都可以讓現(xiàn)場(chǎng)等待若干時(shí)間。除wait()方法可以被喚醒外,另外一個(gè)主要的區(qū)別就是wait()方法會(huì)釋放目標(biāo)對(duì)象的鎖,而Thread.sleep()方法不會(huì)釋放鎖。
示例:
public class WaitNotifyTest {
public static Object lock = new Object();
public static void main(String[] args) {
new T1("Thread-1").start();
new T2("Thread-2").start();
}
static class T1 extends Thread {
public T1(String name) {
super(name);
}
@Override
public void run() {
synchronized (lock) {
System.out.println(this.getName() + " start");
try {
System.out.println(this.getName() + " wait");
lock.wait();
} catch (InterruptedException e) {
e.printStackTrace();
}
System.out.println(this.getName() + " end");
}
}
}
static class T2 extends Thread {
public T2(String name) {
super(name);
}
@Override
public void run() {
synchronized (lock) {
System.out.println(this.getName() + " start");
System.out.println(this.getName() + " notify");
lock.notify();
System.out.println(this.getName() + " end");
try {
TimeUnit.SECONDS.sleep(2);
} catch (InterruptedException e) {
e.printStackTrace();
}
System.out.println(this.getName() + " end,2 second later");
}
}
}
}
輸出:
Thread-1 start
Thread-1 wait
Thread-2 start
Thread-2 notify
Thread-2 end
Thread-2 end,2 second later
Thread-1 end
注意下打印結(jié)果,T2調(diào)用notify方法之后,T1并不能立即繼續(xù)執(zhí)行,而是要等待T2釋放objec投遞鎖之后,T1重新成功獲取鎖后,才能繼續(xù)執(zhí)行。因此最后2行日志相差了2秒(因?yàn)門2調(diào)用notify方法后休眠了2秒)。
可以這么理解,obj對(duì)象上有2個(gè)隊(duì)列,q1:等待隊(duì)列,q2:準(zhǔn)備獲取鎖的隊(duì)列;
掛起(suspend)和繼續(xù)執(zhí)行(resume)線程——方法已廢棄
Thread類中還有2個(gè)方法,即線程掛起(suspend)和繼續(xù)執(zhí)行(resume),這2個(gè)操作是一對(duì)相反的操作,被掛起的線程,必須要等到resume()方法操作后,才能繼續(xù)執(zhí)行。系統(tǒng)中已經(jīng)標(biāo)注著2個(gè)方法過(guò)時(shí)了,不推薦使用。
系統(tǒng)不推薦使用suspend()方法去掛起線程是因?yàn)閟uspend()方法導(dǎo)致線程暫停的同時(shí),并不會(huì)釋放任何鎖資源。此時(shí),其他任何線程想要訪問(wèn)被它占用的鎖時(shí),都會(huì)被牽連,導(dǎo)致無(wú)法正常運(yùn)行(如圖2.7所示)。直到在對(duì)應(yīng)的線程上進(jìn)行了resume()方法操作,被掛起的線程才能繼續(xù),從而其他所有阻塞在相關(guān)鎖上的線程也可以繼續(xù)執(zhí)行。但是,如果resume()方法操作意外地在suspend()方法前就被執(zhí)行了,那么被掛起的線程可能很難有機(jī)會(huì)被繼續(xù)執(zhí)行了。并且,更嚴(yán)重的是:它所占用的鎖不會(huì)被釋放,因此可能會(huì)導(dǎo)致整個(gè)系統(tǒng)工作不正常。而且,對(duì)于被掛起的線程,從它線程的狀態(tài)上看,居然還是Runnable狀態(tài),這也會(huì)影響我們對(duì)系統(tǒng)當(dāng)前狀態(tài)的判斷。

等待線程結(jié)束(join)和謙讓(yeild)
很多時(shí)候,一個(gè)線程的輸入可能非常依賴于另外一個(gè)或者多個(gè)線程的輸出,此時(shí),這個(gè)線程就需要等待依賴的線程執(zhí)行完畢,才能繼續(xù)執(zhí)行。jdk提供了join()操作來(lái)實(shí)現(xiàn)等待線程結(jié)束。
//表示無(wú)限等待,當(dāng)前線程會(huì)一直等待,直到目標(biāo)線程執(zhí)行完畢
public final void join() throws InterruptedException;
//millis參數(shù)用于指定等待時(shí)間,如果超過(guò)了給定的時(shí)間目標(biāo)線程還在執(zhí)行,當(dāng)前線程也會(huì)停止等待,而繼續(xù)往下執(zhí)行。
public final synchronized void join(long millis) throws InterruptedException;
例子:線程T1需要等待T2、T3完成之后才能繼續(xù)執(zhí)行,那么在T1線程中需要分別調(diào)用T2和T3的join()方法。
示例:
public class JoinTest {
private static int num = 0;
public static void main(String[] args) throws InterruptedException {
T1 t1 = new T1("T1");
t1.start();
long start = System.currentTimeMillis();
t1.join();
long end = System.currentTimeMillis();
System.out.println(Thread.currentThread().getName() + " end .user time ="+(end-start)+" ,get num=" + num);
}
static class T1 extends Thread {
public T1(String name) {
super(name);
}
@Override
public void run() {
System.out.println(this.getName() + " start");
for (int i = 0; i < 9; i++) {
num++;
}
try {
TimeUnit.SECONDS.sleep(3);
} catch (InterruptedException e) {
e.printStackTrace();
}
System.out.println(this.getName() + " end");
}
}
}
輸出:
T1 start
T1 end
main end .user time =3003 ,get num=9
Thread.yield()方法。是屈服,放棄,謙讓的意思。
這是一個(gè)靜態(tài)方法,一旦執(zhí)行,它會(huì)讓當(dāng)前線程讓出CPU,但需要注意的是,讓出CPU并不是說(shuō)不讓當(dāng)前線程執(zhí)行了,當(dāng)前線程在出讓CPU后,還會(huì)進(jìn)行CPU資源的爭(zhēng)奪,但是能否再搶到CPU的執(zhí)行權(quán)就不一定了。因此,對(duì)Thread.yield()方法的調(diào)用好像就是在說(shuō):我已經(jīng)完成了一些主要的工作,我可以休息一下了,可以讓CPU給其他線程一些工作機(jī)會(huì)了。
如果覺(jué)得一個(gè)線程不太重要,或者優(yōu)先級(jí)比較低,而又擔(dān)心此線程會(huì)過(guò)多的占用CPU資源,那么可以在適當(dāng)?shù)臅r(shí)候調(diào)用一下Thread.yield()方法,給與其他線程更多的機(jī)會(huì)。
public static native void yield();
總結(jié)
- 創(chuàng)建線程的4種方式:繼承Thread類;實(shí)現(xiàn)Runnable接口;實(shí)現(xiàn)Callable接口;使用線程池創(chuàng)建。
- 啟動(dòng)線程:調(diào)用線程的start()方法
- 終止線程:調(diào)用線程的stop()方法,方法已過(guò)時(shí),建議不要使用
- 線程中斷相關(guān)的方法:調(diào)用線程實(shí)例interrupt()方法將中斷標(biāo)志置為true;使用線程實(shí)例方法isInterrupted()獲取中斷標(biāo)志;調(diào)用Thread的靜態(tài)方法interrupted()獲取線程是否被中斷,此方法調(diào)用之后會(huì)清除中斷標(biāo)志(將中斷標(biāo)志置為false了)
- wait、notify、notifyAll方法
- 線程掛起使用線程實(shí)例方法suspend(),恢復(fù)線程使用線程實(shí)例方法resume(),這2個(gè)方法都過(guò)時(shí)了,不建議使用
- 等待線程結(jié)束:調(diào)用線程實(shí)例方法join()
- 讓出cpu資源:調(diào)用線程靜態(tài)方法yeild()
疑問(wèn):
Q:方法interrupted()是一個(gè)靜態(tài)方法,返回boolean類型,也是用來(lái)判斷當(dāng)前線程是否被中斷,但是同時(shí)會(huì)清除當(dāng)前線程的中斷標(biāo)志位的狀態(tài)。 清除當(dāng)前線程的中斷標(biāo)志位的狀態(tài)是表示該線程可以不中斷了么?清除當(dāng)前線程的中斷標(biāo)志位的狀態(tài)是什么意思,有什么作用?怎么使用?
Q:三個(gè)線程交替打印ABC 10次使用wait(),notifyAll()如何實(shí)現(xiàn)?
public class AlternatePrint {
public static Object lock = new Object();
public static AtomicInteger count = new AtomicInteger(0);
public static void main(String[] args) {
new Thread(() -> {
for (int i = 0; i < 10; i++) {
synchronized (lock) {
while (count.get() % 3 != 0) {
try {
lock.wait();
} catch (InterruptedException e) {
e.printStackTrace();
}
}
System.out.print(Thread.currentThread().getName());
count.incrementAndGet();
lock.notifyAll();
}
}
}, "A").start();
new Thread(() -> {
for (int i = 0; i < 10; i++) {
synchronized (lock) {
while (count.get() % 3 != 1) {
try {
lock.wait();
} catch (InterruptedException e) {
e.printStackTrace();
}
}
System.out.print(Thread.currentThread().getName());
count.incrementAndGet();
lock.notifyAll();
}
}
}, "B").start();
new Thread(() -> {
for (int i = 0; i < 10; i++) {
synchronized (lock) {
while (count.get() % 3 != 2) {
try {
lock.wait();
} catch (InterruptedException e) {
e.printStackTrace();
}
}
System.out.print(Thread.currentThread().getName());
count.incrementAndGet();
lock.notifyAll();
}
}
}, "C").start();
}
}
LockSupport實(shí)現(xiàn)三個(gè)線程交替打印ABC 10次
public class AlternatePrint2 {
public static Thread a, b, c;
public static void main(String[] args) {
a = new Thread(() -> {
for (int i = 0; i < 10; i++) {
LockSupport.park();
System.out.print(Thread.currentThread().getName());
LockSupport.unpark(b);
}
}, "A");
b = new Thread(() -> {
for (int i = 0; i < 10; i++) {
LockSupport.park();
System.out.print(Thread.currentThread().getName());
LockSupport.unpark(c);
}
}, "B");
c = new Thread(() -> {
for (int i = 0; i < 10; i++) {
LockSupport.park();
System.out.print(Thread.currentThread().getName());
LockSupport.unpark(a);
}
}, "C");
a.start();
b.start();
c.start();
//注意進(jìn)入后先都讓線程阻塞住,否則會(huì)被其他線程率先執(zhí)行導(dǎo)致失序。
LockSupport.unpark(a);
}
}
volatile與Java內(nèi)存模型
volatile解決了共享變量在多線程中可見(jiàn)性的問(wèn)題,可見(jiàn)性是指一個(gè)線程對(duì)共享變量的修改,對(duì)于另一個(gè)線程來(lái)說(shuō)是否是可以看到的。
使用volatile保證內(nèi)存可見(jiàn)性示例:
public class VolatileTest {
//public static boolean flag = true;
public static volatile boolean flag = true;
public static void main(String[] args) throws InterruptedException {
T1 t1 = new T1("T1");
t1.start();
//TimeUnit.SECONDS.sleep(3);
Thread.sleep(2000);
//將flag置為false
flag = false;
}
public static class T1 extends Thread {
public T1(String name) {
super(name);
}
@Override
public void run() {
System.out.println(this.getName() + " start");
while (VolatileTest.flag) {
//奇怪現(xiàn)象,為什么執(zhí)行輸出語(yǔ)句在運(yùn)行一會(huì)兒后會(huì)讓flag=false讀取到,而 ; 空循環(huán)卻會(huì)導(dǎo)致程序無(wú)法終止呢?
//個(gè)人覺(jué)得應(yīng)該是虛擬機(jī)從解釋執(zhí)行轉(zhuǎn)換為編譯執(zhí)行,這時(shí)候會(huì)重新讀到flag。
//System.out.println(this.getName() +"endless loop");
;
}
System.out.println(this.getName() + " end");
}
}
}
不加volatile運(yùn)行上面代碼,會(huì)發(fā)現(xiàn)程序無(wú)法終止。
Q:t1線程中為什么看不到被主線程修改之后的flag?
要解釋這個(gè),我們需要先了解一下java內(nèi)存模型(JMM),Java線程之間的通信由Java內(nèi)存模型(本文簡(jiǎn)稱為JMM)控制,JMM決定一個(gè)線程對(duì)共享變量的寫(xiě)入何時(shí)對(duì)另一個(gè)線程可見(jiàn)。從抽象的角度來(lái)看,JMM定義了線程和主內(nèi)存之間的抽象關(guān)系:線程之間的共享變量存儲(chǔ)在主內(nèi)存(main memory)中,每個(gè)線程都有一個(gè)私有的本地內(nèi)存(local memory),本地內(nèi)存中存儲(chǔ)了該線程讀/寫(xiě)共享變量的副本。本地內(nèi)存是JMM的一個(gè)抽象概念,并不真實(shí)存在。它涵蓋了緩存,寫(xiě)緩沖區(qū),寄存器以及其他的硬件和編譯器優(yōu)化。
Q: 上面的代碼為什么執(zhí)行輸出語(yǔ)句在運(yùn)行一會(huì)兒后會(huì)讓flag=false讀取到,而 ; 空循環(huán)卻會(huì)導(dǎo)致程序無(wú)法終止呢?

Java內(nèi)存模型的抽象示意圖:

線程A需要和線程B通信,必須要經(jīng)歷下面2個(gè)步驟:
- 首先,線程A把本地內(nèi)存A中更新過(guò)的共享變量刷新到主內(nèi)存中去。
- 然后,線程B到主內(nèi)存中去讀取線程A之前已更新過(guò)的共享變量。
線程t1中為何看不到被主線程修改為false的flag的值的原因,有兩種可能:
- 主線程修改了flag之后,未將其刷新到主內(nèi)存,所以t1看不到
- 主線程將flag刷新到了主內(nèi)存,但是t1一直讀取的是自己工作內(nèi)存中flag的值,沒(méi)有去主內(nèi)存中獲取flag最新的值
使用volatile修飾共享變量,就可以達(dá)到上面的效果,被volatile修改的變量有以下特點(diǎn):
- 線程中讀取的時(shí)候,每次讀取都會(huì)去主內(nèi)存中讀取共享變量最新的值,然后將其復(fù)制到工作內(nèi)存(正確的來(lái)說(shuō)是當(dāng)主內(nèi)存有變化時(shí),各個(gè)工作內(nèi)存的副本會(huì)被緩存一致性協(xié)議和總線嗅探機(jī)制作用而失效,因此當(dāng)其他線程去獲取工作內(nèi)存上的值時(shí)發(fā)現(xiàn)失效了或重新獲取最新的主內(nèi)存上的值)
- 線程中修改了工作內(nèi)存中變量的副本,修改之后會(huì)立即刷新到主內(nèi)存
線程組
我們可以把線程歸屬到某個(gè)線程組中,線程組可以包含多個(gè)線程以及線程組,線程和線程組組成了父子關(guān)系,是個(gè)樹(shù)形結(jié)構(gòu)。使用線程組可以方便管理線程 。(線程池是不是更實(shí)在一點(diǎn)?)

創(chuàng)建線程關(guān)聯(lián)線程組
創(chuàng)建線程的時(shí)候,可以給線程指定一個(gè)線程組。
創(chuàng)建線程組的時(shí)候,可以給其指定一個(gè)父線程組,也可以不指定,如果不指定父線程組,則父線程組為當(dāng)前線程的線程組,系統(tǒng)自動(dòng)獲取當(dāng)前線程的線程組作為默認(rèn)父線程組。java api有2個(gè)常用的構(gòu)造方法用來(lái)創(chuàng)建線程組:
public ThreadGroup(String name) public ThreadGroup(ThreadGroup parent, String name)
第一個(gè)構(gòu)造方法未指定父線程組,看一下內(nèi)部的實(shí)現(xiàn):
public ThreadGroup(String name) { this(Thread.currentThread().getThreadGroup(), name); }
批量停止線程
調(diào)用線程組interrupt(),會(huì)將線程組樹(shù)下的所有子孫線程中斷標(biāo)志置為true,可以用來(lái)批量中斷線程。
建議創(chuàng)建線程或者線程組的時(shí)候,給他們?nèi)∫粋€(gè)有意義的名字,在系統(tǒng)出問(wèn)題的時(shí)候方面查詢定位。
示例:
public class ThreadGroupTest {
public static class R1 implements Runnable {
@Override
public void run() {
System.out.println("threadName:" + Thread.currentThread().getName());
while (!Thread.currentThread().isInterrupted()){
;
}
System.out.println(Thread.currentThread().getName()+"線程停止了");
}
}
public static void main(String[] args) throws InterruptedException {
//threadGroup1未指定父線程組,系統(tǒng)獲取了主線程的線程組作為threadGroup1的父線程組,輸出結(jié)果中是:main
ThreadGroup threadGroup = new ThreadGroup("thread-group-1");
Thread t1 = new Thread(threadGroup, new R1(), "t1");
Thread t2 = new Thread(threadGroup, new R1(), "t2");
t1.start();
t2.start();
TimeUnit.SECONDS.sleep(1);
System.out.println("活動(dòng)線程數(shù):" + threadGroup.activeCount());
System.out.println("活動(dòng)線程組:" + threadGroup.activeGroupCount());
System.out.println("線程組名稱:" + threadGroup.getName());
ThreadGroup threadGroup2 = new ThreadGroup(threadGroup, "thread-group-2");
Thread t3 = new Thread(threadGroup2, new R1(), "t3");
Thread t4 = new Thread(threadGroup2, new R1(), "t4");
t3.start();
t4.start();
threadGroup.list();
//java.lang.ThreadGroup[name=main,maxpri=10] 主線程的線程組為main
System.out.println(Thread.currentThread().getThreadGroup());
//java.lang.ThreadGroup[name=system,maxpri=10] 根線程組為system
System.out.println(Thread.currentThread().getThreadGroup().getParent());
//null
System.out.println(Thread.currentThread().getThreadGroup().getParent().getParent());
threadGroup.interrupt();
TimeUnit.SECONDS.sleep(2);
threadGroup.list();
}
}
輸出:
threadName:t1
threadName:t2
活動(dòng)線程數(shù):2
活動(dòng)線程組:0
線程組名稱:thread-group-1
java.lang.ThreadGroup[name=thread-group-1,maxpri=10]
Thread[t1,5,thread-group-1]
Thread[t2,5,thread-group-1]
java.lang.ThreadGroup[name=thread-group-2,maxpri=10]
Thread[t3,5,thread-group-2]
Thread[t4,5,thread-group-2]
java.lang.ThreadGroup[name=main,maxpri=10]
java.lang.ThreadGroup[name=system,maxpri=10]
null
t2線程停止了
t1線程停止了
threadName:t4
threadName:t3
t4線程停止了
t3線程停止了
java.lang.ThreadGroup[name=thread-group-1,maxpri=10]
java.lang.ThreadGroup[name=thread-group-2,maxpri=10]
用戶線程和守護(hù)線程
守護(hù)線程是一種特殊的線程,在后臺(tái)默默地完成一些系統(tǒng)性的服務(wù),比如垃圾回收線程、JIT線程都是守護(hù)線程。與之對(duì)應(yīng)的是用戶線程,用戶線程可以理解為是系統(tǒng)的工作線程,它會(huì)完成這個(gè)程序需要完成的業(yè)務(wù)操作。如果用戶線程全部結(jié)束了,意味著程序需要完成的業(yè)務(wù)操作已經(jīng)結(jié)束了,系統(tǒng)可以退出了。所以當(dāng)系統(tǒng)只剩下守護(hù)進(jìn)程的時(shí)候,java虛擬機(jī)會(huì)自動(dòng)退出。
java線程分為用戶線程和守護(hù)線程,線程的daemon屬性為true表示是守護(hù)線程,false表示是用戶線程。
線程daemon的默認(rèn)值
我們看一下創(chuàng)建線程源碼,位于Thread類的init()方法中:
Thread parent = currentThread();
this.daemon = parent.isDaemon();
dameon的默認(rèn)值為為父線程的daemon,也就是說(shuō),父線程如果為用戶線程,子線程默認(rèn)也是用戶線程,父線程如果是守護(hù)線程,子線程默認(rèn)也是守護(hù)線程。
總結(jié)
- java中的線程分為用戶線程和守護(hù)線程
- 程序中的所有的用戶線程結(jié)束之后,不管守護(hù)線程處于什么狀態(tài),java虛擬機(jī)都會(huì)自動(dòng)退出
- 調(diào)用線程的實(shí)例方法setDaemon()來(lái)設(shè)置線程是否是守護(hù)線程
- setDaemon()方法必須在線程的start()方法之前調(diào)用,在后面調(diào)用會(huì)報(bào)異常,并且不起效
- 線程的daemon默認(rèn)值和其父線程一樣
示例:
public class DaemonThreadTest {
public static class T1 extends Thread {
public T1(String name) {
super(name);
}
@Override
public void run() {
System.out.println(this.getName() + " start ,isDaemon= "+isDaemon());
while (true) {
;
}
}
}
public static void main(String[] args) throws InterruptedException {
T1 t1 = new T1("T1");
// 設(shè)置守護(hù)線程,需要在start()方法之前進(jìn)行
// t1.start()必須在setDaemon(true)之后,否則執(zhí)行會(huì)報(bào)異常:Exception in thread "main" java.lang.IllegalThreadStateException
//t1.start();
//將t1線程設(shè)置為守護(hù)線程
t1.setDaemon(true);
t1.start();
//當(dāng)程序中所有的用戶線程執(zhí)行完畢之后,不管守護(hù)線程是否結(jié)束,系統(tǒng)都會(huì)自動(dòng)退出。
TimeUnit.SECONDS.sleep(1);
}
}
疑問(wèn):
Q:JIT線程?
A: JIT一般指準(zhǔn)時(shí)制。準(zhǔn)時(shí)制生產(chǎn)方式(Just In Time簡(jiǎn)稱JIT ).JIT線程在Java中表示即時(shí)編譯線程。
在Java編程語(yǔ)言和環(huán)境中,即時(shí)編譯器(JIT compiler,just-in-time compiler)是一個(gè)把Java的字節(jié)碼(包括需要被解釋的指令的程序)轉(zhuǎn)換成可以直接發(fā)送給處理器的指令的程序。
線程安全和synchronized關(guān)鍵字
什么是線程安全?
當(dāng)多個(gè)線程去訪問(wèn)同一個(gè)類(對(duì)象或方法)的時(shí)候,該類都能表現(xiàn)出一致的行為,沒(méi)有意想不到的不同結(jié)果,那我們就可以說(shuō)這個(gè)類是線程安全的。
造成線程安全問(wèn)題的主要誘因有兩點(diǎn):
- 一是存在共享數(shù)據(jù)(也稱臨界資源)
- 二是存在多條線程共同操作共享數(shù)據(jù)
為了解決這個(gè)問(wèn)題,當(dāng)存在多個(gè)線程操作共享數(shù)據(jù)時(shí),需要保證同一時(shí)刻有且只有一個(gè)線程在操作共享數(shù)據(jù),這種方式有個(gè)高尚的名稱叫互斥鎖,在 Java 中,關(guān)鍵字 synchronized可以保證在同一個(gè)時(shí)刻,只有一個(gè)線程可以執(zhí)行某個(gè)方法或者某個(gè)代碼塊(主要是對(duì)方法或者代碼塊中存在共享數(shù)據(jù)的操作),同時(shí)我們還應(yīng)該注意到synchronized另外一個(gè)重要的作用,synchronized可保證一個(gè)線程的變化(主要是共享數(shù)據(jù)的變化)被其他線程所看到(保證可見(jiàn)性,完全可以替代volatile功能)
鎖的互斥性表現(xiàn)在線程嘗試獲取的是否是同一個(gè)鎖,相同類型不同實(shí)例的對(duì)象鎖不互斥,而class類對(duì)象的鎖與實(shí)例鎖之間也不互斥。
synchronized主要有3種使用方式
- 修飾實(shí)例方法,作用于當(dāng)前實(shí)例,進(jìn)入同步代碼前需要先獲取實(shí)例的鎖
- 修飾靜態(tài)方法,作用于類的Class對(duì)象,進(jìn)入修飾的靜態(tài)方法前需要先獲取類的Class對(duì)象的鎖
- 修飾代碼塊,需要指定加鎖對(duì)象(記做lockobj),在進(jìn)入同步代碼塊前需要先獲取lockobj的鎖
synchronized作用于實(shí)例對(duì)象
synchronize作用于實(shí)例方法需要注意:
- 實(shí)例方法上加synchronized,線程安全的前提是,多個(gè)線程操作的是同一個(gè)實(shí)例,如果多個(gè)線程作用于不同的實(shí)例,那么線程安全是無(wú)法保證的
- 同一個(gè)實(shí)例的多個(gè)實(shí)例方法上有synchronized,這些方法都是互斥的,同一時(shí)間只允許一個(gè)線程操作同一個(gè)實(shí)例的其中的一個(gè)synchronized方法
synchronized作用于靜態(tài)方法
當(dāng)synchronized作用于靜態(tài)方法時(shí),鎖的對(duì)象就是當(dāng)前類的Class對(duì)象。
synchronized同步代碼塊
方法體可能比較大,同時(shí)存在一些比較耗時(shí)的操作,而需要同步的代碼又只有一小部分時(shí)使用。加鎖時(shí)可以使用自定義的對(duì)象作為鎖,也可以使用this對(duì)象(代表當(dāng)前實(shí)例)或者當(dāng)前類的class對(duì)象作為鎖 。
疑問(wèn):
Q:synchronized可保證一個(gè)線程的變化(主要是共享數(shù)據(jù)的變化)被其他線程所看到(保證可見(jiàn)性,完全可以替代volatile功能),synchronized是怎么保證可見(jiàn)性的呢?
Java原子操作要求lock和unlock操作時(shí)必須做到可見(jiàn)性,即對(duì)主內(nèi)存的副本工作內(nèi)存做修改時(shí)會(huì)馬上回寫(xiě)主內(nèi)存;還有就是讀取工作內(nèi)存副本數(shù)據(jù)時(shí)每次都會(huì)讀主內(nèi)存最新的數(shù)據(jù)。
Q:同一個(gè)實(shí)例的多個(gè)實(shí)例方法上有synchronized,這些方法都是互斥的,同一時(shí)間只允許一個(gè)線程操作同一個(gè)實(shí)例的其中的一個(gè)synchronized方法.驗(yàn)證同一時(shí)間只允許一個(gè)線程操作同一個(gè)實(shí)例的其中的一個(gè)synchronized方法是對(duì)的。
A:示例有下:
public class MethodObject {
public synchronized void methodA() throws InterruptedException {
System.out.println("methodA start");
TimeUnit.SECONDS.sleep(10);
System.out.println("methodA finish");
}
public synchronized void methodB() throws InterruptedException {
System.out.println("methodB start");
TimeUnit.SECONDS.sleep(5);
System.out.println("methodB finish");
}
}
public class SynchronousTest {
public static void main(String[] args) throws InterruptedException {
MethodObject mo = new MethodObject();
T1 t1 = new T1("T1", mo);
T2 t2 = new T2("T2", mo);
t1.start();
TimeUnit.MILLISECONDS.sleep(300);
t2.start();
}
public static class T1 extends Thread {
private MethodObject mo;
public T1(String name, MethodObject mo) {
super(name);
this.mo = mo;
}
@Override
public void run() {
try {
mo.methodA();
} catch (InterruptedException e) {
e.printStackTrace();
}
}
}
public static class T2 extends Thread {
private MethodObject mo;
public T2(String name, MethodObject mo) {
super(name);
this.mo = mo;
}
@Override
public void run() {
try {
mo.methodB();
} catch (InterruptedException e) {
e.printStackTrace();
}
}
}
}
synchronized實(shí)現(xiàn)原理
深入理解Java并發(fā)之synchronized實(shí)現(xiàn)原理
線程中斷的2種方式
1、通過(guò)一個(gè)volatile修飾的變量控制線程中斷
利用volatile控制的變量在多線程中的可見(jiàn)性,Java內(nèi)存模型實(shí)現(xiàn)。
示例:
public class VolatileTest {
//public static boolean flag = true;
public static volatile boolean flag = true;
public static void main(String[] args) throws InterruptedException {
T1 t1 = new T1("T1");
t1.start();
//TimeUnit.SECONDS.sleep(3);
Thread.sleep(3000);
//將flag置為false
flag = false;
}
public static class T1 extends Thread {
public T1(String name) {
super(name);
}
@Override
public void run() {
System.out.println(this.getName() + " start");
while (VolatileTest.flag) {
;
}
System.out.println(this.getName() + " end");
}
}
}
2、通過(guò)線程自帶的中斷標(biāo)志interrupt() 控制
當(dāng)調(diào)用線程的interrupt()實(shí)例方法之后,線程的中斷標(biāo)志會(huì)被置為true,可以通過(guò)線程的實(shí)例方法isInterrupted()獲取線程的中斷標(biāo)志。
當(dāng)運(yùn)行的線程處于阻塞狀態(tài)時(shí):
- 調(diào)用線程的interrupt()實(shí)例方法,線程的中斷標(biāo)志會(huì)被置為true
- 當(dāng)線程處于阻塞狀態(tài)時(shí),調(diào)用線程的interrupt()實(shí)例方法,線程內(nèi)部會(huì)觸發(fā)InterruptedException異常,并且會(huì)清除線程內(nèi)部的中斷標(biāo)志(即將中斷標(biāo)志置為false)
阻塞狀態(tài)處理方法:這時(shí)候應(yīng)該在catch中再調(diào)用this.interrupt();一次,將中斷標(biāo)志置為true。然后在run()方法中通過(guò)this.isInterrupted()來(lái)獲取線程的中斷標(biāo)志,退出循環(huán)break。
總結(jié)
- 當(dāng)一個(gè)線程處于被阻塞狀態(tài)或者試圖執(zhí)行一個(gè)阻塞操作時(shí),可以使用
Thread.interrupt()方式中斷該線程,注意此時(shí)將會(huì)拋出一個(gè)InterruptedException的異常,同時(shí)中斷狀態(tài)將會(huì)被復(fù)位(由中斷狀態(tài)改為非中斷狀態(tài))。阻塞狀態(tài)線程要通過(guò)線程自帶的中斷標(biāo)志interrupt() 控制中斷。 - 內(nèi)部有循環(huán)體,可以通過(guò)一個(gè)變量來(lái)作為一個(gè)信號(hào)控制線程是否中斷,注意變量需要volatile修飾。
- 文中的2種方式可以結(jié)合起來(lái)靈活使用控制線程的中斷.
示例:
public class InterruptTest1 {
public static class T1 extends Thread {
public T1(String name) {
super(name);
}
@Override
public void run() {
System.out.println(this.getName() + " start");
while (true) {
try {
//下面模擬阻塞代碼
TimeUnit.SECONDS.sleep(200);
} catch (InterruptedException e) {
e.printStackTrace();
this.interrupt();
}
if (this.isInterrupted()) {
break;
}
}
System.out.println(this.getName() + " end");
}
}
public static void main(String[] args) throws InterruptedException {
T1 t1 = new T1("thread1");
t1.start();
TimeUnit.SECONDS.sleep(2);
t1.interrupt();
}
}
輸出:
thread1 start
thread1 end
java.lang.InterruptedException: sleep interrupted
at java.lang.Thread.sleep(Native Method)
at java.lang.Thread.sleep(Thread.java:340)
at java.util.concurrent.TimeUnit.sleep(TimeUnit.java:386)
at com.self.current.InterruptTest1$T1.run(InterruptTest1.java:27)