1. 程式人生 > >多執行緒詳解(一)

多執行緒詳解(一)

[多執行緒詳解(一)](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());
}
}

執行結果:

這裡寫圖片描述

未完待續,這只是建立執行緒的第一種方法,第二種方法及其他介紹,請閱讀“多執行緒(二)”