多執行緒詳解(二)
在正式介紹執行緒建立的第二種方法之前,我們接著多執行緒詳解(一),講一下:對執行緒的記憶體圖、執行緒的狀態,為下面的學習打下基礎,小夥伴們不要急喲!!
一、多執行緒執行的記憶體圖(ps.博主沒有找到合適的畫圖工具,歡迎大神們貢獻啊)
class person extends Thread
{
int i;
private String name;
person(String name)
{
//給執行緒命名,使用super
super(name);
this.name=name;
}
public void run()
{
show();
}
public void show()
{
for(i=0;i<=10;i++)
{
System.out.println(name+"x="+i+"...name="+Thread.currentThread().getName());
}
}
}
public class demo1{
public static void main(String[] args){
person p1=new person("zhangsan" );
person p2=new person("lisi");
p1.start();
p2.start();
System.out.println("出來吧,name="+Thread.currentThread().getName());
}
}
每開啟一條執行緒就多一條執行路徑,所以以上述程式碼為例——共有三條路徑
-
(1)main執行緒路徑
-
(2)run執行緒路徑
-
(3)另一條run執行緒路徑
1、每一條路徑上獨立的進行壓棧彈棧,每一條執行緒上都要自己的棧區,所以互相不影響
2、當一條路勁上的程式碼執行完,該執行緒自動結束
3、當main執行緒提前執行結束後,其他兩條執行緒依舊執行。所以可以看出每條執行緒之間是完全獨立的,當一條執行緒有異常時,其他的執行緒繼續執行不受影響。
4、為多執行緒安全分析埋下伏筆(這裡不詳細講多執行緒安全了,以後會更行的哦)
二、執行緒的狀態(重點)
1、執行(使用start方法):具備執行資格和執行權
2、消亡(任務執行完了,自動消亡OR使用stop方法強制消亡):既不具備執行資格也不具備執行資格
3、凍結:
《1》使用sleep(time)方法———時間到了,自動恢復————既不擁有執行權也不擁有執行資格(釋放執行權的同時釋放執行資格)
《2》使用wait()方法————使用notify()方法喚醒————既不擁有執行權也不擁有執行資格(釋放執行權的同時釋放執行資格))
解釋:如果一個執行緒處於執行狀態,那麼CPU就具備執行資格和擁有CPU的執行權,那麼什麼是執行權?什麼又是執行資格呢??
《1》CPU執行資格:CPU可以處理,在CPU的佇列中排隊等待處理
《2》CPU執行權:正在被CPU處理
下面我們通過舉一個例子,解釋一下什麼是臨時阻塞
假設A、B、C、D四個執行緒都處於執行狀態,當A執行的時候,A具備執行資格和執行權,此時其他三個具備執行資格但正在等待執行權,這種狀態就是臨時阻塞狀態
所以,臨時阻塞狀態———具備執行資格但是不具備執行權
在一個時刻,只有一個執行緒擁有執行權,而其他執行的執行緒都是臨時阻塞的。
辨析:臨時阻塞狀態和凍結狀態的區別
-
(1)執行資格和執行權的不同
-
(2)凍結是由程式設計師控制的而臨時阻塞是cpu執行控制的
-
(3)臨時阻塞的時間往往是極短的,但是凍結的時間是自定義的,相比較而言是長的。
-
(4)臨時阻塞是執行時的一種狀態,所以凍結喚醒後,必須經過執行才能有時間阻塞。
三、建立執行緒的第二種方式
1、繼承Thread類建立方法的侷限性
如果原來的類已經繼承了其他類,使用第一種方式繼承Thread,就存在了多繼承,在java中是不允許的
例如:
class person extends fu
{ int i;
private String name;
person(String name)
{
super(name);
this.name=name;
}
public void run()
{
show();
}
public void show()
{
for(i=0;i<=10;i++)
{
System.out.println(name+"x="+i+"...name="+Thread.currentThread().getName());
}
}
}
public class demo1{
public static void main(String[] args){
person p1=new person("zhangsan");
person p2=new person("lisi");
p1.start();
p2.start();
System.out.println("出來吧,name="+Thread.currentThread().getName());
}
}
但是在這種情況下,如果你依舊要使用繼承Thread的方法建立執行緒也是可以的,就是利用再增加一層繼承的方法來實現。示例如下:
class fu extends Thread
{
int i;
private String name;
person(String name)
{
super(name);
this.name=name;
}
public void run()
{
show();
}
public void show()
{
for(i=0;i<=10;i++)
{
System.out.println(name+"x="+i+"...name="+Thread.currentThread().getName());
}
}
}
class person extends fu
{
}
public class demo1{
public static void main(String[] args){
person p1=new person("zhangsan");
person p2=new person("lisi");
p1.start();
p2.start();
System.out.println("出來吧,name="+Thread.currentThread().getName());
}
}
從上面的示例我們可以看出,多增加一層繼承關係,其實是開發者強制加上去的,而且實現過程也很繁瑣,所以我們應該拋棄此方法,重新思考……..
2、使用介面的方法建立執行緒
我們現在的目的是讓person類建立一個執行緒,但是person類不可以再繼承。由於需要擴充套件person類功能,所有我們很自然的想到使用————介面
建立步驟:
-
《1》定義類實現Runnable介面
-
《2》覆蓋介面中的run方法,將執行緒的任封裝到run方法中
-
《3》通過Thread類建立執行緒物件,並將Runnable介面的子類物件作為Thread類的建構函式進行傳遞。
-
《4》呼叫執行緒物件的start方法開啟執行緒
為什麼第三步中,要將Runnable介面的子類物件作為Thread類的建構函式進行傳遞呢?
因為,執行緒的任務都是封裝在Runnable介面子類物件的run方法中。所以要線上程物件建立時就明確要執行的任務。
示例程式碼如下:
//實現Runable介面,
class person implements Runnable
{ int i;
private String name;
person(String name)
{
this.name=name;
}
// 覆蓋run方法
public void run()
{
show();
}
public void show()
{
for(i=0;i<=10;i++)
{
System.out.println(name+"x="+i+"...name="+Thread.currentThread().getName());
}
}
}
public class demo1{
public static void main(String[] args){
//這個不是執行緒物件
// person p1=new person("zhangsan");
// person p2=new person("lisi");
//這個是執行緒物件
person p=new person("zhangsan");
Thread t1=new Thread(p);
Thread t2=new Thread(p);
t1.start();
t2.start();
System.out.println("出來吧,name="+Thread.currentThread().getName());
}
}
執行結果: