多執行緒詳解(一)
[多執行緒詳解(一)](http://www.neilx.com)
一、概念準備
1、程序
(1)直譯:正在進行中的程式
(2)解釋:執行一個程式時,會在記憶體中為程式開闢空間,這個空間就是一個程序。
(3)注意:一個程序中不可能沒有執行緒,只有有了執行緒才能執行; 程序只開闢空間,並不執行,執行的是執行緒.
2、程序
(1)定義:就是程序中一個負責執行的控制單元(執行路徑)
(3)注意:一個程序中可以有多個執行路徑,稱為多執行緒;一個程序中至少有一個執行緒
3、任務
開啟多個執行緒,是為了同時執行多個內容,這個內容就是任務
二、多執行緒的初步認識
jvm(java虛擬機器)中的多執行緒解析
jvm啟動時,就啟動了多個執行緒:
其中至少有兩個執行緒:執行main函式的主執行緒和負責垃圾回收的執行緒
解釋:在執行main中任務的時候,會開啟一條執行緒,當執行垃圾越來越多,又會有垃圾回收任務,所以垃圾回收執行緒開啟。其中,main方法任務的程式碼都定義主函式程式碼中。
三、垃圾回收執行緒和主函式執行緒示例
1、垃圾回收執行緒
class person extends Object
{
public void finalize()
{
System.out.println("hahaha!" );
}
}
public class demo1{
public static void main(String[] args){
person p1=new person();
person p2=new person();
//呼叫gc方法,執行垃圾回收期
System.gc();
System.out.println("hehehe!");
}
}
執行結果:
![垃圾回收執行緒輸出結果](https://img-blog.csdn.net/20160615152724948)解釋:垃圾回收機制是虛擬機器呼叫Object類的finalize方法。當垃圾回收器已經確定不存在對某物件的引用時,垃圾回收器自動呼叫fianlize方法,子類重寫finalize方法,執行回收動作。程式碼如上
System.gc();是垃圾回收程式,呼叫後,不定時的執行垃圾回收
小插曲:System.gc()與finalize()的辨析:
(1)gc()只能清除在堆上分配的記憶體(純java語言的所有物件都在堆上使用new分配記憶體),而不能清除棧上分配的記憶體(當使用JNI技術時,可能會在棧上分配記憶體,例如java呼叫c程式,而該c程式使用malloc分配記憶體時)。因此,如果某些物件被分配了棧上的記憶體區域,那gc就管不著了,對這樣的物件進行記憶體回收就要靠finalize().
舉個例子來說,當java呼叫非java方法時(這種方法可能是c或是c++的),在非java程式碼內部也許呼叫了c的malloc()函式來分配記憶體,而且除非呼叫那個了free(),否則不會釋放記憶體(因為free()是c的函式),這個時候要進行釋放記憶體的工作,gc是不起作用的,因而需要在finalize()內部的一個固有方法呼叫它(free()).
(2)finalize的工作原理應該是這樣的:一旦垃圾收集器準備好釋放物件佔用的儲存空間,它首先呼叫finalize(),而且只有在下一次垃圾收集過程中,才會真正回收物件的記憶體.所以如果使用finalize(),就可以在垃圾收集期間進行一些重要的清除或清掃工作.
(3)那麼什麼時候呼叫finalize()呢?
-
《1》所有物件被gc()時自動呼叫,比如執行System.gc()的時候.
-
《2》程式退出時為每個物件呼叫一次finalize方法
-
《3》顯式的呼叫finalize方法
(4)除此以外,正常情況下,當某個物件被系統收集為無用資訊的時候,finalize()將被自動呼叫,但是jvm不保證finalize()一定被呼叫,也就是說,finalize()的呼叫是不確定的,這也就是為什麼sun不提倡使用finalize()的原因. 簡單來講,finalize()是在物件被GC回收前會呼叫的方法,而System.gc()強制GC開始回收工作糾正,不是強制,是建議,具體執行要看GC的意思簡單地說,呼叫了 System.gc() 之後,java 在記憶體回收過程中就會呼叫那些要被回收的物件的 finalize() 方法。
2、主執行緒的執行示例
class person extends Object
{
int i;
private String name;
person(String name)
{
this.name=name;
}
public void show()
{
for(i=0;i<=10;i++)
{
System.out.println(name+"x="+i);
}
}
}
public class demo1{
public static void main(String[] args){
person p1=new person("zhangsan");
person p2=new person("lisi");
p1.show();
p2.show();
System.out.println("hehehe!");
}
}
執行流程:p1.show執行—結束後——出棧——p2.show執行——執行完畢—出棧
此時,在執行p1.show時,必須執行完張三後,李四才可以執行,如果想讓張三執行的同時,李四也執行呢?——使用多執行緒
四、建立執行緒
從上面的示例中可以看出,張三show、李四show是在一條主執行緒上執行,所以輸出的結果中是按照一定的順序列印輸出的。這種方法,程式執行的效率較低,如果我們可以讓兩個物件“同時”執行的話,那麼效率會得到極大的提升。
注意:此處說的同時,是依賴於多核處理器執行情況下的同時。我們都知道,單核下所謂的多執行緒是依賴於cpu高速處理下的程式往返切換而造成的“偽多執行緒”“偽同時”在說明執行緒建立方法之前,我們不妨首先思考一下上述的主執行緒是如何建立的?
在上面的示例中,我們並沒有刻意的新增程式碼,但是主執行緒就別建立了,由此,我們可以知道是java虛擬機器預設為我們建立了主執行緒。那麼java虛擬機器又是如何建立的呢?其實是java虛擬機器以來作業系統的一些功能,為我們建立的。說道這裡大家可能有些暈了,下面我們來梳理一下思路(學習嘛,就是先把書本學厚,再學薄的過程)
jvm→→→作業系統→→→建立執行緒所以在java中我們可以利用物件,通過虛擬機器連線底層的作業系統進行執行緒的建立
方法一:繼承Thread類,實現執行緒的建立
1、定義一個類,繼承Thread類
2、覆蓋Thread類中的run方法
示例:
//繼承Thread
class person extends Thread
{
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=“+getName());
}
}
}
public class demo1{
public static void main(String[] args){
person p1=new person("zhangsan");
person p2=new person("lisi”);
//開啟多執行緒並呼叫
p1.start();
p2.start();
}
}
可能會有人比較奇怪,既然繼承Thread類後就已經建立了執行緒。run()又是什麼鬼???Thread類中為什麼會有run()方法呢??請聽我慢慢道來
這就需要我們用到之前的關於程序、執行緒、任務的概念了。
//繼承Thread
class person extends Thread
{ 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=“+getName());
}
}
}
public class demo1{
public static void main(String[] args){
person p1=new person("zhangsan");
person p2=new person("lisi”);
//未開啟多執行緒
p1.run();
p2.run();
}
}
執行結果:
結果是顯而易見的,由於未使用start方法所以並沒有實現多執行緒的效果。這也是建立多執行緒失敗的一個原因。
六、顯示執行緒的名字
當我們建立執行緒成功後,如何直觀的看出正在執行的是哪一個執行緒呢?這就需要我們直接列印輸出執行緒的名字————使用getName()方法
class person extends Thread
{
int i;
private String name;
person(String name)
{
this.name=name;
}
public void run()
{
show();
}
public void show()
{
for(i=0;i<=10;i++)
{
System.out.println(name+"x="+i+"...name="+getName());
}
}
}
public class demo1{
public static void main(String[] args){
person p1=new person("zhangsan");
person p2=new person("lisi”);
//開啟執行緒
p1.start();
p2.start();
}
}
執行結果:
此時,我們會發現列印輸出的結果中有執行緒的名字:Thread-0、Thread-1
下面我們修改一下程式碼,不開啟執行緒,看看會有什麼發生??????
class person extends Thread
{
int i;
private String name;
person(String name)
{
this.name=name;
}
public void run()
{
show();
}
public void show()
{
for(i=0;i<=10;i++)
{
System.out.println(name+"x="+i+"...name="+getName());
}
}
}
public class demo1{
public static void main(String[] args){
person p1=new person("zhangsan");
person p2=new person("lisi”);
//注意此時沒有開啟執行緒,僅呼叫run函式,沒有使用執行緒
p1.run();
p2.run();
}
}
執行結果:
顯而易見:輸出的結果中,仍然有Thread-0、Thread-1(即執行緒的名字)
基於上述情況,讓我們重新認識一下getName()方法吧!!!!
由上述可知,我們沒有啟動多執行緒,但是執行結果卻列印輸出了執行緒的名字。其實,線上程建立時,執行緒就擁有了自己的名字。也就是說此時得到的是物件執行緒的名字,而不是正在執行的執行緒名字 這樣做的好處是,一旦產生就擁有唯一標識自己身份的ID,便於以後的管理和使用,減少錯誤情況的產生。就如同我們出生後,往往會盡快的上戶口,如果在打預防針的時候,你還沒有上戶口,那麼就會出出現等等問題。
那麼如何獲得正在執行的執行緒名字呢?
七、顯示正在執行執行緒的名字
使用Thread類下的currentThread()方法
示例如下
class person extends Thread
{
int i;
private String name;
person(String 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());
}
}
執行結果:
人類的慾望總是無休止的,現在我們已經輸出了正在執行的執行緒名字,但是為了提高閱讀的直觀性,我們能不能將執行緒的名字與物件的名字結合起來呢?比如給一個執行緒取名字叫張三,一個叫李四
如何給執行緒取名字呢?
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());
}
}
執行結果: