最新国产好看的视频,伊人天堂AV在线,国产Aaaaaa视频,蜜臀视频在线观看一区,人妻av色图,密臀久久久精品影片,青青视频免费观看毛片,久草在线观看视,国产三级精品色情在线

Java 高并發(fā)二:多線程基礎(chǔ)詳細(xì)介紹

 更新時(shí)間:2016年09月09日 17:09:44   作者:Hosee  
本文主要介紹Java 高并發(fā)多線程的知識(shí),這里整理詳細(xì)的資料來解釋線程的知識(shí),有需要的學(xué)習(xí)高并發(fā)的朋友可以參考下

本系列基于煉數(shù)成金課程,為了更好的學(xué)習(xí),做了系列的記錄。 本文主要介紹 1.什么是線程 2.線程的基本操作 3.守護(hù)線程 4.線程優(yōu)先級(jí) 5.基本的線程同步操作

1. 什么是線程

線程是進(jìn)程內(nèi)的執(zhí)行單元

某個(gè)進(jìn)程當(dāng)中都有若干個(gè)線程。

線程是進(jìn)程內(nèi)的執(zhí)行單元。

使用線程的原因是,進(jìn)程的切換是非常重量級(jí)的操作,非常消耗資源。如果使用多進(jìn)程,那么并發(fā)數(shù)相對(duì)來說不會(huì)很高。而線程是更細(xì)小的調(diào)度單元,更加輕量級(jí),所以線程會(huì)較為廣泛的用于并發(fā)設(shè)計(jì)。

在Java當(dāng)中線程的概念和操作系統(tǒng)級(jí)別線程的概念是類似的。事實(shí)上,Jvm將會(huì)把Java中的線程映射到操作系統(tǒng)的線程區(qū)。

2. 線程的基本操作

2.1 線程狀態(tài)圖

上圖是Java中線程的基本操作。

當(dāng)new出一個(gè)線程時(shí),其實(shí)線程并沒有工作。它只是生成了一個(gè)實(shí)體,當(dāng)你調(diào)用這個(gè)實(shí)例的start方法時(shí),線程才真正地被啟動(dòng)。啟動(dòng)后到Runnable狀態(tài),Runnable表示該線程的資源等等已經(jīng)被準(zhǔn)備好,已經(jīng)可以執(zhí)行了,但是并不表示一定在執(zhí)行狀態(tài),由于時(shí)間片輪轉(zhuǎn),該線程也可能此時(shí)并沒有在執(zhí)行。對(duì)于我們來說,該線程可以認(rèn)為已經(jīng)被執(zhí)行了,但是是否真實(shí)執(zhí)行,還得看物理cpu的調(diào)度。當(dāng)線程任務(wù)執(zhí)行結(jié)束后,線程就到了Terminated狀態(tài)。

有時(shí)候在線程的執(zhí)行當(dāng)中,不可避免的會(huì)申請(qǐng)某些鎖或某個(gè)對(duì)象的監(jiān)視器,當(dāng)無法獲取時(shí),這個(gè)線程會(huì)被阻塞住,會(huì)被掛起,到了Blocked狀態(tài)。如果這個(gè)線程調(diào)用了wait方法,它就處于一個(gè)Waiting狀態(tài)。進(jìn)入Waiting狀態(tài)的線程會(huì)等待其他線程給它notify,通知到之后由Waiting狀態(tài)又切換到Runnable狀態(tài)繼續(xù)執(zhí)行。當(dāng)然等待狀態(tài)有兩種,一種是無限期等待,直到被notify。一直則是有限期等待,比如等待10秒還是沒有被notify,則自動(dòng)切換到Runnable狀態(tài)。

2.2 新建線程

Thread thread = new Thread();
thread.start();

這樣就開啟了一個(gè)線程。
有一點(diǎn)需要注意的是

Thread thread = new Thread();
thread.run();

直接調(diào)用run方法是無法開啟一個(gè)新線程的。

start方法其實(shí)是在一個(gè)新的操作系統(tǒng)線程上面去調(diào)用run方法。換句話說,直接調(diào)用run方法而不是調(diào)用start方法的話,它并不會(huì)開啟新的線程,而是在調(diào)用run的當(dāng)前的線程當(dāng)中執(zhí)行你的操作。

Thread thread = new Thread("t1")
{
 @Override
 public void run()
 {
 // TODO Auto-generated method stub
 System.out.println(Thread.currentThread().getName());
 }
};
thread.start();
如果調(diào)用start,則輸出是t1
Thread thread = new Thread("t1")
{
 @Override
 public void run()
 {
 // TODO Auto-generated method stub
 System.out.println(Thread.currentThread().getName());
 }
};
thread.run();

如果是run,則輸出main。(直接調(diào)用run其實(shí)就是一個(gè)普通的函數(shù)調(diào)用而已,并沒有達(dá)到多線程的作用)

run方法的實(shí)現(xiàn)有兩種方式

第一種方式,直接覆蓋run方法,就如剛剛代碼中所示,最方便的用一個(gè)匿名類就可以實(shí)現(xiàn)。

Thread thread = new Thread("t1")
{
 @Override
 public void run()
 {
 // TODO Auto-generated method stub
 System.out.println(Thread.currentThread().getName());
 }
};

第二種方式

Thread t1=new Thread(new CreateThread3());

CreateThread3()實(shí)現(xiàn)了Runnable接口。

在張孝祥的視頻中,推薦第二種方式,稱其更加面向?qū)ο蟆?/p>

2.3 終止線程

Thread.stop() 不推薦使用。它會(huì)釋放所有monitor
在源碼中已經(jīng)明確說明stop方法被Deprecated,在Javadoc中也說明了原因。

原因在于stop方法太過"暴力"了,無論線程執(zhí)行到哪里,它將會(huì)立即停止掉線程。

當(dāng)寫線程得到鎖以后開始寫入數(shù)據(jù),寫完id = 1,在準(zhǔn)備將name = 1時(shí)被stop,釋放鎖。讀線程獲得鎖進(jìn)行讀操作,讀到的id為1,而name還是0,導(dǎo)致了數(shù)據(jù)不一致。

最重要的是這種錯(cuò)誤不會(huì)拋出異常,將很難被發(fā)現(xiàn)。

2.4 線程中斷

線程中斷有3種方法

public void Thread.interrupt() // 中斷線程
public boolean Thread.isInterrupted() // 判斷是否被中斷
public static boolean Thread.interrupted() // 判斷是否被中斷,并清除當(dāng)前中斷狀態(tài)

什么是線程中斷呢?

如果不了解Java的中斷機(jī)制,這樣的一種解釋極容易造成誤解,認(rèn)為調(diào)用了線程的interrupt方法就一定會(huì)中斷線程。
其實(shí),Java的中斷是一種協(xié)作機(jī)制。也就是說調(diào)用線程對(duì)象的interrupt方法并不一定就中斷了正在運(yùn)行的線程,它只是要求線程自己在合適的時(shí)機(jī)中斷自己。每個(gè)線程都有一個(gè)boolean的中斷狀態(tài)(不一定就是對(duì)象的屬性,事實(shí)上,該狀態(tài)也確實(shí)不是Thread的字段),interrupt方法僅僅只是將該狀態(tài)置為true。對(duì)于非阻塞中的線程, 只是改變了中斷狀態(tài), 即Thread.isInterrupted()將返回true,并不會(huì)使程序停止;

public void run(){//線程t1
 while(true){
 Thread.yield();
 }
}
t1.interrupt();

這樣使線程t1中斷,是不會(huì)有效果的,只是更改了中斷狀態(tài)位。

如果希望非常優(yōu)雅地終止這個(gè)線程,就該這樣做

public void run(){ 
 while(true)
 { 
 if(Thread.currentThread().isInterrupted())
 { 
  System.out.println("Interruted!"); 
  break; 
 } 
 Thread.yield(); 
 } 
}

使用中斷,就對(duì)數(shù)據(jù)一致性有了一定的保證。

對(duì)于可取消的阻塞狀態(tài)中的線程, 比如等待在這些函數(shù)上的線程, Thread.sleep(), Object.wait(), Thread.join(), 這個(gè)線程收到中斷信號(hào)后, 會(huì)拋出InterruptedException, 同時(shí)會(huì)把中斷狀態(tài)置回為false.

對(duì)于取消阻塞狀態(tài)中的線程,可以這樣抒寫代碼:

public void run(){
 while(true){
 if(Thread.currentThread().isInterrupted()){
  System.out.println("Interruted!");
  break;
 }
 try {
  Thread.sleep(2000);
 } catch (InterruptedException e) {
  System.out.println("Interruted When Sleep");
  //設(shè)置中斷狀態(tài),拋出異常后會(huì)清除中斷標(biāo)記位
  Thread.currentThread().interrupt();
 }
 Thread.yield();
 }
}

2.5 線程掛起

掛起(suspend)和繼續(xù)執(zhí)行(resume)線程

suspend()不會(huì)釋放鎖

如果加鎖發(fā)生在resume()之前 ,則死鎖發(fā)生
這兩個(gè)方法都是Deprecated方法,不推薦使用。

原因在于,suspend不釋放鎖,因此沒有線程可以訪問被它鎖住的臨界區(qū)資源,直到被其他線程resume。因?yàn)闊o法控制線程運(yùn)行的先后順序,如果其他線程的resume方法先被運(yùn)行,那則后運(yùn)行的suspend,將一直占有這把鎖,造成死鎖發(fā)生。

用以下代碼來模擬這個(gè)場(chǎng)景

package test;

public class Test
{
 static Object u = new Object();
 static TestSuspendThread t1 = new TestSuspendThread("t1");
 static TestSuspendThread t2 = new TestSuspendThread("t2");

 public static class TestSuspendThread extends Thread
 {
 public TestSuspendThread(String name)
 {
 setName(name);
 }

 @Override
 public void run()
 {
 synchronized (u)
 {
 System.out.println("in " + getName());
 Thread.currentThread().suspend();
 }
 }
 }

 public static void main(String[] args) throws InterruptedException
 {
 t1.start();
 Thread.sleep(100);
 t2.start();
 t1.resume();
 t2.resume();
 t1.join();
 t2.join();
 }
}

讓t1,t2同時(shí)爭(zhēng)奪一把鎖,爭(zhēng)奪到的線程suspend,然后再resume,按理來說,應(yīng)該某個(gè)線程爭(zhēng)奪后被resume釋放了鎖,然后另一個(gè)線程爭(zhēng)奪掉鎖,再被resume。
結(jié)果輸出是:

in t1
in t2

說明兩個(gè)線程都爭(zhēng)奪到了鎖,但是控制臺(tái)的紅燈還是亮著的,說明t1,t2一定有線程沒有執(zhí)行完。我們dump出堆來看看

發(fā)現(xiàn)t2一直被suspend。這樣就造成了死鎖。

2.6 join和yeild

yeild是個(gè)native靜態(tài)方法,這個(gè)方法是想把自己占有的cpu時(shí)間釋放掉,然后和其他線程一起競(jìng)爭(zhēng)(注意yeild的線程還是有可能爭(zhēng)奪到cpu,注意與sleep區(qū)別)。在javadoc中也說明了,yeild是個(gè)基本不會(huì)用到的方法,一般在debug和test中使用。

join方法的意思是等待其他線程結(jié)束,就如suspend那節(jié)的代碼,想讓主線程等待t1,t2結(jié)束以后再結(jié)束。沒有結(jié)束的話,主線程就一直阻塞在那里。

package test;

public class Test
{
 public volatile static int i = 0;

 public static class AddThread extends Thread
 {
 @Override
 public void run()
 {
 for (i = 0; i < 10000000; i++)
 ;
 }
 }

 public static void main(String[] args) throws InterruptedException
 {
 AddThread at = new AddThread();
 at.start();
 at.join();
 System.out.println(i);
 }
}

如果把上述代碼的at.join去掉,則主線程會(huì)直接運(yùn)行結(jié)束,i的值會(huì)很小。如果有join,打印出的i的值一定是10000000。
那么join是怎么實(shí)現(xiàn)的呢?

join的本質(zhì)

while(isAlive())
{
   wait(0);
}

join()方法也可以傳遞一個(gè)時(shí)間,意為有限期地等待,超過了這個(gè)時(shí)間就自動(dòng)喚醒。
這樣就有一個(gè)問題,誰(shuí)來notify這個(gè)線程呢,在thread類中沒有地方調(diào)用了notify?

在javadoc中,找到了相關(guān)解釋。當(dāng)一個(gè)線程運(yùn)行完成終止后,將會(huì)調(diào)用notifyAll方法去喚醒等待在當(dāng)前線程實(shí)例上的所有線程,這個(gè)操作是jvm自己完成的。

所以javadoc中還給了我們一個(gè)建議,不要使用wait和notify/notifyall在線程實(shí)例上。因?yàn)閖vm會(huì)自己調(diào)用,有可能與你調(diào)用期望的結(jié)果不同。

3. 守護(hù)線程

在后臺(tái)默默地完成一些系統(tǒng)性的服務(wù),比如垃圾回收線程、JIT線程就可以理解為守護(hù)線程。
當(dāng)一個(gè)Java應(yīng)用內(nèi),所有非守護(hù)進(jìn)程都結(jié)束時(shí),Java虛擬機(jī)就會(huì)自然退出。
此前有寫過一篇python中如何實(shí)現(xiàn),查看這里。

而Java中變成守護(hù)進(jìn)程就相對(duì)簡(jiǎn)單了。

Thread t=new DaemonT();
t.setDaemon(true);
t.start();

這樣就開啟了一個(gè)守護(hù)線程。

package test;

public class Test
{
 public static class DaemonThread extends Thread
 {
 @Override
 public void run()
 {
 for (int i = 0; i < 10000000; i++)
 {
 System.out.println("hi");
 }
 }
 }

 public static void main(String[] args) throws InterruptedException
 {
 DaemonThread dt = new DaemonThread();
 dt.start();
 }
}

當(dāng)線程dt不是一個(gè)守護(hù)線程時(shí),在運(yùn)行后,我們能看到控制臺(tái)輸出hi
當(dāng)在start之前加入

dt.setDaemon(true);

控制臺(tái)就直接退出了,并沒有輸出。

4. 線程優(yōu)先級(jí)

Thread類中有3個(gè)變量定義了線程優(yōu)先級(jí)。

public final static int MIN_PRIORITY = 1;
public final static int NORM_PRIORITY = 5;
public final static int MAX_PRIORITY = 10;
package test;

public class Test
{
 public static class High extends Thread
 {
 static int count = 0;
 @Override
 public void run()
 {
 while (true)
 {
 synchronized (Test.class)
 {
 count++;
 if (count > 10000000)
 {
 System.out.println("High");
 break;
 }
 }
 }
 }
 }
 public static class Low extends Thread
 {
 static int count = 0;
 @Override
 public void run()
 {
 while (true)
 {
 synchronized (Test.class)
 {
 count++;
 if (count > 10000000)
 {
 System.out.println("Low");
 break;
 }
 }
 }
 }
 }

 public static void main(String[] args) throws InterruptedException
 {
 High high = new High();
 Low low = new Low();
 high.setPriority(Thread.MAX_PRIORITY);
 low.setPriority(Thread.MIN_PRIORITY);
 low.start();
 high.start();
 }
}

讓一個(gè)高優(yōu)先級(jí)的線程和低優(yōu)先級(jí)的線程同時(shí)爭(zhēng)奪一個(gè)鎖,看看哪個(gè)最先完成。
當(dāng)然并不一定是高優(yōu)先級(jí)一定先完成。再多次運(yùn)行后發(fā)現(xiàn),高優(yōu)先級(jí)完成的概率比較大,但是低優(yōu)先級(jí)還是有可能先完成的。

5. 基本的線程同步操作

synchronized 和 Object.wait() Obejct.notify()

這一節(jié)內(nèi)容詳情請(qǐng)看以前寫的一篇Blog

主要要注意的是

synchronized有三種加鎖方式:

指定加鎖對(duì)象:對(duì)給定對(duì)象加鎖,進(jìn)入同步代碼前要獲得給定對(duì)象的鎖。
直接作用于實(shí)例方法:相當(dāng)于對(duì)當(dāng)前實(shí)例加鎖,進(jìn)入同步代碼前要獲得當(dāng)前實(shí)例的鎖。
直接作用于靜態(tài)方法:相當(dāng)于對(duì)當(dāng)前類加鎖,進(jìn)入同步代碼前要獲得當(dāng)前類的鎖。

作用于實(shí)例方法,則不要new兩個(gè)不同的實(shí)例

作用于靜態(tài)方法,只要類一樣就可以了,因?yàn)榧拥逆i是類.class,可以new兩個(gè)不同實(shí)例。

wait和notify的用法:

用什么鎖住,就用什么調(diào)用wait和notify

本文就不細(xì)說了。

相關(guān)文章

  • Java中token的存儲(chǔ)和獲取實(shí)例代碼

    Java中token的存儲(chǔ)和獲取實(shí)例代碼

    關(guān)于java獲取微信Token驗(yàn)證的問題相信很多人都遇見過,尤其是對(duì)剛接觸微信開發(fā)的人來說確實(shí)有點(diǎn)棘手,下面這篇文章主要給大家介紹了關(guān)于Java中token存儲(chǔ)和獲取的相關(guān)資料,需要的朋友可以參考下
    2022-08-08
  • 基于spring boot 命令行啟動(dòng)的一些坑

    基于spring boot 命令行啟動(dòng)的一些坑

    這篇文章主要介紹了spring boot 命令行啟動(dòng)的一些坑,具有很好的參考價(jià)值,希望對(duì)大家有所幫助。如有錯(cuò)誤或未考慮完全的地方,望不吝賜教
    2021-09-09
  • 分布式調(diào)度器之Spring Task 的使用詳解

    分布式調(diào)度器之Spring Task 的使用詳解

    SpringTask是Spring框架中用于任務(wù)調(diào)度的組件,通過簡(jiǎn)單的注解就能實(shí)現(xiàn)定時(shí)任務(wù)的創(chuàng)建和調(diào)度,可以通過配置線程池來實(shí)現(xiàn),本文給大家介紹分布式調(diào)度器之Spring Task 的使用,感興趣的朋友跟隨小編一起看看吧
    2024-10-10
  • SpringBoot MongoDB 索引沖突分析及解決方法

    SpringBoot MongoDB 索引沖突分析及解決方法

    這篇文章主要介紹了SpringBoot MongoDB 索引沖突分析及解決方法,小編覺得挺不錯(cuò)的,現(xiàn)在分享給大家,也給大家做個(gè)參考。一起跟隨小編過來看看吧
    2018-11-11
  • 淺談為什么重寫equals()就要重寫hashCode()

    淺談為什么重寫equals()就要重寫hashCode()

    困擾我很久的問題,一直不明白為什么重寫equals()方法的時(shí)候要重寫hashCode()方法,這次總算弄明白了,作此分享,感興趣的可以了解一下
    2021-10-10
  • springboot定時(shí)任務(wù)詳解

    springboot定時(shí)任務(wù)詳解

    這篇文章主要介紹了springboot定時(shí)任務(wù)的相關(guān)資料,幫助大家更好的理解和使用springboot框架,感興趣的朋友可以了解下
    2021-01-01
  • 關(guān)于Java中你所不知道的Integer詳解

    關(guān)于Java中你所不知道的Integer詳解

    這篇文章主要給大家介紹了關(guān)于Java中你所不知道的一些關(guān)于Integer的相關(guān)資料,文中通過示例代碼介紹的非常詳細(xì),對(duì)大家的學(xué)習(xí)或者工作具有一定的參考學(xué)習(xí)價(jià)值,需要的朋友們下面隨著小編來一起學(xué)習(xí)學(xué)習(xí)吧。
    2017-12-12
  • springboot-mongodb的多數(shù)據(jù)源配置的方法步驟

    springboot-mongodb的多數(shù)據(jù)源配置的方法步驟

    這篇文章主要介紹了springboot-mongodb的多數(shù)據(jù)源配置的方法步驟,文中通過示例代碼介紹的非常詳細(xì),對(duì)大家的學(xué)習(xí)或者工作具有一定的參考學(xué)習(xí)價(jià)值,需要的朋友們下面隨著小編來一起學(xué)習(xí)學(xué)習(xí)吧
    2019-04-04
  • 5個(gè)并發(fā)處理技巧代碼示例

    5個(gè)并發(fā)處理技巧代碼示例

    這篇文章主要介紹了5個(gè)并發(fā)處理技巧代碼示例,具有一定參考價(jià)值,需要的朋友可以了解下。
    2017-10-10
  • javaSE中異常如何處理舉例詳解

    javaSE中異常如何處理舉例詳解

    程序運(yùn)行過程中發(fā)生了不正常的情況,這種不正常的情況叫做異常,下面這篇文章主要給大家介紹了關(guān)于javaSE中異常如何處理的相關(guān)資料,文中通過實(shí)例代碼介紹的非常詳細(xì),需要的朋友可以參考下
    2023-01-01

最新評(píng)論

合水县| 万山特区| 饶平县| 沂南县| 阜新| 高安市| 会泽县| 蓬安县| 民乐县| 天津市| 武威市| 九江市| 莒南县| 阳原县| 波密县| 成都市| 克山县| 中宁县| 资兴市| 永年县| 丘北县| 新丰县| 江达县| 平和县| 天柱县| 同江市| 宁南县| 开化县| 赞皇县| 南召县| 贵定县| 奇台县| 琼结县| 山东| 石狮市| 日土县| 广西| 虞城县| 鹤峰县| 石渠县| 开阳县|