為什么使用多線程
可以最大限度地利用CPU的空閑時間來處理其它任務芽隆。異步處理不同的任務青自,提高任務處理效率葫隙。
線程的五種狀態(tài)
1栽烂、新建狀態(tài)(New):線程對象創(chuàng)建后,就進入新建狀態(tài)恋脚。
2腺办、就緒狀態(tài)(Runnable):就緒狀態(tài)又稱可執(zhí)行狀態(tài),線程被創(chuàng)建后通過調(diào)用start()方法糟描,從而啟動線程怀喉。就緒狀態(tài)的線程,隨時有可能被CPU調(diào)度運行蚓挤。
3磺送、運行狀態(tài)(Running):線程獲取CPU權(quán)限進行執(zhí)行。只有就緒狀態(tài)的線程才能進入運行狀態(tài)灿意。
4、阻塞狀態(tài)(Blocked):線程因為某種原因放棄CPU使用權(quán)崇呵,停止運行缤剧。直到線程進入就緒狀態(tài),才可以再到運行狀態(tài)域慷。
阻塞狀態(tài)三種情況:
(1)荒辕、等待阻塞:通過調(diào)用線程的wait()方法,讓線程等待某工作完成
(2)犹褒、同步阻塞:線程獲取同步鎖synchronized同步鎖失敗(因為鎖正在被其它線程使用)抵窒,進入同步阻塞。
(3)叠骑、其它阻塞:通過調(diào)用線程的sleep()李皇、join()或發(fā)出I/O請求,線程進入阻塞狀態(tài)宙枷。當sleep()狀態(tài)超時掉房、join()等待終止或超時茧跋、或者I/O處理完畢時,線程重新進入就休狀態(tài)卓囚。
5瘾杭、死亡狀態(tài)(Dead):線程正常直行完成或者因為異常原因退出run()方法,該線程生命周期結(jié)束哪亿。
通過Thread和Runnable創(chuàng)建線程
Java的JDK開發(fā)包中粥烁,已經(jīng)自帶了對多線程技術(shù)的支持,我們可以很方便的進行多線程編程蝇棉。
實現(xiàn)多線程編程的方式主要有兩種页徐,一種是繼承Thread類,另一種就是實現(xiàn)Runnable接口银萍。而Thread和Runnable的關(guān)系就是Thread類實現(xiàn)了Runnable接口:
public class Thread implements Runnable {}
1变勇、Runnable實現(xiàn)
java.lang.Runnable是一個接口,里面只定義了run()抽象方法贴唇。如果要實現(xiàn)多線程搀绣,可以實現(xiàn)Runnable接口,然后通過Thread thread = new Thread(new Xxx())戳气,其中Xxx是實現(xiàn)Runnable接口的類链患。
public interface Runnable {
public abstract void run();
}
Runnable方式實現(xiàn)多線程
public class RunnableTest implements Runnable{
int num = 10;
@Override
public void run() {
for(int i=0;i<20;i++){
if(this.num > 0){
System.out.println(Thread.currentThread().getName()+" num:" +this.num-- );
}
}
}
}
class Test{
public static void main(String[] args){
RunnableTest runnable = new RunnableTest();
Thread thread1 = new Thread(runnable);
Thread thread2 = new Thread(runnable);
Thread thread3 = new Thread(runnable);
thread1.start();//只有start()后才進入就緒狀態(tài)
thread2.start();
thread3.start();
}
}
運行結(jié)果
- Thread-0 num:10
- Thread-0 num:7
- Thread-2 num:8
- Thread-1 num:10
- Thread-1 num:9
- Thread-1 num:4
- Thread-1 num:3
- Thread-1 num:2
- Thread-1 num:1
- Thread-2 num:5
- Thread-0 num:6
結(jié)論:三個線程共享num變量,共同對num相減十次瓶您。這種情況也是出現(xiàn)“非線程安全的”原因麻捻,兩個線程可能同時獲取了同一變量值,同時進行操作呀袱,比如上面的線程0和線程1都打印了10贸毕。
2、Thread實現(xiàn)
java.lang.Thread是一個類夜赵,實現(xiàn)了Runnable接口明棍。如果要實現(xiàn)多線程,需要繼承Thread類寇僧,然后通過創(chuàng)建實現(xiàn)類對象來啟動線程摊腋。
Thread方式實現(xiàn)多線程
class ThreadTest extends Thread{
int num = 10;
@Override
public void run() {
for(int i=0;i<20;i++){
if(this.num > 0){
System.out.println(this.getName() + " num:" + this.num--);
}
}
}
}
class Test{
public static void main(String[] args){
ThreadTest threadTest1 = new ThreadTest();
ThreadTest threadTest2 = new ThreadTest();
ThreadTest threadTest3 = new ThreadTest();
threadTest1.start();
threadTest2.start();
threadTest3.start();
}
}
運行結(jié)果
- Thread-0 num:10
- Thread-0 num:9
- Thread-0 num:8
- Thread-0 num:7
- Thread-0 num:6
- Thread-2 num:10
- Thread-2 num:9
- Thread-1 num:10
- Thread-1 num:9
- Thread-0 num:1
- ....
結(jié)論:通過繼承Thread類創(chuàng)建的線程,每個線程直接不會共享變量嘁傀。每個線程都會各自對num進行10次相減兴蒸。
實際上因為Thread實現(xiàn)了Runnable接口,所以我們也可以使用new Thead(theadTest)形式來啟動線程细办,這樣得到結(jié)果和實現(xiàn)Runnable接口結(jié)果時一樣的橙凳,因為所有線程都共享了同一個theadTest的num變量。
還有一個需要注意的點,就是如果多次調(diào)用start()方法痕惋,則會拋出異常:Exception in thread "main" java.lang.IllegalThreadStateException区宇。
Thread對象交由其它線程執(zhí)行
Thread的構(gòu)造方法可以接收實現(xiàn)了Runnable接口的線程,而Thread類是Runnable接口的實現(xiàn)類值戳,所以我們可以將一個Thread對象交由另一個線程執(zhí)行议谷,也就是另一個線程指向這個Thread實現(xiàn)類的run方法。
在說明這個問題前我們先介紹下Thread類的介個方法:
- currentThread:返回當前代碼塊被哪個線程執(zhí)行堕虹。
- getName:返回當前線程名稱卧晓。
- isAlive():判斷當前線程是否處于活動狀態(tài),活動狀態(tài)是指線程已經(jīng)啟動并且還沒有執(zhí)行完成(就緒狀態(tài)赴捞、運行狀態(tài)和阻塞狀態(tài))逼裆。
下面是將線程1直接交由線程2來執(zhí)行:
public class ThreadDemo {
public static void main(String[] args) {
//創(chuàng)建線程1
ThreadTest thread1 = new ThreadTest();
thread1.setName("rename thread1");
//創(chuàng)建線程2,并將線程1傳遞給線程2
Thread thread2 = new Thread(thread1);
thread2.setName("rename thread2");
thread2.start();
}
}
class ThreadTest extends Thread {
public ThreadTest() {
System.out.println("====1:" + Thread.currentThread().getName());
System.out.println("====1:" + Thread.currentThread().isAlive());
System.out.println("====2:" + this.getName());
System.out.println("====2:" + this.isAlive());
}
@Override
public void run() {
System.out.println("====3:" + Thread.currentThread().getName());
System.out.println("====3:" + Thread.currentThread().isAlive());
System.out.println("====4:" + this.getName());
System.out.println("====4:" + this.isAlive());
}
}
上面程序執(zhí)行結(jié)果:
====1:main
====1:true
====2:Thread-0
====2:false
====3:rename thread2
====3:true
====4:rename thread1
====4:false
以====1開頭的打印內(nèi)容是在ThreadTest構(gòu)造方法中打印的赦政,而調(diào)用ThreadTest構(gòu)造方法是在main進程中的main方法中調(diào)用的胜宇,所以Thread.currentThread()表示的是main進程(因為ThreadTest代碼塊當前被main進程執(zhí)行)。那====2的打印結(jié)果又說明什么呢恢着,我們先看一下this.getName()好Thread.currentThread().getName()的區(qū)別桐愉。
Thread.currentThread()是指執(zhí)行當前代碼塊的線程,所以Thread.currentThread().getName()給出的是執(zhí)行當前代碼塊的線程名稱掰派。而this是指當前線程對象从诲,上面的例子也就是ThreadTest的實例,所以this.getName()給出的是當前對象的線程名稱靡羡。
通過上面的說明我們就知道了====2實際打印的是ThreadTest的一個實例系洛,Thread-0是當我們創(chuàng)建一個Thread實例時,Java為我們提供的默認線程名稱略步,并且Thread-0此時并沒有運行(當前是main進程在執(zhí)行)描扯。
====3和=====4是在ThreadTest的run方法定義的,當啟動thread2線程時候會執(zhí)行該run方法纳像。====3同樣指的是運行當前代碼塊的線程荆烈,也就是"rename thread2"(因為我們在啟動thread2時為其重新定義名稱),并且thread2處于運行狀態(tài)(在執(zhí)行當前代碼塊)竟趾。而====4的this此時還是值得ThreadTest的實例,也就是thread1線程宫峦,thread1也進行了重命名"rename thread1"(調(diào)用構(gòu)造方法時還沒有進行重命名岔帽,所以當時的名稱是默認名稱Thread-0),并且當前thread1線程并沒有執(zhí)行导绷,thread1中的run方法是交由thread2線程執(zhí)行的犀勒。
注意當前執(zhí)行線程(Thread.currentThread())和當前線程對象(this)的區(qū)別。但是要清楚this.currentThread()和Thread.currentThread()是一樣。
上面的實例就說明:線程被創(chuàng)建后可以不執(zhí)行贾费,而是交由其它線程執(zhí)行钦购。其它線程啟動后,就會調(diào)用這個被創(chuàng)建的線程的run方法褂萧。
3押桃、Runnable和Thread區(qū)別
- 通過實現(xiàn)Runnable接口能夠解決單繼承問題,如果實現(xiàn)Thread類則不能繼承其它類了导犹。
- 通過Runnable接口方式實現(xiàn)多線程唱凯,能夠起到資源共享的目的,因為多個線程共用一個Runnable實現(xiàn)類谎痢,而Thread是繼承Runnable接口磕昼,每個Thread都會實現(xiàn)各自的Runnable接口。這種說法也不絕對节猿,因為我們也可以將同一個Thread的實現(xiàn)類交由
new Thread(Runnable target)
來達到共享變量的目的票从。
4、Thread中start()和run()方法
start()和run()都是Thread中的方法滨嘱,start()會啟動一個新線程峰鄙,新線程會去執(zhí)行相應的run()方法,start()不能重復調(diào)用九孩。run()和普通成員方法一樣先馆,單獨調(diào)用run()方法,不會啟動新線程躺彬,而是使用當前的線程執(zhí)行run()方法(哪個線程調(diào)用的run方法煤墙,就由哪個線程來執(zhí)行),這個run()和普通方法一樣宪拥,可以被重復調(diào)用仿野。
run()源代碼:
@Override
public void run() {
if (target != null) {
//交由指定的線程來執(zhí)行定義的run方法
target.run();
}
}
target是一個Runnable對象,run()方法直接調(diào)用Thread線程的Runnable的run()方法她君,并不會新建一個線程脚作。
start()源代碼:
public synchronized void start() {
if (threadStatus != 0)//如果線程不是處于就緒狀態(tài),拋出異常
throw new IllegalThreadStateException();
group.add(this);//將當前線程添加到線程組里
boolean started = false;
try {
start0();//啟動線程
started = true;//啟動標志位
} finally {
try {
if (!started) {
group.threadStartFailed(this);//啟動失敗缔刹,添加到失敗隊列里
}
} catch (Throwable ignore) {
/* do nothing. If start0 threw a Throwable then
it will be passed up the call stack */
}
}
}
private native void start0(); //啟動線程的本地方法
start()通過調(diào)用本地start0()方法球涛,啟動一個線程,新線程會調(diào)用run()方法校镐。
線程常用方法
sleep()
Thread.sleep()的作用是“當前正在執(zhí)行的線程”休眠指定時間亿扁,這個當前正在執(zhí)行的線程指的是Thread.currentThread()。
getId()
this.getId()用于獲取線程的唯一標識鸟廓。
yield()方法
yield()方法的作用是讓當前線程放棄CPU的使用權(quán)从祝,讓其它任務執(zhí)行襟己。但是放棄的時間不固定,有可能馬上又獲取到CPU時間片牍陌。
測試下面代碼擎浴,查看注釋Thread.yield()前后的執(zhí)行耗時。
class YieldThread extends Thread {
@Override
public void run() {
int count = 0;
long beginTime = System.currentTimeMillis();
for(int i= 0; i < 1000000; i++){
//Thread.yield();
count = count + i + 1;
}
long endTime = System.currentTimeMillis();
System.out.println("耗時" + (endTime - beginTime) + "毫秒!");
}
}
//注釋Thread.yield()打印結(jié)果:
耗時6毫秒!
//打開注釋
耗時820毫秒!
停止線程
Java中有以下三種方式來停止正在運行的線程毒涧。
- 使用退出標志贮预,讓線程正常退出,也就是執(zhí)行完了run方法后自動停止链嘀。
- 使用Thread類提供的stop()萌狂、suspend()或resume(),但是這三種方法是不建議使用的怀泊,因為它們可能產(chǎn)生不可預期的結(jié)果(可能一些清理工作得不到完成茫藏;對一些鎖定對象進行了“解鎖”,導致得不到同步處理霹琼,從而出現(xiàn)數(shù)據(jù)不一致的問題)务傲,并且這三個方法已經(jīng)被標記為廢棄了。
- 使用interrupt方法來中斷線程枣申,進而手動實現(xiàn)停止當前線程售葡。
對于第一點沒什么說的,通過判斷退出標志位來退出run方法忠藤。第二點中Thread中自帶的stop()和suspend()由于不安全(從JDK2挟伙,開始不建議使用),所以已經(jīng)過時不在建議使用了模孩。線程中斷是目前停止線程的通用方式尖阔,也是我們這里要說的方式。
線程中斷方法
使用interrupt()方法并不會像使用for-break那樣立即退出循環(huán)榨咐,調(diào)用interrupt()方法只是將當前線程打了一個標記介却,也就是中斷標記。我們還需要加入一個判斷邏輯块茁,來手動停止當線程齿坷。
Thread類提供了兩個方法來判斷當前線程中斷標記:
- Thread.interrupted():判斷當前線程是否已經(jīng)中斷,當前線程指的是當前正在運行的線程(如果使用threadInstance.intrrupted()方式則指的是當前線程對象数焊,有可能不是正在運行的線程)永淌。Thread.interrupted()除了具有判斷線程是否中斷的功能,它還會清除線程中斷標記佩耳。也就是說如果當前線程中斷狀態(tài)為true仰禀,那么調(diào)用過interrupted后線程中斷狀態(tài)重新恢復到false。
- isInterrupted():判斷線程對象(有可能不是正在運行的線程)是否已經(jīng)中斷蚕愤,該方法不是靜態(tài)方法答恶,所以不能通過Thread.currentThread調(diào)用。
注意isInterrupted和interrupted的區(qū)別:所以只能被線程對象調(diào)用萍诱,這時候線程對象有可能并沒有運行悬嗓;另一個重要的區(qū)別在于isInterrupted方法不會清楚中斷狀態(tài)。
停止運行狀態(tài)的線程
停止運行中的線程裕坊,我們可以通過:判斷線程中斷標記+手動拋出異常的方式來停止線程包竹。
class ThreadDemo extends Thread {
@Override
public void run() {
try {
while (true){
if(this.isInterrupted())
throw new InterruptedException();
//執(zhí)行業(yè)務邏輯
}
}catch (InterruptedException e) {
System.out.println("進入Catch代碼塊,run方法執(zhí)行完成籍凝,退出異常");
}
}
}
public class ThreadTest {
public static void main(String[] args) {
ThreadDemo threadDemo = new ThreadDemo();
threadDemo.start();
//中斷線程標記
threadDemo.interrupt();
}
}
注意try-catch需要在while循環(huán)外部周瞎,否則不會退出run方法。
停止阻塞狀態(tài)的線程
停止阻塞狀態(tài)的線程比較容易饵蒂,因為它不需要我們判斷中斷標記声诸。當對阻塞狀態(tài)中的線程調(diào)用interrupt()方法時,會自動拋出InterrupterdException異常退盯,并且會清除中斷標記狀態(tài)值彼乌,也就是變?yōu)閒alse。
public class ThreadTest {
public static void main(String[] args) {
ThreadDemo threadDemo = new ThreadDemo();
threadDemo.start();
threadDemo.interrupt();
}
}
class ThreadDemo extends Thread {
@Override
public void run() {
try {
while (true){
//業(yè)務執(zhí)行邏輯
Thread.sleep(10000);
}
}catch (InterruptedException e) {
System.out.println("進入Catch代碼塊渊迁,run方法執(zhí)行完成慰照,退出異常");
}
}
}
需要注意try-catch在代碼中的維值,如果將異常處理放在while()中琉朽,這樣while(true)不會被停止毒租。
暫停線程
暫停線程是指此線程能夠暫時停止,之后還能恢復運行箱叁。我們可以使用Thread類中的suspend()方法來暫停線程墅垮,使用resume()方法來恢復線程執(zhí)行。
需要注意:suspend()蝌蹂、resume()方法和 stop()方法都已經(jīng)廢棄了患整,因為它們有可能產(chǎn)生不可預知的后果
suspend()和resume()使用實例:
class MyThread extends Thread {
private long i = 0;
public long getI() {
return i;
}
@Override
public void run() {
while (true)
i++;
}
}
public class ThreadTest {
public static void main(String[] args) throws InterruptedException {
MyThread myThread = new MyThread();
myThread.start();
Thread.sleep(3000);
myThread.suspend();
System.out.println("i:" + myThread.getI());
Thread.sleep(3000);
System.out.println("i:" + myThread.getI());
myThread.resume();
Thread.sleep(3000);
myThread.suspend();
System.out.println("i:" + myThread.getI());
Thread.sleep(3000);
System.out.println("i:" + myThread.getI());
}
}
打印結(jié)果:
i:1629776063
i:1629776063
i:3296163993
i:3296163993
從上面的打印結(jié)果可以看到,線程確實暫停了毡咏,之后又重新恢復了教翩。之所以將它們廢棄,主要是使用suspend()和resume()極易造成公共同步對象獨占齐鲤,導致其它線程無法訪問公共對象斥废。
synchronized void printString(String str) {
if(Thread.currentThread().getName().equals("TheadA")) {
System.out.println("被線程A獨占");
Thread.currentThread().suspend();
}
}
除了上面問題,suspend()和resume()方法如果使用不當给郊,也會造成數(shù)據(jù)不同步牡肉,所以我們應該避免使用這兩個方法。
synchronized同步鎖
原理:每個對象都有且只有一個同步鎖淆九,同步鎖依賴于對象存在统锤。當調(diào)用某個對象的synchronized方法時毛俏,就獲取該對象的同步鎖。例如synchronized(obj)就是獲取了obj的同步鎖饲窿,不同線程對同步鎖訪問是互斥的煌寇。就是說一個時間點,對象的同步鎖只能被一個線程調(diào)用逾雄,其它線程如果要使用阀溶,需要等待正在使用同步鎖的線程釋放掉后才能使用。
synchronized規(guī)則:
- 當一個線程訪問某對象的synchronized方法或synchronized代碼塊時鸦泳,其它線程對該對象的該synchronized方法或者synchronized代碼塊訪問將被受阻银锻;
- 當一個線程訪問某個對象的synchronized方法或synchronized代碼塊時,其它線程可以訪問該對象的非synchronized方法或synchronized代碼塊做鹰;
- 當一個線程訪問某個對象的synchronized方法或synchronized代碼塊時击纬,當其它對象訪問該對象的synchronized方法或synchronized代碼塊時,其它對象的線程將被受阻誊垢。
public class RunnableTest implements Runnable{
@Override
public void run() {
synchronized(this){
for(int i=0;i<3;i++){
System.out.println(Thread.currentThread().getName()+" num:" + i );
}
}
}
}
class Test{
public static void main(String[] args){
RunnableTest runnable = new RunnableTest();
Thread thread1 = new Thread(runnable);
Thread thread2 = new Thread(runnable);
Thread thread3 = new Thread(runnable);
thread1.start();//只有start()后才進入就緒狀態(tài)
thread2.start();
thread3.start();
}
}
運行結(jié)果
- Thread-0 num:0
- Thread-0 num:1
- Thread-0 num:2
- Thread-2 num:0
- Thread-2 num:1
- Thread-2 num:2
- Thread-1 num:0
- Thread-1 num:1
- Thread-1 num:2
結(jié)論:
當一個線程訪問對象的synchronized方法或者代碼塊掉弛,其它線程將會被阻塞。Thread0喂走、Thread1殃饿、Thread2共用RunnableTest實現(xiàn)Runnable接口的同步鎖,當一個線程運行synchronized()代碼塊時候芋肠,其它線程需要等待正在運行的線程釋放同步鎖后才能運行乎芳。
再來看下使用Thread方式實現(xiàn)多線程的獲取同步鎖的執(zhí)行流程
class ThreadTest extends Thread{
int num = 10;
@Override
public void run() {
synchronized(this){
for(int i=0;i<3;i++){
System.out.println(Thread.currentThread().getName()+" num:" + i );
}
}
}
}
class Test{
public static void main(String[] args){
ThreadTest threadTest1 = new ThreadTest();
ThreadTest threadTest2 = new ThreadTest();
ThreadTest threadTest3 = new ThreadTest();
threadTest1.start();
threadTest2.start();
threadTest3.start();
}
}
運行結(jié)果
- Thread-0 num:0
- Thread-0 num:1
- Thread-0 num:2
- Thread-2 num:0
- Thread-1 num:0
- Thread-2 num:1
- Thread-1 num:1
- Thread-2 num:2
- Thread-1 num:2
結(jié)論:
發(fā)現(xiàn)并沒有我們之前說的Thread0、Thread1帖池、Thread2阻塞順序執(zhí)行奈惑,這個主要是和Thread形式創(chuàng)建多線程有關(guān),trhreadTest1睡汹、trhreadTest2肴甸、trhreadTest3是三個不同的對象,它們是通過new ThreadTest()創(chuàng)建的三個對象囚巴,這里synchronized(this)是指的ThreadTest對象原在,所以threadTest1、threadTest2彤叉、threadTest3是獲取的三個不同的同步鎖庶柿。而上面使用RunnableTest方式實現(xiàn)的多線程,this是指的RunnableTest秽浇,這樣三個線程使用的是同一個對象的同步鎖浮庐。
當一個進程訪問對象的同步鎖時,其它線程可以訪問這個對象的非synchronize代碼塊
class ThreadTest2{
public void synMethod(){
synchronized (this){
for(int i=0;i<3;i++){
System.out.println(Thread.currentThread().getName() + " num: " + i);
}
}
}
public void nonSynMethod(){
for(int i=0;i<3;i++){
System.out.println(Thread.currentThread().getName() + " num:" + i);
}
}
}
class Test{
public static void main(String[] args){
final ThreadTest2 threadTest = new ThreadTest2();
Thread thread1 = new Thread(new Runnable() {
@Override
public void run() {
threadTest.synMethod();
}
});
Thread thread2 = new Thread(new Runnable() {
@Override
public void run() {
threadTest.nonSynMethod();
}
});
thread1.start();
thread2.start();
}
}
返回結(jié)果
- Thread-0 num:0
- Thread-0 num:1
- Thread-1 num:0
- Thread-0 num:2
- Thread-1 num:1
- Thread-1 num:2
結(jié)論:
thread1訪問對象的synchronize代碼塊柬焕,thread2訪問非synchronized代碼塊审残。thread2并沒有因為thread1受阻梭域。
當一個線程訪問一個對象的synchronized方法或代碼塊,其它線程訪問這個對象的其它synchronized也是受阻的维苔。
class ThreadTest2{
public void synMethod1(){
synchronized (this){
for(int i=0;i<3;i++){
System.out.println(Thread.currentThread().getName() + " num: " + i);
}
}
}
public void synMethod2(){
synchronized (this){
for(int i=0;i<3;i++){
System.out.println(Thread.currentThread().getName() + " num:" + i);
}
}
}
}
class Test{
public static void main(String[] args){
final ThreadTest2 threadTest = new ThreadTest2();
Thread thread1 = new Thread(new Runnable() {
@Override
public void run() {
threadTest.synMethod1();
}
});
Thread thread2 = new Thread(new Runnable() {
@Override
public void run() {
threadTest.synMethod2();
}
});
thread1.start();
thread2.start();
}
}
返回結(jié)果
- Thread-0 num:0
- Thread-0 num:1
- Thread-0 num:2
- Thread-1 num:0
- Thread-1 num:1
- Thread-1 num:2
結(jié)論:thread1碰辅、thread2都會調(diào)用ThreadTest2的synchronized(this)代碼塊,而這個this都是ThreadTest2介时,所以線程2需要等到線程1執(zhí)行完synchronized才能執(zhí)行。
synchronized方法和synchronized代碼塊
synchronized方法是用synchronized修飾類方法凌彬,synchronized代碼塊是用synchronized修飾代碼塊的沸柔。synchronized代碼塊可以更精準的控制限制區(qū)域,有時效率也是比synchronized方法高的铲敛。
class ThreadTest2{
public synchronized void synMethod1(){
for(int i=0;i<3;i++){
System.out.println(Thread.currentThread().getName() + " num: " + i);
}
}
public void synMethod2(){
//this獲取當前對象的同步鎖褐澎,如果修改成xxx,則獲取xxx的同步鎖
synchronized (this){
for(int i=0;i<3;i++){
System.out.println(Thread.currentThread().getName() + " num:" + i);
}
}
}
}
實例鎖和全局鎖
- 實例鎖:如果鎖在某一個實例上面伐蒋,那么該鎖就是實例鎖工三。如果這個類是單例,那么這個鎖也具有全局鎖的概念先鱼。實例鎖使用synchronized關(guān)鍵字俭正。
- 全局鎖:如果鎖針對的是一個類上面,無論多少個實例共享這個鎖焙畔。全局鎖使用static synchronized掸读,或者鎖在該類的class或classloader上面。
class SomeLock{
public synchronized void intanceLockA(){}
public synchronized void instanceLockB(){};
public static synchronized void globalLockA(){};
public static synchronized void globalLockB(){};
}
- x.instaceLockA()和x.instanceLockB()宏多,二者不能同時被訪問儿惫,因為二者都是訪問的都是x的實例鎖。
- x.instaceLockA()和y.instaceLockA()伸但,二者可以同時被訪問肾请,因為二者訪問的不是同一個對象的鎖。
- x.globalLocckA()和y.globalLockB()更胖,二者不能同時訪問铛铁,因為y.globalLockB()相當于SomeLock.globalLockB(),x.globalLockA()相當于SomeLock.globalLockA()函喉,二者使用的是同一個同步鎖避归,所以不能同時被訪問。
- x.instaceLocakA()和b.globalLockA()管呵,二者可以同時被訪問梳毙,因為一個是示例的鎖,一個是類的鎖捐下。
線程的等待與喚醒
線程的等待與喚醒使用了Object類中的wait()账锹、wait(long timeout)萌业、wait(long timeout,int nanos)、notify()奸柬、notifyAll()
- wait():使線程進入等待狀態(tài)(等待阻塞)生年,直到其它線程調(diào)用該對象的 notify()或notifyAll(),當前線程會被喚醒(進入就緒狀態(tài))廓奕。
- wait(long timeout):使線程進入等待狀態(tài)(等待阻塞)抱婉,直到其它線程調(diào)用該對象的notify()或notifyAll()或超過了指定時間,當前線程會被喚醒(進入就緒狀態(tài))桌粉。
- wait(long timeout,int nanos):使線程進入等待狀態(tài)(等待阻塞)蒸绩,直到其它線程調(diào)用該對象的notify()或notifyAll()或超過了指定時間或被其它線程中斷,當前線程會被喚醒(進入就緒狀態(tài))铃肯。
- notify():喚醒在此對象監(jiān)視器(同步鎖的實現(xiàn)原理)上等待的單個線程患亿。
- notifyAll():喚醒在此對象監(jiān)視器上等待的多個線程。
注意:
wait()的作用是讓當前線程等待押逼,當前線程指的是正在cpu運行的線程步藕,而不是調(diào)用wait()方法的線程。wait()挑格、notify()咙冗、notifyAll()都是屬于Object類下邊的方法,之所以在Object下面而沒有在Thread類下面恕齐,主要原因就是同步鎖乞娄。
wait()和notify()都是對對象的同步鎖進行操作,同步鎖是對象持有的显歧,并且每個對象有且僅有一個仪或。
線程讓步y(tǒng)ield()
yield()的作用是讓步。讓當前線程由運行狀態(tài)進入就緒狀態(tài)士骤,從而讓其他具有高優(yōu)先級的線程獲取cpu執(zhí)行范删。但是并不會保證當前線程調(diào)用yield()后,其它同等級線程一定獲取到cpu執(zhí)行權(quán)拷肌。也有可能當前線程又進入到運行狀態(tài)到旦。
yield()與wait()的區(qū)別
- wait()會由運行狀態(tài)進入等待狀態(tài)(阻塞狀態(tài)),而yield()會從運行狀態(tài)進入就緒狀態(tài)巨缘。
- wait()會釋放對象的同步鎖添忘,而yield()是不會釋放對象的同步鎖的。
線程休眠sleep()
sleep()在Thread.class類中定義若锁,讓當前線程由運行狀態(tài)進入休眠狀態(tài)(阻塞狀態(tài))搁骑。sleep()需要指定休眠時間,線程休眠時間會大于等于該休眠時間;線程被重新喚醒時會由阻塞狀體進入就緒狀態(tài)仲器。
sleep()與wait()區(qū)別
sleep()和wait()都會讓線程由運行狀態(tài)進入阻塞狀態(tài)煤率,但是wait()會釋放對象同步鎖,而sleep()不會釋放同步鎖乏冀。
線程join()
join()在Thread.class類中定義蝶糯,讓主線程等待子線程結(jié)束后才能繼續(xù)運行。
// 主線程
public class Father extends Thread {
public void run() {
Son s = new Son();
s.start();
s.join();
...
}
}
// 子線程
public class Son extends Thread {
public void run() {
...
}
}
Son線程是在Father線程中創(chuàng)建的辆沦,并且調(diào)用了s.join()昼捍,這樣Father線程要等到Son線程執(zhí)行完成后,才會執(zhí)行众辨《巳可以查看下join()的源代碼:
public final void join() throws InterruptedException {
join(0);
}
public final synchronized void join(long millis)
throws InterruptedException {
long base = System.currentTimeMillis();
long now = 0;
if (millis < 0) {
throw new IllegalArgumentException("timeout value is negative");
}
if (millis == 0) {
while (isAlive()) {
wait(0);
}
} else {
while (isAlive()) {
long delay = millis - now;
if (delay <= 0) {
break;
}
wait(delay);
now = System.currentTimeMillis() - base;
}
}
}
join()中通過wait()進行等待,所以即使是子線程調(diào)用的join()鹃彻,而真實等待的是正在執(zhí)行的父進程。
線程優(yōu)先級
在操作系統(tǒng)中妻献,線程可以劃分優(yōu)先級蛛株,優(yōu)先級較高的線程被CPU優(yōu)先執(zhí)行。設置線程優(yōu)先級就是幫助“線程規(guī)劃器”確定下次選哪一個線程來優(yōu)先執(zhí)行育拨。
java中線程優(yōu)先級從1~10谨履,默認是5。Thead類提供了3個常量預定義優(yōu)先級的值:
public final static int MIN_PRIORITY = 1;
public final static int NORM_PRIORITY = 5;
public final static int MAX_PRIORITY = 10;
線程優(yōu)先級具有繼承性熬丧,這里的繼承性是指比如在A線程中啟動B線程笋粟,那么B線程就有與A線程同樣的優(yōu)先級。
需要注意的是析蝴,高優(yōu)先級的線程總是大部分都會先執(zhí)行完成害捕,但是并不代表高優(yōu)先級的線程執(zhí)行完成后,再去執(zhí)行低優(yōu)先級的線程闷畸。高優(yōu)先級尝盼,只說明CPU盡量將執(zhí)行資源給優(yōu)先級比較高的線程。
守護線程
在Java線程有兩種線程佑菩,非守護線程(又稱為用戶線程)和守護線程(Daemon)盾沫。
守護線程是一種特殊的線程,它的特性就是陪伴殿漠,當進程中不存在非守護線程了赴精,則守護線程自動銷毀。比如GC線程就是典型的守護線程绞幌,當進程中沒有非守護線程了蕾哟,則垃圾回收線程自動銷毀。
比如下面測試用例:
class DaemonThread extends Thread {
@Override
public void run() {
int i =0;
try {
while (true) {
System.out.println(i++);
Thread.sleep(1000);
}
}catch (InterruptedException e) {
e.printStackTrace();
}
}
}
public class ThreadTest {
public static void main(String[] args) throws InterruptedException {
DaemonThread daemonThread = new DaemonThread();
daemonThread.setDaemon(true);
daemonThread.start();
Thread.sleep(5000);
System.out.println("main線程退出,daemonThread也不會執(zhí)行了");
}
}
打印結(jié)果:
0
1
2
3
4
main線程退出渐苏,daemonThread也不會執(zhí)行了
因為DaemonThread是守護線程掀潮,而main線程為非守護線程,當main線程退出后琼富,daemonThread也會退出仪吧。
關(guān)注我
歡迎關(guān)注我的公眾號,會定期推送優(yōu)質(zhì)技術(shù)文章鞠眉,讓我們一起進步薯鼠、一起成長!
公眾號搜索:data_tc
或直接掃碼:??