Thread類相關(guān)狀態(tài)和方法示意圖:

一. Thread類核心API使用
1. 進(jìn)程是一次程序的執(zhí)行,可以理解成Windows任務(wù)管理器的一個(gè)exe程序;線程是進(jìn)程中獨(dú)立運(yùn)行的子任務(wù)。
2. 實(shí)現(xiàn)多線程編程有兩種方式:
? ?2.1 繼承Thread類,覆蓋run()。(Thread類也實(shí)現(xiàn)了Runnaable接口)
? ? ? ?優(yōu)點(diǎn):如需訪問當(dāng)前線程,無需使用Thread.currentThread()方法。
? ? ? ?缺點(diǎn):已繼承Thread類,不能再繼承其他父類。
? ?2.2 實(shí)現(xiàn)Runnable接口來創(chuàng)建(必須封裝到Thread類)。
? ? ? ?優(yōu)點(diǎn):還可以繼承其他父類,適合多個(gè)相同線程來處理同一份資源。
? ? ? ?缺點(diǎn):如需訪問當(dāng)前線程,必須使用Thread.currentThread()方法。
使用多線程時(shí),代碼的運(yùn)行結(jié)果與代碼執(zhí)行順序或調(diào)用順序無關(guān)。
3.currentThread()方法
Thread.currentThread().getName():返回代碼段正在被哪個(gè)線程調(diào)用的信息。
4.isAlive():判斷線程是否處于活動(dòng)狀態(tài)。
package pgsthread;
public class TestAlive extends Thread{
public TestAlive(){
System.out.println("---TestAlive begins---");
System.out.println("Thread.currentThread().getName()="+Thread.currentThread().getName());
System.out.println("Thread.currentThread().isAlive()="+Thread.currentThread().isAlive());
System.out.println("this.getName()="+this.getName());
System.out.println("this.isAlive()="+this.isAlive());
System.out.println("---TestAlive ends---");
}
@Override
public void run(){
System.out.println("---run begins---");
System.out.println("Thread.currentThread().getName()="+Thread.currentThread().getName());
System.out.println("Thread.currentThread().isAlive()="+Thread.currentThread().isAlive());
System.out.println("this.getName()="+this.getName());
System.out.println("this.isAlive()="+this.isAlive());
System.out.println("---run ends---");
}
}
package pgsthread;
public class Run {
public static void main(String[] args) {
TestAlive t = new TestAlive();
Thread t1 = new Thread(t);
//Thread(Runnable target):可以傳入一個(gè)Thread類的對(duì)象,將t的run()交給t1執(zhí)行。
System.out.println("main begin t1 isAlive="+t1.isAlive());
t1.setName("A");
t1.start();
System.out.println("main end t1 isAlive="+t1.isAlive());
}
}
運(yùn)行結(jié)果如下:
---TestAlive begins---
Thread.currentThread().getName()=main
Thread.currentThread().isAlive()=true
this.getName()=Thread-0
this.isAlive()=false
---TestAlive ends---
main begin t1 isAlive=false
main end t1 isAlive=true
---run begins---
Thread.currentThread().getName()=A
Thread.currentThread().isAlive()=true
this.getName()=Thread-0
this.isAlive()=false
---run ends---
此處的Thread.currentThread()指向t1,this指向t。
5.判讀線程是否是停止?fàn)顟B(tài)?
import pgsthread.TestAlive;
public class TestInterrupt {
public static void main(String[] args) {
?Thread.currentThread().interrupt();
?System.out.println(Thread.interrupted()); //true
?System.out.println(Thread.interrupted()); //false
?TestAlive t = new TestAlive();
?t.start();
?t.interrupt();
?System.out.println(t.isInterrupted()); //true
?System.out.println(t.isInterrupted()); //true
}
}
Thread.currentThread().interrupt();中斷的是當(dāng)前線程,即main。
this.interrupted():具有將狀態(tài)位清楚為false的功能。
this.isInterrupted():不清除標(biāo)志位。
6.主動(dòng)行為——暫停線程使之阻塞
suspend():暫停線程;resume():恢復(fù)線程的執(zhí)行。
廢棄上述兩個(gè)方法的原因:
(1) 具有獨(dú)占性,若在同步代碼塊中調(diào)用suspend()而一直未調(diào)用resume(),會(huì)鎖死該同步代碼塊。
public void println(long x){
? ?synchronized (this){
? ? ? ?print(x);
? ? ? ?newline();
? ?}
}
如果在進(jìn)入println(long x)內(nèi)部是調(diào)用suspend(),將鎖定該方法,同步鎖將不釋放。
(2) 不同步,導(dǎo)致線程不安全。
會(huì)導(dǎo)致同方法suspend()后的代碼得不到執(zhí)行。
7.線程優(yōu)先級(jí)
setPriority():優(yōu)先級(jí)分為1~10級(jí),默認(rèn)為5。
(1)線程的優(yōu)先級(jí)具有繼承性。
(2)優(yōu)先級(jí)具有規(guī)則性:高優(yōu)先級(jí)的線程總是大部分先執(zhí)行完,但不代表高優(yōu)先級(jí)的線程全部先執(zhí)行完。
(3) 優(yōu)先級(jí)具有隨機(jī)性,并不一定優(yōu)先級(jí)高的線程就一定先執(zhí)行完。ps,線程優(yōu)先級(jí)與代碼中出現(xiàn)的先后順序無關(guān)。
8.守護(hù)線程 Thread.setDaemon(true)
Java線程分為兩種,一種是用戶線程,另一種是守護(hù)(Daemon)線程。當(dāng)進(jìn)程中沒有用戶線程時(shí),守護(hù)線程也將自動(dòng)銷毀。
二. 線程間通信
1.等待/通知(wait/notify)機(jī)制
(1) 不使用等待/通知機(jī)制
使用sleep()結(jié)合while(true)死循環(huán)輪詢檢測(cè),會(huì)浪費(fèi)CPU資源。即兩個(gè)線程主動(dòng)式的讀取一個(gè)共享變量,在花費(fèi)讀取時(shí)間的基礎(chǔ)上,讀到的值不一定是想要的。
(2) 等待/通知機(jī)制:
wait()使線程停止運(yùn)行,notify()使停止的線程繼續(xù)運(yùn)行(隨機(jī)挑選出一個(gè)呈wait狀態(tài)的線程)。
在調(diào)用wait()和notify()前必須獲得該對(duì)象的對(duì)象級(jí)別鎖,即只能在同步方法或者代碼塊中調(diào)用wait()和notify()方法。
※要等到notify()方法的線程將程序執(zhí)行完,也就是退出synchronized代碼塊后,當(dāng)前線程才釋放鎖,而不是notify()后馬上喚醒wait狀態(tài)的線程進(jìn)行操作。
(3) sleep():不釋放鎖;notify():不立即釋放鎖;wait():釋放鎖。
(4) 不管線程是wait()還是sleep()狀態(tài),調(diào)用interrupt()都會(huì)拋出InterruptedException異常。只有運(yùn)行的線程才可被interrupt()。
(5) 喚醒所有等待中的線程:
方法一:多次調(diào)用notify();
方法二:notifyAll();
(6) 等待wait的條件發(fā)生變化:
※用while去判斷,while是輪詢的,同步的,每一次都會(huì)進(jìn)行判斷;而if的話可能有兩個(gè)線程進(jìn)去同時(shí)進(jìn)入if語句塊就會(huì)產(chǎn)生非線程安全問題。
2.生產(chǎn)者/消費(fèi)者模式實(shí)現(xiàn)
多個(gè)生產(chǎn)者與消費(fèi)者:采用wait/notify機(jī)制,但不一定每一次喚醒的都是異類,連續(xù)喚醒同類就會(huì)產(chǎn)生“假死”,以下加粗的地方則為連續(xù)喚醒同類。
生產(chǎn)者1 ?+1
生產(chǎn)者1 wait
生產(chǎn)者2 wait
消費(fèi)者1 ?1-1=0
消費(fèi)者1 wait
消費(fèi)者2 wait
生產(chǎn)者1 +1
生產(chǎn)者1 wait
生產(chǎn)者2 wait
解決方案1:改為notifyAll();
3. 通過管道進(jìn)行線程間通信
:一個(gè)線程發(fā)送數(shù)據(jù)到輸出管道,另一個(gè)線程從輸入管道中讀數(shù)據(jù)。
3.1 字節(jié)流:PipedInputStream和PipedOutputStream
PipedInputStream in = new PipedInputStream();
PipedOutputStream out = new PipedOutputStream();
out.connect(in);或 in.connect(out);//使兩個(gè)管道之間進(jìn)行通信鏈接
3.2 字符流:PipedReader和PipedWriter,同上connect
4.實(shí)例 -- 等待/通知の交叉?zhèn)浞?-- 使線程具有有序性(設(shè)置一個(gè)volatile參數(shù))
package threadservice;
public class DBTools {
volatile private boolean flag = false;
synchronized public void saveA(){
?while(flag){
? try {
? ?wait();
? } catch (InterruptedException e) {
? ?// TODO Auto-generated catch block
? ?e.printStackTrace();
? }
?}
?System.out.println("save A!");
?flag = true;
?notifyAll();
}
synchronized public void saveB(){
?while(!flag){
? try {
? ?wait();
? } catch (InterruptedException e) {
? ?// TODO Auto-generated catch block
? ?e.printStackTrace();
? }
?}
?System.out.println("save B!");
?flag = false;
?notifyAll();
}}
package threadservice;
public class ThreadA extends Thread{
private DBTools dbtools;
public ThreadA(DBTools dbtools){
?this.dbtools = dbtools;
}
?
@Override
public void run() {
?dbtools.saveA();
}
}
package threadservice;
public class ThreadB extends Thread{
private DBTools dbtools;
public ThreadB(DBTools dbtools){
?this.dbtools = dbtools;
}
?
@Override
public void run() {
?dbtools.saveB();
}
}
package threadservice;
public class Run {
public static void main(String[] args) {
?DBTools dbtools = new DBTools();
?
?for(int i = 0; i < 3;i++){
? ThreadA tA = new ThreadA(dbtools);
? tA.start();
? ThreadB tB = new ThreadB(dbtools);
? tB.start();
?}
}
}
打印結(jié)果就是交叉打印的,循環(huán)等待和通知所有wait線程,但是只有flag正確的那個(gè)線程才能運(yùn)行,即想要順序執(zhí)行的話,只要設(shè)置參數(shù)就可以。
save A!
save B!
save A!
save B!
save A!
save B!
5.join()
比如子線程要進(jìn)行大量的耗時(shí)計(jì)算,主線程往往早于子線程結(jié)束。當(dāng)主線程需要等待子線程完成之后再結(jié)束,就需要用到j(luò)oin()。
如在t1中調(diào)用t2.join();t1暫停,必須等到t2運(yùn)行完才能繼續(xù)運(yùn)行。即等待線程對(duì)象銷毀。
(1) join與synchronized區(qū)別:
join()在內(nèi)部使用wait()方法進(jìn)行等待,而synchronized使用的是“對(duì)象監(jiān)視器”原理作為同步。
(2) 如果當(dāng)前線程對(duì)象被中斷,則當(dāng)前線程出現(xiàn)異常(只影響當(dāng)前線程)。
方法join()與interrupt()方法彼此遇到,會(huì)出現(xiàn)異常。但影響的是當(dāng)前的線程,其他線程正常運(yùn)行。
(3) join(long)和sleep(long)的區(qū)別
join(long):在內(nèi)部使用的是wait(long),具有釋放鎖地特點(diǎn)。在內(nèi)部執(zhí)行wait(long)后,當(dāng)前線程的鎖被釋放,其他線程就可以調(diào)用此線程中的同步方法了。
而sleep(long)不釋放鎖。
(4) 經(jīng)過運(yùn)行如下代碼發(fā)現(xiàn)join后面的代碼有可能提前運(yùn)行,可能會(huì)出現(xiàn)以下結(jié)果:
package jointhread;
public class ThreadA extends Thread {
private ThreadB b;public ThreadA(ThreadB b) {
?super();
?this.b = b;
}@Override
public void run() {
?try {
? synchronized (b) {
? ?System.out.println("A begins at " + System.currentTimeMillis());
? ?Thread.sleep(5000);
? ?System.out.println("A ends at " + System.currentTimeMillis());
? }
?} catch (InterruptedException e) {
? // TODO Auto-generated catch block
? e.printStackTrace();
?}
}
}
package jointhread;
public class ThreadB extends Thread{
?
@Override
synchronized public void run() {
?try {
? System.out.println("B begins at " + System.currentTimeMillis());
? Thread.sleep(5000);
? System.out.println("B ends at " + System.currentTimeMillis());
?} catch (InterruptedException e) {
? // TODO Auto-generated catch block
? e.printStackTrace();
?}
}
}
package jointhread;
public class Run {
public static void main(String[] args) {
?try {
? ThreadB b = new ThreadB();
? ThreadA a = new ThreadA(b);
? a.start();
? b.start();
? b.join(2000);
? System.out.println("main ends at " + System.currentTimeMillis());
?} catch (InterruptedException e) {
? // TODO Auto-generated catch block
? e.printStackTrace();
?}
}
}
運(yùn)行結(jié)果1:
A begins at 1486956475786
A ends at 1486956480825
main ends at 1486956480857
B begins at 1486956480872
B ends at 1486956485880
step1. b.join(2000)先搶到B鎖,然后將B鎖釋放;
step2. ThreadA搶到B鎖,打印 A begins 并且sleep(5000);
step3. ThreadA打印 A ends,并釋放鎖;
step4. 此時(shí)join(2000)和ThreadB爭(zhēng)搶鎖,而join(2000)再次搶到鎖,發(fā)現(xiàn)時(shí)間已過,釋放鎖后打印main ends;
step5. ThreadB搶到鎖打印B begins;
step6. 5秒之后再打印B ends。
運(yùn)行結(jié)果2:
A begins at 1486956475786
A ends at 1486956480825
B begins at 1486956480872
B ends at 1486956485880
main ends at 1486956485880
step1. b.join(2000)先搶到B鎖,然后將B鎖釋放;
step2. ThreadA搶到B鎖,打印 A begins 并且sleep(5000);
step3. ThreadA打印 A ends,并釋放鎖;
step4. 此時(shí)join(2000)和ThreadB爭(zhēng)搶鎖,而ThreadB搶到鎖后執(zhí)行sleep(5000)后釋放鎖;
step5. main ends 在最后輸出。
運(yùn)行結(jié)果3:
A begins at 1486956475786
A ends at 1486956480825
B begins at 1486956480872
main ends at 1486956480872
B ends at 1486956485880
step1. b.join(2000)先搶到B鎖,然后將B鎖釋放;
step2. ThreadA搶到B鎖,打印 A begins 并且sleep(5000);
step3. ThreadA打印 A ends,并釋放鎖;
step4. 此時(shí)join(2000)和ThreadB爭(zhēng)搶鎖,而join(2000)再次搶到鎖,發(fā)現(xiàn)時(shí)間已過,釋放鎖后打印main ends;
step5. ThreadB搶到鎖打印B begins;
step6. 這時(shí)main ends 也異步輸出;
step7. 打印B ends。
6.類ThreadLocal
變量值的共享:public static +變量名;
每一個(gè)線程都有自己的共享變量:采用類ThreadLocal,使每一個(gè)線程綁定自己的值,保證隔離性。
package localthread;
public class Tools {
public static ThreadLocalExt t1 = new ThreadLocalExt();}
package localthread;
import java.util.Date;
public class ThreadLocalExt extends ThreadLocal {
@Override
protected Object initialValue() {
?return new Date().getTime();
}
}
package localthread;
public class ThreadA extends Thread{
@Override
public void run() {
?try {
? for(int i = 0;i<10;i++){
? ?System.out.println("ThreadA = " + Tools.t1.get());
? ?Thread.sleep(100);
? }
?} catch (InterruptedException e) {
? // TODO Auto-generated catch block
? e.printStackTrace();
?}
}
}
package localthread;
public class Run {
public static void main(String[] args) {
try {
?for(int i = 0;i<10;i++){
? System.out.println("main =" + Tools.t1.get());
? Thread.sleep(100);
?}
?Thread.sleep(5000);
?ThreadA a = new ThreadA();
?a.start();
} catch (InterruptedException e) {
?// TODO: handle exception
}
}
}
運(yùn)行結(jié)果為10個(gè)main打印的值相等,10個(gè)ThreadA 打印的值也相等,但main和ThreadA 的不相等。
說明ThreadLocal能夠?qū)崿F(xiàn)一個(gè)線程享有一個(gè)專屬變量。
7.類InheritableThreadLocal
可以在子線程中取得父線程繼承下來的值。
(1) 將上例中Tools.java的ThreadLocal改為InheritableThreadLocal,即可得到ThreadA 打印的值和main一樣,可謂繼承性。
(2) InheritableThreadLocal.childValue(Object object) : 可以修改子線程中的值。
(3) 如果子線程取得值的同時(shí),主線程將InheritableThreadLocal中的值進(jìn)行修改,子線程拿到的值還是舊值。
三. 線程組
1.作用:可以批量管理線程或線程組對(duì)象。
Thread aRunnable = new ThreadA();
ThreadGroup group = new ThreadGroup("小胖的線程組");
Thread aThread = new Thread(group, aRunnable);
2.在實(shí)例化一個(gè)線程組如果不指定所屬的線程組,則該線程組自動(dòng)歸到當(dāng)前線程對(duì)象所屬的線程組中,也就是隱式地在一個(gè)線程組中添加了一個(gè)子線程組。
JVM的根線程組是system,再取其父線程組則出現(xiàn)異常。
3.遞歸與非遞歸取得組內(nèi)對(duì)象。
enumerate(ThreadGroup, true);//遞歸(打印A->B)
enumerate(ThreadGroup, false);//非遞歸(只打印A)
4.在默認(rèn)的情況下,線程組中的一個(gè)線程出錯(cuò),不影響其他線程的運(yùn)行。