Java多線程編程核心技術(shù)1——Thread類核心API

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

線程狀態(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)行。

最后編輯于
?著作權(quán)歸作者所有,轉(zhuǎn)載或內(nèi)容合作請(qǐng)聯(lián)系作者
【社區(qū)內(nèi)容提示】社區(qū)部分內(nèi)容疑似由AI輔助生成,瀏覽時(shí)請(qǐng)結(jié)合常識(shí)與多方信息審慎甄別。
平臺(tái)聲明:文章內(nèi)容(如有圖片或視頻亦包括在內(nèi))由作者上傳并發(fā)布,文章內(nèi)容僅代表作者本人觀點(diǎn),簡(jiǎn)書系信息發(fā)布平臺(tái),僅提供信息存儲(chǔ)服務(wù)。

相關(guān)閱讀更多精彩內(nèi)容

友情鏈接更多精彩內(nèi)容