一区二区三区在线-一区二区三区亚洲视频-一区二区三区亚洲-一区二区三区午夜-一区二区三区四区在线视频-一区二区三区四区在线免费观看

服務器之家:專注于服務器技術及軟件下載分享
分類導航

PHP教程|ASP.NET教程|JAVA教程|ASP教程|

服務器之家 - 編程語言 - JAVA教程 - Java 高并發二:多線程基礎詳細介紹

Java 高并發二:多線程基礎詳細介紹

2020-06-14 11:27Hosee JAVA教程

本文主要介紹Java 高并發多線程的知識,這里整理詳細的資料來解釋線程的知識,有需要的學習高并發的朋友可以參考下

本系列基于煉數成金課程,為了更好的學習,做了系列的記錄。 本文主要介紹 1.什么是線程 2.線程的基本操作 3.守護線程 4.線程優先級 5.基本的線程同步操作

1. 什么是線程

線程是進程內的執行單元

Java 高并發二:多線程基礎詳細介紹

某個進程當中都有若干個線程。

線程是進程內的執行單元。

使用線程的原因是,進程的切換是非常重量級的操作,非常消耗資源。如果使用多進程,那么并發數相對來說不會很高。而線程是更細小的調度單元,更加輕量級,所以線程會較為廣泛的用于并發設計。

在Java當中線程的概念和操作系統級別線程的概念是類似的。事實上,Jvm將會把Java中的線程映射到操作系統的線程區。

2. 線程的基本操作

2.1 線程狀態圖

Java 高并發二:多線程基礎詳細介紹

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

當new出一個線程時,其實線程并沒有工作。它只是生成了一個實體,當你調用這個實例的start方法時,線程才真正地被啟動。啟動后到Runnable狀態,Runnable表示該線程的資源等等已經被準備好,已經可以執行了,但是并不表示一定在執行狀態,由于時間片輪轉,該線程也可能此時并沒有在執行。對于我們來說,該線程可以認為已經被執行了,但是是否真實執行,還得看物理cpu的調度。當線程任務執行結束后,線程就到了Terminated狀態。

有時候在線程的執行當中,不可避免的會申請某些鎖或某個對象的監視器,當無法獲取時,這個線程會被阻塞住,會被掛起,到了Blocked狀態。如果這個線程調用了wait方法,它就處于一個Waiting狀態。進入Waiting狀態的線程會等待其他線程給它notify,通知到之后由Waiting狀態又切換到Runnable狀態繼續執行。當然等待狀態有兩種,一種是無限期等待,直到被notify。一直則是有限期等待,比如等待10秒還是沒有被notify,則自動切換到Runnable狀態。

2.2 新建線程

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

這樣就開啟了一個線程。
有一點需要注意的是

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

直接調用run方法是無法開啟一個新線程的。

start方法其實是在一個新的操作系統線程上面去調用run方法。換句話說,直接調用run方法而不是調用start方法的話,它并不會開啟新的線程,而是在調用run的當前的線程當中執行你的操作。

?
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
Thread thread = new Thread("t1")
{
 @Override
 public void run()
 {
 // TODO Auto-generated method stub
 System.out.println(Thread.currentThread().getName());
 }
};
thread.start();
如果調用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。(直接調用run其實就是一個普通的函數調用而已,并沒有達到多線程的作用)

run方法的實現有兩種方式

第一種方式,直接覆蓋run方法,就如剛剛代碼中所示,最方便的用一個匿名類就可以實現。

?
1
2
3
4
5
6
7
8
9
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()實現了Runnable接口。

在張孝祥的視頻中,推薦第二種方式,稱其更加面向對象。

2.3 終止線程

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

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

Java 高并發二:多線程基礎詳細介紹

當寫線程得到鎖以后開始寫入數據,寫完id = 1,在準備將name = 1時被stop,釋放鎖。讀線程獲得鎖進行讀操作,讀到的id為1,而name還是0,導致了數據不一致。

最重要的是這種錯誤不會拋出異常,將很難被發現。

2.4 線程中斷

線程中斷有3種方法

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

什么是線程中斷呢?

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

?
1
2
3
4
5
6
public void run(){//線程t1
 while(true){
 Thread.yield();
 }
}
t1.interrupt();

這樣使線程t1中斷,是不會有效果的,只是更改了中斷狀態位。

如果希望非常優雅地終止這個線程,就該這樣做

?
1
2
3
4
5
6
7
8
9
10
11
public void run(){
 while(true)
 {
 if(Thread.currentThread().isInterrupted())
 {
  System.out.println("Interruted!");
  break;
 }
 Thread.yield();
 }
}

使用中斷,就對數據一致性有了一定的保證。

對于可取消的阻塞狀態中的線程, 比如等待在這些函數上的線程, Thread.sleep(), Object.wait(), Thread.join(), 這個線程收到中斷信號后, 會拋出InterruptedException, 同時會把中斷狀態置回為false.

對于取消阻塞狀態中的線程,可以這樣抒寫代碼:

?
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
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");
  //設置中斷狀態,拋出異常后會清除中斷標記位
  Thread.currentThread().interrupt();
 }
 Thread.yield();
 }
}

2.5 線程掛起

掛起(suspend)和繼續執行(resume)線程

suspend()不會釋放鎖

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

原因在于,suspend不釋放鎖,因此沒有線程可以訪問被它鎖住的臨界區資源,直到被其他線程resume。因為無法控制線程運行的先后順序,如果其他線程的resume方法先被運行,那則后運行的suspend,將一直占有這把鎖,造成死鎖發生。

用以下代碼來模擬這個場景

?
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
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同時爭奪一把鎖,爭奪到的線程suspend,然后再resume,按理來說,應該某個線程爭奪后被resume釋放了鎖,然后另一個線程爭奪掉鎖,再被resume。
結果輸出是:

in t1
in t2

說明兩個線程都爭奪到了鎖,但是控制臺的紅燈還是亮著的,說明t1,t2一定有線程沒有執行完。我們dump出堆來看看

Java 高并發二:多線程基礎詳細介紹

發現t2一直被suspend。這樣就造成了死鎖。

2.6 join和yeild

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

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

?
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
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去掉,則主線程會直接運行結束,i的值會很小。如果有join,打印出的i的值一定是10000000。
那么join是怎么實現的呢?

join的本質

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

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

在javadoc中,找到了相關解釋。當一個線程運行完成終止后,將會調用notifyAll方法去喚醒等待在當前線程實例上的所有線程,這個操作是jvm自己完成的。

所以javadoc中還給了我們一個建議,不要使用wait和notify/notifyall在線程實例上。因為jvm會自己調用,有可能與你調用期望的結果不同。

3. 守護線程

在后臺默默地完成一些系統性的服務,比如垃圾回收線程、JIT線程就可以理解為守護線程。
當一個Java應用內,所有非守護進程都結束時,Java虛擬機就會自然退出。
此前有寫過一篇python中如何實現,查看這里。

而Java中變成守護進程就相對簡單了。

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

這樣就開啟了一個守護線程。

?
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
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();
 }
}

當線程dt不是一個守護線程時,在運行后,我們能看到控制臺輸出hi
當在start之前加入

dt.setDaemon(true);

控制臺就直接退出了,并沒有輸出。

4. 線程優先級

Thread類中有3個變量定義了線程優先級。

?
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
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();
 }
}

讓一個高優先級的線程和低優先級的線程同時爭奪一個鎖,看看哪個最先完成。
當然并不一定是高優先級一定先完成。再多次運行后發現,高優先級完成的概率比較大,但是低優先級還是有可能先完成的。

5. 基本的線程同步操作

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

這一節內容詳情請看以前寫的一篇Blog

主要要注意的是

synchronized有三種加鎖方式:

指定加鎖對象:對給定對象加鎖,進入同步代碼前要獲得給定對象的鎖。
直接作用于實例方法:相當于對當前實例加鎖,進入同步代碼前要獲得當前實例的鎖。
直接作用于靜態方法:相當于對當前類加鎖,進入同步代碼前要獲得當前類的鎖。

作用于實例方法,則不要new兩個不同的實例

作用于靜態方法,只要類一樣就可以了,因為加的鎖是類.class,可以new兩個不同實例。

wait和notify的用法:

用什么鎖住,就用什么調用wait和notify

本文就不細說了。

延伸 · 閱讀

精彩推薦
主站蜘蛛池模板: 亚州男人的天堂 | 俄罗斯一级毛片免费播放 | 性欧美高清强烈性视频 | 亚洲精美视频 | 国产精品欧美亚洲韩国日本 | 成年视频在线观看免费 | 精品一久久香蕉国产线看播放 | 天天插在线视频 | 狠狠综合久久综合网站 | 香蕉久久夜色精品国产小优 | 免费一级特黄特色大片在线观看 | 亚洲AV久久久噜噜噜久久 | 欧美日韩中文字幕在线视频 | 蜜桃破解版免费看nba | 调教全程肉动画片在线观看 | 视频污版| jk制服白丝超短裙流白浆 | 欧美怡红院视频一区二区三区 | 四虎音影 | 天堂8在线天堂资源bt | 美女吃jj| 国语自产拍在线播放不卡 | 免费观看a毛片一区二区不卡 | 欧美a在线观看 | 羞羞视频动漫 | 亚洲国产精品自在现线让你爽 | 四虎精品影视 | 国产福利在线观看91精品 | 欧美日韩精品一区二区三区视频播放 | a4yy欧美一区二区三区 | 99久久香蕉国产综合影院 | 黄色aaa级片 | chinese456老人gay china外卖员gay国产xnxx | 国产精品资源在线观看网站 | 午夜五月天 | 毛片一级免费 | ass老妇黑森林pic | 久久天堂成人影院 | 91在线免费看 | 99精品在线视频观看 | 天堂在线中文无弹窗全文阅读 |