java學習日記(線程)

java學習日記(線程)

一、線程的概念:
線程與進程相似,是一段完成某個特定功能的代碼,是程序中單個順序的流控制;但與進程不同的是,同類的多個線程是共享一塊內存空間和一組系統資源,而線程本身的數據通常只有微處理器的寄存器數據,以及一個供程序執行時使用的堆棧。所以系統在產生一個線程,或者在各個線程之間切換時,負擔要比進程小的多,正因如此,線程被稱爲輕負荷進程(light-weight process)。一個進程中可以包含多個線程。

一個線程是一個程序內部的順序控制流。
  1. 進程:每個進程都有獨立的代碼和數據空間(進程上下文) ,進程切換的開銷大。
  2. 線程:輕量的進程,同一類線程共享代碼和數據空間,每個線程有獨立的運行棧和程序計數器(PC),線程切換的開銷小。
  3. 多進程:在操作系統中,能同時運行多個任務程序。
  4. 多線程:在同一應用程序中,有多個順序流同時執行。

Java內在支持多線程,它的所有類都是在多線程下定義的,Java利用多線程使整個系統成爲異步系統。
  1. 虛擬的CPU,封裝在java.lang.Thread類中。
  2. CPU所執行的代碼,傳遞給Thread類。
  3. CPU所處理的數據,傳遞給Thread類。

二、線程的構造
線程實例表示Java解釋器中的真正的線程,通過它可以啓動線程、終止線程、線程掛起等,每個線程都是通過類Thread在Java的軟件包Java.lang中定義,它的構造方法爲:

   public Thread (ThreadGroup group,Runnable target,String name);

  其中,group 指明該線程所屬的線程組;target實際執行線程體的目標對象,它必須實現接口Runnable; name爲線程名。Java中的每個線程都有自己的名稱,Java提供了不同Thread類構造器,允許給線程指定名稱。如果name爲null時,則 Java自動提供唯一的名稱。
當上述構造方法的某個參數爲null時,我們可得到下面的幾個構造方法:

  public Thread ();
  public Thread (Runnable target);
  public Thread (Runnable target,String name);
  public Thread (String name);
  public Thread (ThreadGroup group,Runnable target);
  public Thread (ThreadGroup group,String name);

  一個類聲明實現Runnable接口就可以充當線程體,在接口Runnable中只定義了一個方法 run():
       public void run();

  任何實現接口Runnable的對象都可以作爲一個線程的目標對象,類Thread本身也實現了接口Runnable,因此我們可以通過兩種方法實現線程體。

  (一)定義一個線程類,它繼承線程類Thread並重寫其中的方法 run(),這時在初始化這個類的實例時,目標target可爲null,表示由這個實例對來執行線程體。由於Java只支持單重繼承,用這種方法定義的類不能再繼承其它父類。

  (二)提供一個實現接口Runnable的類作爲一個線程的目標對象,在初始化一個Thread類或者Thread子類的線程對象時,把目標對象傳遞給這個線程實例,由該目標對象提供線程體 run()。這時,實現接口Runnable的類仍然可以繼承其它父類。

三、線程的狀態
  每個線程都是通過某個特定Thread對象的方法run( )來完成其操作的,方法run( )稱爲線程體。下圖表示了java線程的不同狀態以及狀態之間轉換所調用的方法。

1. 創建狀態(new Thread)
  執行下列語句時,線程就處於創建狀態:
  Thread myThread = new Thread( );
  當一個線程處於創建狀態時,它僅僅是一個空的線程對象,系統不爲它分配資源。

  2. 可運行狀態( Runnable )
  Thread myThread = new Thread( );
  myThread.start( );
  當一個線程處於可運行狀態時,系統爲這個線程分配了它需的系統資源,安排其運行並調用線程運行方法,這樣就使得該線程處於可運行( Runnable )狀態。需要注意的是這一狀態並不是運行中狀態(Running ),因爲線程也許實際上並未真正運行。由於很多計算機都是單處理器的,所以要在同一時刻運行所有的處於可運行狀態的線程是不可能的,Java的運行系統必須實現調度來保證這些線程共享處理器。
  
  3. 不可運行狀態(Not Runnable)
  進入不可運行狀態的原因有如下幾條:
  1) 調用了sleep()方法;
  2) 調用了suspend()方法;
  3) 爲等候一個條件變量,線程調用wait()方法;
  4) 輸入輸出流中發生線程阻塞;
  不可運行狀態也稱爲阻塞狀態(Blocked)。因爲某種原因(輸入/輸出、等待消息或其它阻塞情況),系統不能執行線程的狀態。這時即使處理器空閒,也不能執行該線程。

  4. 死亡狀態(Dead)
  線程的終止一般可通過兩種方法實現:自然撤消(線程執行完)或是被停止(調用stop()方法)。目前不推薦通過調用stop()來終止線程的執行,而是讓線程執行完。


四、有關線程的一些長用的方法
1. sleep(long millis)
這個方法是一個靜態的方法,也就是說我們可以直接調用它,如Thread.sleep(5000)就是指讓目前正在運行的線程先停下工作等待5000毫秒。有一點需要注意的是:不能肯定這個線程在過5000毫秒肯定會立刻被執行。

2.interrupt()
這個方法用來打斷一個線程(感覺說睡眠中的進程更加合適)。這個方法的作用可以舉個例子來看一下:
public class TestInterrupt extends Thread
{

/** Creates a new instance of TestInterrupt */
public TestInterrupt()
{
}

public void run()
{
try
{

for ( int i=0; i<5; i++)
{
System.out.println("running the first loop " + i);
}
Thread.sleep(10000);

for ( int i=6; i<10; i++)
{
System.out.println("running the second loop" + i);
}

}catch (InterruptedException ie)
{
System.out.println("Sleep interrupted in run()");
for ( int i=11; i<15; i++)
{
System.out.println("running the third loop" + i);
}

}

}

public static void main(String[] args)
{
TestInterrupt ti = new TestInterrupt();
Thread t = new Thread(ti);
t.start();

//Delay for a few seconds to let the other thread get going
try
{
Thread.sleep(2500);
}catch (InterruptedException ie)
{
System.out.println("Sleep interrupted in main()");
}

System.out.println("About to wake up the other thread");
t.interrupt();
System.out.println("Exiting from Main");

}
}

上面的例子中假如沒有t.interropt()的話,程序運行做的就是
for ( int i=6; i<10; i++)
{
System.out.println("running the second loop" + i);
}
加上以後做的就是catch中的內容了.

3.join()和join(long millis)
join()這個函數的作用是使得目前正在運行的線程假如爲a停下來,一直到調用join()方法的這個線程b被執行完畢,再繼續一開始的線程a;
看個例子好了:
public class TestJoin1 extends Thread
{

/** Creates a new instance of TestJoin1 */
public TestJoin1()
{
}

public void run()
{
try
{

for ( int i=0; i<5; i++)
{
System.out.println("running the first loop " + i);
}
Thread.sleep(1000);

for ( int i=6; i<10; i++)
{
System.out.println("running the second loop" + i);
}

}catch (InterruptedException ie)
{
System.out.println("Sleep interrupted in run()");
}

}

public static void main(String[] args)
{
try
{
TestJoin1 ti = new TestJoin1();
Thread t = new Thread(ti);
t.start();
t.join();

for ( int i=11; i<15; i++)
{
System.out.println("running the third loop" + i);
}
}catch (InterruptedException ie)
{
System.out.println("Join interrupted in run()");
}

System.out.println("Exiting from Main");

}
}
這個程序的結果是先讓t.join()時刻正在運行的線程(其實就是main)被擱置,做完了t這個線程的所有內容,再回到main線程繼續做的 t.join();語句後剩下的內容.假如把t.join()這行去掉的話,在一般的計算機上跑出來的結果應該是先做了main所有的內容再去做t線程的內容.

join(long millis)這個方法和join()方法差不多,都是正在執行的線程a被擱置,去做調用join(long millis)這個方法的線程b的run裏面的內容。但後面的millis這個參數決定了b這個線程能被優先運行多少時間(millis代表多少毫秒),millis豪秒過後b線程即使沒有運行完畢,也會回到線程a.
底下的一個程序能很好的說明這個問題:
public class TestJoin2 extends Thread
{

/** Creates a new instance of TestJoin2 */
public TestJoin2()
{
}

public void run()
{
try
{

for ( int i=0; i<5; i++)
{
System.out.println("running the first loop " + i);
}
Thread.sleep(3500);

for ( int i=6; i<10; i++)
{
System.out.println("running the second loop" + i);
}

}catch (InterruptedException ie)
{
System.out.println("Sleep interrupted in run()");
}

}

public static void main(String[] args)
{
try
{
TestJoin2 t2 = new TestJoin2();
Thread t = new Thread(t2);
t.start();
t.join(3000);

for ( int i=11; i<15; i++)
{
System.out.println("running the third loop" + i);
}
}catch (InterruptedException ie)
{
System.out.println("Join interrupted in run()");
}
System.out.println("Exiting from Main");

}
}

看了這麼多以後,好象很容易產生一種誤解join()這個函數就是讓調用這個方法的線程b優先(第一個)被執行.其實事實並不是這樣的,join()的作用如上面所說的,它只能讓目前運行的線程a擱置等,等b執行完畢再開始執行a.
底下的程序可以讓人消除這中誤解:
public class Test extends Thread
{
public Test(String a)
{
super(a);
}


public void run()
{
System.out.println(this.getName());
}

public static void main(String [] args)
{
Test a = new Test("a");
Test b = new Test("b");
Test c = new Test("c");

a.start();
b.start();
c.start();
try
{
c.join();
}
catch(Exception e){
}
System.out.println("This is Main!");
}
}

看了運行結果是a,b先被執行了,然後纔是c,最後是main^^;

4.關於synchronized
這個關鍵字出現的目的是爲了讓幾個線程能同步,舉一個最簡單的例子。一個電影院有20張票要賣,它有3個售票員。
寫個程序來證實一下不用synchronized的結果好了,需要使用sleep()函數來製造出這種可能(線程的執行時機誰也不能預料)出現的情況:
public class Sell
{
public static void main(String [] args)
{
SellThread sell = new SellThread();
Thread sell1 = new Thread(sell,"sellman1");
Thread sell2 = new Thread(sell,"sellman2");
Thread sell3 = new Thread(sell,"sellman3");
sell1.start();
sell2.start();
sell3.start();
}
}

class SellThread implements Runnable
{
private int i = 20;
public void run()
{
while(true)
{
if( i > 0)
{
try
{
Thread.sleep(100);
} catch(Exception e)
{
}

System.out.println(Thread.currentThread().getName() + " sell " + i--);
}
}
}
}

結果一共賣掉了22張票(估計電影院以爲無緣無故多收了門票錢會很高興,不過一會也許就要面對憤怒的顧客了....)
這個時候我們的synchronized應該發揮作用了^^修改程序如下:
public class Sell2
{
public static void main(String [] args)
{
SellThread sell = new SellThread();
Thread sell1 = new Thread(sell,"sellman1");
Thread sell2 = new Thread(sell,"sellman2");
Thread sell3 = new Thread(sell,"sellman3");
sell1.start();
sell2.start();
sell3.start();
}
}

class SellThread implements Runnable
{
private int i = 20;
String a = "now ok!";
public void run()
{
while(true)
{
synchronized(a)
{
if( i > 0)
{
try
{
Thread.sleep(100);
} catch(Exception e)
{
}

System.out.println(Thread.currentThread().getName() + " sell " + i--);
}
}
}
}
}

這樣就好了只會賣20張票了, synchronized()中的括號中需要的是一個class的對象所以我們不能直接在括號中寫上i,就定義了一個String的對象a,a的標誌旗 (不知道說什麼更合適)本來爲1代表大家都能使用,這樣一個售票員selln的賣票線程拿到了a以後他就可以開始賣票,同時他把a這個對象標誌旗置爲0, 然後其他售票員賣票的線程發現他們拿不到a這個對象了就只先擱置了.一直到selln的賣票線程釋放了a,a的標誌旗就又變成了1,這個時候其他售票員的賣票的線程就可以競爭了,看誰先拿到a這個對象.不過String a和賣票沒什麼關係,所以我們可以用this來代替synchronized()中的a,它和a的效果一樣表示誰拿到了this對象才能執行.


這裏有兩個容易誤解的地方:
(1).一個線程拿到synchronized的括號中的對象之後,其他也要需要拿到這個對象才能運行的線程不能被執行了.其實是其他線程也是可以執行的,但他們執行到了需要synchronized中對象的時候,他們發現對象的標誌旗爲0,所以只能又被擱置了。(看來幸運女神只能同時光顧一個人^^)所以我們用synchronized來使得線程同步的時候是以犧牲效率爲代價的,所以不需要使用的地方就別用好了.

(2),一個線程拿到synchronized的括號中的對象之後,其他任何線程都不能執行了,其實假如其他不需要synchronized的對象才能繼續執行的線程還是可以和拿到synchronized的括號中的對象的線程一起運行的。

有的方法前面被加上了synchronized.其實這個時候就是把這個方法的調用者,也就是this的標誌旗置0了,他不能和其他需要this才能運行的線程一起執行,但可以和其他不需要這個this對象的線程一起運行。

5.wait()和notify()或者notifyAll()
這個幾個函數是爲了使得幾個同步的線程按照一定的先後順序執行。
都是和synchronized()一起使用,設()中對象爲Obj吧。
有一點需要注意的是,我們應該讓需要Obj.wait的線程先啓動。因爲執行順序是需要Obj.wait()的線程a先啓動,然後它運行到 Obj.wait()的時候,進入擱置狀態,讓其他線程先執行。於是帶用Obj.notify()的線程b開始執行了,一直到b執行到了 Obj.notify()以後(Obj.notify()實際上就是通知因爲Obj.wait()被擱置的線程:"輪到你了"),b被擱置,然後繼續做a 的Obj.wait()以後的內容.
所以我們假如讓帶有Obj.notify()的線程b先運行的話,那麼b執行完畢以後,b執行的Obj.notify()沒有找到任何在因 Obj.wait()而進入擱置狀態的線程.然後開始做帶有Obj.wait()的線程a的話.a運行到了Obj.wait()就進入擱置狀態,等待另外一個線程中的Obj.notify()來喚醒它,不過可惜它永遠也等不到了,因爲帶有Obj.notify()的線程已經到擱置的線程中來找過它一次,很可惜的是沒找到.於是線程a就一直擱置了...(感覺有點像愛情劇...);
舉個例子好了:
public class ThreadTest
{
public static void main(String [] args)
{
Storage stor = new Storage();
Counter a = new Counter(stor);
Printer b = new Printer(stor);


a.start();
b.start();
}
}

class Storage
{
public int i = 0;
}

class Counter extends Thread
{
private Storage a;
public Counter(Storage stor)
{
a = stor;
}

public void run()
{
System.out.println("Hi");
try
{
sleep(100);
}
catch(Exception e) {}
int i = 0;
while(i < 5)
{
synchronized(a)
{
System.out.println("Counter");
a.i = (int)(Math.random()*50);
System.out.println(a.i);
a.notify();
}
System.out.println("Counter2");
++i;
}

}
}

class Printer extends Thread
{
private Storage a;
public Printer(Storage stor)
{
a = stor;
}

public void run()
{
int i = 0;
while(i < 5)
{
synchronized(a)
{
System.out.println("Printer");
try{
a.wait();
}
catch(InterruptedException e) {}
System.out.println(a.i);
}
System.out.println("Printer2");
++i;
}
}
}

運行好了以後把
try
{
sleep(100);
}
catch(Exception e) {}
這幾行註釋掉再看看運行結果吧。

還有兩點說下:
(1).假如幾個線程因爲Obj.wait()進入擱置的話,那麼只要一個Obj.notifyAll()執行以後,他們都處於可以運行狀態,不過到底誰先運行我們不就知道。

(2).sleep()和wait()有時候可以執行相同的功能不過要注意的是thread.sleep(long a)過了a毫秒以後,表示可以開始執行了,不代表thread立刻被執行。thread.wait()一但接受到了thread.notify()以後是立刻被執行的.
發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章