java學習筆記_線程

一、Java線程:概念與原理

1、操作系統中線程和進程的概念


現在的操作系統是多任務操作系統。多線程是實現多任務的一種方式。進程是指一個內存中運行的應用程序,每個進程都有自己獨立的一塊內存空間,一個進程中可以啓動多個線程。比如在Windows系統中,一個運行的exe就是一個進程。線程是指進程中的一個執行流程,一個進程中可以運行多個線程。比如java.exe進程中可以運行很多線程。線程總是屬於某個進程,進程中的多個線程共享進程的內存。“同時”執行是人的感覺,在線程之間實際上輪換執行。


2、Java中的線程

在Java中,“線程”指兩件不同的事情:
1、java.lang.Thread類的一個實例;

2、線程的執行。

使用java.lang.Thread類或者java.lang.Runnable接口編寫代碼來定義、實例化和啓動新線程。

一個Thread類實例只是一個對象,像Java中的任何其他對象一樣,具有變量和方法,生死於堆上。

Java中,每個線程都有一個調用棧,即使不在程序中創建任何新的線程,線程也在後臺運行着。

一個Java應用總是從main()方法開始運行,mian()方法運行在一個線程內,它被稱爲主線程。

一旦創建一個新的線程,就產生一個新的調用棧。

線程總體分兩類:用戶線程和守候線程。當所有用戶線程執行完畢的時候,JVM自動關閉。但是守候線程卻不獨立於JVM,守候線程一般是由操作系統或者用戶自己創建的。


二、java線程:創建與啓動

a、定義線程

1、擴展java.lang.Thread類。

此類中有個run()方法,應該注意其用法:

public void run()

如果該線程是使用獨立的 Runnable 運行對象構造的,則調用該 Runnable 對象的 run 方法;否則,該方法不執行任何操作並返回。 

Thread 的子類應該重寫該方法。

2、實現java.lang.Runnable接口。

void run()

使用實現接口 Runnable 的對象創建一個線程時,啓動該線程將導致在獨立執行的線程中調用對象的 run 方法。 

方法 run 的常規協定是,它可能執行任何所需的操作。

b、實例化線程

1、如果是擴展java.lang.Thread類的線程,則直接new即可。

2、如果是實現了java.lang.Runnable接口的類,則用Thread的構造方法:

Thread(Runnable target) 
Thread(Runnable target, String name) 
Thread(ThreadGroup group, Runnable target) 
Thread(ThreadGroup group, Runnable target, String name) 
Thread(ThreadGroup group, Runnable target, String name, long stackSize) 

c、啓動線程

在線程的Thread對象上調用start()方法,而不是run()或者別的方法。

在調用start()方法之前:線程處於新狀態中,新狀態指有一個Thread對象,但還沒有一個真正的線程。

在調用start()方法之後:發生了一系列複雜的事情

啓動新的執行線程(具有新的調用棧);

該線程從新狀態轉移到可運行狀態;

當該線程獲得機會執行時,其目標run()方法將運行。

注意:對Java來說,run()方法沒有任何特別之處。像main()方法一樣,它只是新線程知道調用的方法名稱(和簽名)。因此,在Runnable上或者Thread上調用run方法是合法的。但並不啓動新的線程。

四、例子

1、實現Runnable接口的多線程例子

/** 
實現Runnable接口的類 

* @author leizhimin 2008-9-13 18:12:10 
*/ 
public class DoSomething implements Runnable { 
private String name; 

public DoSomething(String name) { 
this.name = name; 


public void run() { 
for (int i = 0; i < 5; i++) { 
for (long k = 0; k < 100000000; k++) ; 
System.out.println(name + ": " + i); 


}

/** 
測試Runnable類實現的多線程程序 

* @author leizhimin 2008-9-13 18:15:02 
*/ 
public class TestRunnable { 
public static void main(String[] args) { 
DoSomething ds1 = new DoSomething("阿三"); 
DoSomething ds2 = new DoSomething("李四"); 

Thread t1 = new Thread(ds1); 
Thread t2 = new Thread(ds2); 

t1.start(); 
t2.start(); 

}

執行結果:

李四: 0 
阿三: 0 
李四: 1 
阿三: 1 
李四: 2 
李四: 3 
阿三: 2 
李四: 4 
阿三: 3 
阿三: 4 

Process finished with exit code 0

2、擴展Thread類實現的多線程例子

/** 
測試擴展Thread類實現的多線程程序 

* @author leizhimin 2008-9-13 18:22:13 
*/ 
public class TestThread extends Thread{ 
public TestThread(String name) { 
super(name); 


public void run() { 
for(int i = 0;i<5;i++){ 
for(long k= 0; k <100000000;k++); 
System.out.println(this.getName()+" :"+i); 



public static void main(String[] args) { 
Thread t1 = new TestThread("阿三"); 
Thread t2 = new TestThread("李四"); 
t1.start(); 
t2.start(); 

}

執行結果:

阿三 :0 
李四 :0 
阿三 :1 
李四 :1 
阿三 :2 
李四 :2 
阿三 :3 
阿三 :4 
李四 :3 
李四 :4 

Process finished with exit code 0

對於上面的多線程程序代碼來說,輸出的結果是不確定的。其中的一條語句for(long k= 0; k <100000000;k++);是用來模擬一個非常耗時的操作的。

五、一些常見問題

1、線程的名字,一個運行中的線程總是有名字的,名字有兩個來源,一個是虛擬機自己給的名字,一個是你自己的定的名字。在沒有指定線程名字的情況下,虛擬機總會爲線程指定名字,並且主線程的名字總是mian,非主線程的名字不確定。

2、線程都可以設置名字,也可以獲取線程的名字,連主線程也不例外。

3、獲取當前線程的對象的方法是:Thread.currentThread();

4、在上面的代碼中,只能保證:每個線程都將啓動,每個線程都將運行直到完成。一系列線程以某種順序啓動並不意味着將按該順序執行。對於任何一組啓動的線程來說,調度程序不能保證其執行次序,持續時間也無法保證。

5、當線程目標run()方法結束時該線程完成。

6、一旦線程啓動,它就永遠不能再重新啓動。只有一個新的線程可以被啓動,並且只能一次。一個可運行的線程或死線程可以被重新啓動。

7、線程的調度是JVM的一部分,在一個CPU的機器上上,實際上一次只能運行一個線程。一次只有一個線程棧執行。JVM線程調度程序決定實際運行哪個處於可運行狀態的線程。

衆多可運行線程中的某一個會被選中做爲當前線程。可運行線程被選擇運行的順序是沒有保障的。

8、儘管通常採用隊列形式,但這是沒有保障的。隊列形式是指當一個線程完成“一輪”時,它移到可運行隊列的尾部等待,直到它最終排隊到該隊列的前端爲止,它才能被再次選中。事實上,我們把它稱爲可運行池而不是一個可運行隊列,目的是幫助認識線程並不都是以某種有保障的順序排列唱呢個一個隊列的事實。

9、儘管我們沒有無法控制線程調度程序,但可以通過別的方式來影響線程調度的方式。

三、java線程:交互

線程交互是比較複雜的問題,給定一個場景,編寫代碼來恰當使用等待、通知和通知所有線程。

一、線程交互的基礎知識

線程交互知識點需要從java.lang.Object的類的三個方法來學習:

void notify() 
喚醒在此對象監視器上等待的單個線程。 
void notifyAll() 
喚醒在此對象監視器上等待的所有線程。 
void wait() 
導致當前的線程等待,直到其他線程調用此對象的 notify() 方法或 notifyAll() 方法。

當然,wait()還有另外兩個重載方法:

void wait(long timeout) 
導致當前的線程等待,直到其他線程調用此對象的 notify() 方法或 notifyAll() 方法,或者超過指定的時間量。 
void wait(long timeout, int nanos) 
導致當前的線程等待,直到其他線程調用此對象的 notify() 方法或 notifyAll() 方法,或者其他某個線程中斷當前線程,或者已超過某個實際時間量。 

以上這些方法是幫助線程傳遞線程關心的時間狀態。

關於等待/通知,要記住的關鍵點是:

必須從同步環境內調用wait()、notify()、notifyAll()方法。線程不能調用對象上等待或通知的方法,除非它擁有那個對象的鎖。

wait()、notify()、notifyAll()都是Object的實例方法。與每個對象具有鎖一樣,每個對象可以有一個線程列表,他們等待來自該信號(通知)。線程通過執行對象上的wait()方法獲得這個等待列表。從那時候起,它不再執行任何其他指令,直到調用對象的notify()方法爲止。如果多個線程在同一個對象上等待,則將只選擇一個線程(不保證以何種順序)繼續執行。如果沒有線程等待,則不採取任何特殊操作。

下面看個例子就明白了:

/** 
計算輸出其他線程鎖計算的數據 

* @author leizhimin 2008-9-15 13:20:38 
*/ 
public class ThreadA { 
public static void main(String[] args) { 
ThreadB b = new ThreadB(); 
//啓動計算線程 
b.start(); 
//線程A擁有b對象上的鎖。線程爲了調用wait()notify()方法,該線程必須是那個對象鎖的擁有者 
synchronized (b) { 
try { 
System.out.println("等待對象b完成計算。。。"); 
//當前線程A等待 
b.wait(); 
catch (InterruptedException e) { 
e.printStackTrace(); 

System.out.println("b對象計算的總和是:" + b.total); 


}

/** 
計算1+2+3 ... +100的和 

* @author leizhimin 2008-9-15 13:20:49 
*/ 
public class ThreadB extends Thread { 
int total; 

public void run() { 
synchronized (this) { 
for (int i = 0; i < 101; i++) { 
total += i; 

//(完成計算了)喚醒在此對象監視器上等待的單個線程,在本例中線程A被喚醒 
notify(); 


}

等待對象b完成計算。。。 
b對象計算的總和是:5050 

Process finished with exit code 0 

千萬注意:

當在對象上調用wait()方法時,執行該代碼的線程立即放棄它在對象上的鎖。然而調用notify()時,並不意味着這時線程會放棄其鎖。如果線程榮然在完成同步代碼,則線程在移出之前不會放棄鎖。因此,只要調用notify()並不意味着這時該鎖變得可用。

二、多個線程在等待一個對象鎖時候使用notifyAll()

在多數情況下,最好通知等待某個對象的所有線程。如果這樣做,可以在對象上使用notifyAll()讓所有在此對象上等待的線程衝出等待區,返回到可運行狀態。

下面給個例子:

/** 
計算線程 

* @author leizhimin 2008-9-20 11:15:46 
*/ 
public class Calculator extends Thread { 
int total; 

public void run() { 
synchronized (this) { 
for (int i = 0; i < 101; i++) { 
total += i; 


//通知所有在此對象上等待的線程 
notifyAll(); 

}

/** 
獲取計算結果並輸出 

* @author leizhimin 2008-9-20 11:15:22 
*/ 
public class ReaderResult extends Thread { 
Calculator c; 

public ReaderResult(Calculator c) { 
this.c = c; 


public void run() { 
synchronized (c) { 
try { 
System.out.println(Thread.currentThread() + "等待計算結果。。。"); 
c.wait(); 
catch (InterruptedException e) { 
e.printStackTrace(); 

System.out.println(Thread.currentThread() + "計算結果爲:" + c.total); 



public static void main(String[] args) { 
Calculator calculator = new Calculator(); 

//啓動三個線程,分別獲取計算結果 
new ReaderResult(calculator).start(); 
new ReaderResult(calculator).start(); 
new ReaderResult(calculator).start(); 
//啓動計算線程 
calculator.start(); 

}

運行結果:

Thread[Thread-1,5,main]等待計算結果。。。 
Thread[Thread-2,5,main]等待計算結果。。。 
Thread[Thread-3,5,main]等待計算結果。。。 
Exception in thread "Thread-0" java.lang.IllegalMonitorStateException: current thread not owner 
at java.lang.Object.notifyAll(Native Method) 
at threadtest.Calculator.run(Calculator.java:18) 
Thread[Thread-1,5,main]計算結果爲:5050 
Thread[Thread-2,5,main]計算結果爲:5050 
Thread[Thread-3,5,main]計算結果爲:5050 

Process finished with exit code 0 

運行結果表明,程序中有異常,並且多次運行結果可能有多種輸出結果。這就是說明,這個多線程的交互程序還存在問題。究竟是出了什麼問題,需要深入的分析和思考,下面將做具體分析。

實際上,上面這個代碼中,我們期望的是讀取結果的線程在計算線程調用notifyAll()之前等待即可。 但是,如果計算線程先執行,並在讀取結果線程等待之前調用了notify()方法,那麼又會發生什麼呢?這種情況是可能發生的。因爲無法保證線程的不同部分將按照什麼順序來執行。幸運的是當讀取線程運行時,它只能馬上進入等待狀態----它沒有做任何事情來檢查等待的事件是否已經發生。 ----因此,如果計算線程已經調用了notifyAll()方法,那麼它就不會再次調用notifyAll(),----並且等待的讀取線程將永遠保持等待。這當然是開發者所不願意看到的問題。

因此,當等待的事件發生時,需要能夠檢查notifyAll()通知事件是否已經發生。


四、java線程:生產者消費者模型

 

對於多線程程序來說,不管任何編程語言,生產者和消費者模型都是最經典的。就像學習每一門編程語言一樣,Hello World!都是最經典的例子。

實際上,準確說應該是“生產者-消費者-倉儲”模型,離開了倉儲,生產者消費者模型就顯得沒有說服力了。

對於此模型,應該明確一下幾點:

1、生產者僅僅在倉儲未滿時候生產,倉滿則停止生產。

2、消費者僅僅在倉儲有產品時候才能消費,倉空則等待。

3、當消費者發現倉儲沒產品可消費時候會通知生產者生產。

4、生產者在生產出可消費產品時候,應該通知等待的消費者去消費。

此模型將要結合java.lang.Object的wait與notify、notifyAll方法來實現以上的需求。這是非常重要的。

/** 
* Java線程:併發協作-生產者消費者模型 

* @author leizhimin 2009-11-4 14:54:36 
*/ 
public class Test { 
public static void main(String[] args) { 
Godown godown = new Godown(30); 
Consumer c1 = new Consumer(50, godown); 
Consumer c2 = new Consumer(20, godown); 
Consumer c3 = new Consumer(30, godown); 
Producer p1 = new Producer(10, godown); 
Producer p2 = new Producer(10, godown); 
Producer p3 = new Producer(10, godown); 
Producer p4 = new Producer(10, godown); 
Producer p5 = new Producer(10, godown); 
Producer p6 = new Producer(10, godown); 
Producer p7 = new Producer(80, godown); 

c1.start(); 
c2.start(); 
c3.start(); 
p1.start(); 
p2.start(); 
p3.start(); 
p4.start(); 
p5.start(); 
p6.start(); 
p7.start(); 



/** 
倉庫 
*/ 
class Godown { 
public static final int max_size = 100; //最大庫存量 
public int curnum; //當前庫存量 

Godown() { 


Godown(int curnum) { 
this.curnum = curnum; 


/** 
生產指定數量的產品 

* @param neednum 
*/ 
public synchronized void produce(int neednum) { 
//測試是否需要生產 
while (neednum + curnum > max_size) { 
System.out.println("要生產的產品數量" + neednum + "超過剩餘庫存量" + (max_size - curnum) + ",暫時不能執行生產任務!"); 
try { 
//當前的生產線程等待 
wait(); 
catch (InterruptedException e) { 
e.printStackTrace(); 


//滿足生產條件,則進行生產,這裏簡單的更改當前庫存量 
curnum += neednum; 
System.out.println("已經生產了" + neednum + "個產品,現倉儲量爲" + curnum); 
//喚醒在此對象監視器上等待的所有線程 
notifyAll(); 


/** 
消費指定數量的產品 

* @param neednum 
*/ 
public synchronized void consume(int neednum) { 
//測試是否可消費 
while (curnum < neednum) { 
try { 
//當前的生產線程等待 
wait(); 
catch (InterruptedException e) { 
e.printStackTrace(); 


//滿足消費條件,則進行消費,這裏簡單的更改當前庫存量 
curnum -= neednum; 
System.out.println("已經消費了" + neednum + "個產品,現倉儲量爲" + curnum); 
//喚醒在此對象監視器上等待的所有線程 
notifyAll(); 



/** 
生產者 
*/ 
class Producer extends Thread { 
private int neednum; //生產產品的數量 
private Godown godown; //倉庫 

Producer(int neednum, Godown godown) { 
this.neednum = neednum; 
this.godown = godown; 


public void run() { 
//生產指定數量的產品 
godown.produce(neednum); 



/** 
消費者 
*/ 
class Consumer extends Thread { 
private int neednum; //生產產品的數量 
private Godown godown; //倉庫 

Consumer(int neednum, Godown godown) { 
this.neednum = neednum; 
this.godown = godown; 


public void run() { 
//消費指定數量的產品 
godown.consume(neednum); 

}

已經生產了10個產品,現倉儲量爲40 
已經生產了10個產品,現倉儲量爲50 
已經消費了50個產品,現倉儲量爲
已經生產了80個產品,現倉儲量爲80 
已經消費了30個產品,現倉儲量爲50 
已經生產了10個產品,現倉儲量爲60 
已經消費了20個產品,現倉儲量爲40 
已經生產了10個產品,現倉儲量爲50 
已經生產了10個產品,現倉儲量爲60 
已經生產了10個產品,現倉儲量爲70 

Process finished with exit code 0 

說明:

對於本例,要說明的是當發現不能滿足生產或者消費條件的時候,調用對象的wait方法,wait方法的作用是釋放當前線程的所獲得的鎖,並調用對象的notifyAll() 方法,通知(喚醒)該對象上其他等待線程,使得其繼續執行。這樣,整個生產者、消費者線程得以正確的協作執行。

notifyAll() 方法,起到的是一個通知作用,不釋放鎖,也不獲取鎖。只是告訴該對象上等待的線程“可以競爭執行了,都醒來去執行吧”。

本例僅僅是生產者消費者模型中最簡單的一種表示,本例中,如果消費者消費的倉儲量達不到滿足,而又沒有生產者,則程序會一直處於等待狀態,這當然是不對的。實際上可以將此例進行修改,修改爲,根據消費驅動生產,同時生產兼顧倉庫,如果倉不滿就生產,並對每次最大消費量做個限制,這樣就不存在此問題了,當然這樣的例子更復雜,更難以說明這樣一個簡單模型。


五、java線程:死鎖

線程發生死鎖可能性很小,即使看似可能發生死鎖的代碼,在運行時發生死鎖的可能性也是小之又小。

發生死鎖的原因一般是兩個對象的鎖相互等待造成的。

在《Java線程:線程的同步與鎖》一文中,簡述死鎖的概念與簡單例子,但是所給的例子是不完整的,這裏給出一個完整的例子。

/** 
* Java線程:併發協作-死鎖 

* @author Administrator 2009-11-4 22:06:13 
*/ 
public class Test { 
public static void main(String[] args) { 
DeadlockRisk dead = new DeadlockRisk(); 
MyThread t1 = new MyThread(dead, 1, 2); 
MyThread t2 = new MyThread(dead, 3, 4); 
MyThread t3 = new MyThread(dead, 5, 6); 
MyThread t4 = new MyThread(dead, 7, 8); 

t1.start(); 
t2.start(); 
t3.start(); 
t4.start(); 




class MyThread extends Thread { 
private DeadlockRisk dead; 
private int a, b; 


MyThread(DeadlockRisk dead, int a, int b) { 
this.dead = dead; 
this.a = a; 
this.b = b; 


@Override 
public void run() { 
dead.read(); 
dead.write(a, b); 



class DeadlockRisk { 
private static class Resource { 
public int value; 


private Resource resourceA = new Resource(); 
private Resource resourceB = new Resource(); 

public int read() { 
synchronized (resourceA) { 
System.out.println("read():" + Thread.currentThread().getName() + "獲取了resourceA的鎖!"); 
synchronized (resourceB) { 
System.out.println("read():" + Thread.currentThread().getName() + "獲取了resourceB的鎖!"); 
return resourceB.value + resourceA.value; 




public void write(int a, int b) { 
synchronized (resourceB) { 
System.out.println("write():" + Thread.currentThread().getName() + "獲取了resourceA的鎖!"); 
synchronized (resourceA) { 
System.out.println("write():" + Thread.currentThread().getName() + "獲取了resourceB的鎖!"); 
resourceA.value = a; 
resourceB.value = b; 



}

下面死鎖的情況發生了,真是難得一見啊:


六、java線程:調度合併

線程的合併的含義就是將幾個並行線程的線程合併爲一個單線程執行,應用場景是當一個線程必須等待另一個線程執行完畢才能執行時可以使用join方法。

join爲非靜態方法,定義如下:

void join() 
等待該線程終止。 
void join(long millis) 
等待該線程終止的時間最長爲 millis 毫秒。 
void join(long millis, int nanos) 
等待該線程終止的時間最長爲 millis 毫秒 + nanos 納秒。 

/** 
* Java線程:線程的調度-合併 

* @author leizhimin 2009-11-4 9:02:40 
*/ 
public class Test { 
public static void main(String[] args) { 
Thread t1 = new MyThread1(); 
t1.start(); 

for (int i = 0; i < 20; i++) { 
System.out.println("主線程第" + i + "次執行!"); 
if (i > 2) try { 
//t1線程合併到主線程中,主線程停止執行過程,轉而執行t1線程,直到t1執行完畢後繼續。 
t1.join(); 
catch (InterruptedException e) { 
e.printStackTrace(); 





class MyThread1 extends Thread { 
public void run() { 
for (int i = 0; i < 10; i++) { 
System.out.println("線程1" + i + "次執行!"); 


}

主線程第0次執行! 
主線程第1次執行! 
主線程第2次執行! 
線程10次執行! 
主線程第3次執行! 
線程11次執行! 
線程12次執行! 
線程13次執行! 
線程14次執行! 
線程15次執行! 
線程16次執行! 
線程17次執行! 
線程18次執行! 
線程19次執行! 
主線程第4次執行! 
主線程第5次執行! 
主線程第6次執行! 
主線程第7次執行! 
主線程第8次執行! 
主線程第9次執行! 
主線程第10次執行! 
主線程第11次執行! 
主線程第12次執行! 
主線程第13次執行! 
主線程第14次執行! 
主線程第15次執行! 
主線程第16次執行! 
主線程第17次執行! 
主線程第18次執行! 
主線程第19次執行! 

Process finished with exit code 0


七、java線程:調度讓步

線程的讓步含義就是使當前運行着線程讓出CPU資源,但是然給誰不知道,僅僅是讓出,線程狀態回到可運行狀態。

線程的讓步使用Thread.yield()方法,yield() 爲靜態方法,功能是暫停當前正在執行的線程對象,並執行其他線程。

/** 
* Java線程:線程的調度-讓步 

* @author leizhimin 2009-11-4 9:02:40 
*/ 
public class Test { 
public static void main(String[] args) { 
Thread t1 = new MyThread1(); 
Thread t2 = new Thread(new MyRunnable()); 

t2.start(); 
t1.start(); 



class MyThread1 extends Thread { 
public void run() { 
for (int i = 0; i < 10; i++) { 
System.out.println("線程1" + i + "次執行!"); 




class MyRunnable implements Runnable { 
public void run() { 
for (int i = 0; i < 10; i++) { 
System.out.println("線程2" + i + "次執行!"); 
Thread.yield(); 


}

線程20次執行! 
線程21次執行! 
線程22次執行! 
線程23次執行! 
線程10次執行! 
線程11次執行! 
線程12次執行! 
線程13次執行! 
線程14次執行! 
線程15次執行! 
線程16次執行! 
線程17次執行! 
線程18次執行! 
線程19次執行! 
線程24次執行! 
線程25次執行! 
線程26次執行! 
線程27次執行! 
線程28次執行! 
線程29次執行! 

Process finished with exit code 0


八、java線程:調度守護線程

守護線程與普通線程寫法上基本麼啥區別,調用線程對象的方法setDaemon(true),則可以將其設置爲守護線程。

守護線程使用的情況較少,但並非無用,舉例來說,JVM的垃圾回收、內存管理等線程都是守護線程。還有就是在做數據庫應用時候,使用的數據庫連接池,連接池本身也包含着很多後臺線程,監控連接個數、超時時間、狀態等等。

setDaemon方法的詳細說明:

public final void setDaemon(boolean on)將該線程標記爲守護線程或用戶線程。當正在運行的線程都是守護線程時,Java 虛擬機退出。 
該方法必須在啓動線程前調用。 

該方法首先調用該線程的 checkAccess 方法,且不帶任何參數。這可能拋出 SecurityException(在當前線程中)。 


參數: 
on - 如果爲 true,則將該線程標記爲守護線程。 
拋出: 
IllegalThreadStateException - 如果該線程處於活動狀態。 
SecurityException - 如果當前線程無法修改該線程。 
另請參見: 
isDaemon(), checkAccess()

/** 
* Java線程:線程的調度-守護線程 

* @author leizhimin 2009-11-4 9:02:40 
*/ 
public class Test { 
public static void main(String[] args) { 
Thread t1 = new MyCommon(); 
Thread t2 = new Thread(new MyDaemon()); 
t2.setDaemon(true); //設置爲守護線程 

t2.start(); 
t1.start(); 



class MyCommon extends Thread { 
public void run() { 
for (int i = 0; i < 5; i++) { 
System.out.println("線程1" + i + "次執行!"); 
try { 
Thread.sleep(7); 
catch (InterruptedException e) { 
e.printStackTrace(); 





class MyDaemon implements Runnable { 
public void run() { 
for (long i = 0; i < 9999999L; i++) { 
System.out.println("後臺線程第" + i + "次執行!"); 
try { 
Thread.sleep(7); 
catch (InterruptedException e) { 
e.printStackTrace(); 



}

後臺線程第0次執行! 
線程10次執行! 
線程11次執行! 
後臺線程第1次執行! 
後臺線程第2次執行! 
線程12次執行! 
線程13次執行! 
後臺線程第3次執行! 
線程14次執行! 
後臺線程第4次執行! 
後臺線程第5次執行! 
後臺線程第6次執行! 
後臺線程第7次執行! 

Process finished with exit code 0

從上面的執行結果可以看出:

前臺線程是保證執行完畢的,後臺線程還沒有執行完畢就退出了。

實際上:JRE判斷程序是否執行結束的標準是所有的前臺執線程行完畢了,而不管後臺線程的狀態,因此,在使用後臺縣城時候一定要注意這個問題。


九、java線程:調度休眠

Java線程調度是Java多線程的核心,只有良好的調度,才能充分發揮系統的性能,提高程序的執行效率。

這裏要明確的一點,不管程序員怎麼編寫調度,只能最大限度的影響線程執行的次序,而不能做到精準控制。

線程休眠的目的是使線程讓出CPU的最簡單的做法之一,線程休眠時候,會將CPU資源交給其他線程,以便能輪換執行,當休眠一定時間後,線程會甦醒,進入準備狀態等待執行。

線程休眠的方法是Thread.sleep(long millis) 和Thread.sleep(long millis, int nanos) ,均爲靜態方法,那調用sleep休眠的哪個線程呢?簡單說,哪個線程調用sleep,就休眠哪個線程。

/** 
* Java線程:線程的調度-休眠 

* @author leizhimin 2009-11-4 9:02:40 
*/ 
public class Test { 
public static void main(String[] args) { 
Thread t1 = new MyThread1(); 
Thread t2 = new Thread(new MyRunnable()); 
t1.start(); 
t2.start(); 



class MyThread1 extends Thread { 
public void run() { 
for (int i = 0; i < 3; i++) { 
System.out.println("線程1" + i + "次執行!"); 
try { 
Thread.sleep(50); 
catch (InterruptedException e) { 
e.printStackTrace(); 





class MyRunnable implements Runnable { 
public void run() { 
for (int i = 0; i < 3; i++) { 
System.out.println("線程2" + i + "次執行!"); 
try { 
Thread.sleep(50); 
catch (InterruptedException e) { 
e.printStackTrace(); 



}

線程20次執行! 
線程10次執行! 
線程11次執行! 
線程21次執行! 
線程12次執行! 
線程22次執行! 

Process finished with exit code 0

從上面的結果輸出可以看出,無法精準保證線程執行次序。

Java線程:線程的調度-優先級

與線程休眠類似,線程的優先級仍然無法保障線程的執行次序。只不過,優先級高的線程獲取CPU資源的概率較大,優先級低的並非沒機會執行。

線程的優先級用1-10之間的整數表示,數值越大優先級越高,默認的優先級爲5。

在一個線程中開啓另外一個新線程,則新開線程稱爲該線程的子線程,子線程初始優先級與父線程相同。

/** 
* Java線程:線程的調度-優先級 

* @author leizhimin 2009-11-4 9:02:40 
*/ 
public class Test { 
public static void main(String[] args) { 
Thread t1 = new MyThread1(); 
Thread t2 = new Thread(new MyRunnable()); 
t1.setPriority(10); 
t2.setPriority(1); 

t2.start(); 
t1.start(); 



class MyThread1 extends Thread { 
public void run() { 
for (int i = 0; i < 10; i++) { 
System.out.println("線程1" + i + "次執行!"); 
try { 
Thread.sleep(100); 
catch (InterruptedException e) { 
e.printStackTrace(); 





class MyRunnable implements Runnable { 
public void run() { 
for (int i = 0; i < 10; i++) { 
System.out.println("線程2" + i + "次執行!"); 
try { 
Thread.sleep(100); 
catch (InterruptedException e) { 
e.printStackTrace(); 



}

線程10次執行! 
線程20次執行! 
線程21次執行! 
線程11次執行! 
線程22次執行! 
線程12次執行! 
線程13次執行! 
線程23次執行! 
線程24次執行! 
線程14次執行! 
線程15次執行! 
線程25次執行! 
線程16次執行! 
線程26次執行! 
線程17次執行! 
線程27次執行! 
線程18次執行! 
線程28次執行! 
線程19次執行! 
線程29次執行! 

Process finished with exit code 0



十、java線程:同步方法

線程的同步是保證多線程安全訪問競爭資源的一種手段。

線程的同步是Java多線程編程的難點,往往開發者搞不清楚什麼是競爭資源、什麼時候需要考慮同步,怎麼同步等等問題,當然,這些問題沒有很明確的答案,但有些原則問題需要考慮,是否有競爭資源被同時改動的問題?

在本文之前,請參閱《Java線程:線程的同步與鎖》,本文是在此基礎上所寫的。

對於同步,在具體的Java代碼中需要完成一下兩個操作:

把競爭訪問的資源標識爲private;

同步哪些修改變量的代碼,使用synchronized關鍵字同步方法或代碼。

當然這不是唯一控制併發安全的途徑。

synchronized關鍵字使用說明

synchronized只能標記非抽象的方法,不能標識成員變量。

爲了演示同步方法的使用,構建了一個信用卡賬戶,起初信用額爲100w,然後模擬透支、存款等多個操作。顯然銀行賬戶User對象是個競爭資源,而多個併發操作的是賬戶方法oper(int x),當然應該在此方法上加上同步,並將賬戶的餘額設爲私有變量,禁止直接訪問。

/** 
* Java線程:線程的同步 

* @author leizhimin 2009-11-4 11:23:32 
*/ 
public class Test { 
public static void main(String[] args) { 
User u = new User("張三", 100); 
MyThread t1 = new MyThread("線程A", u, 20); 
MyThread t2 = new MyThread("線程B", u, -60); 
MyThread t3 = new MyThread("線程C", u, -80); 
MyThread t4 = new MyThread("線程D", u, -30); 
MyThread t5 = new MyThread("線程E", u, 32); 
MyThread t6 = new MyThread("線程F", u, 21); 

t1.start(); 
t2.start(); 
t3.start(); 
t4.start(); 
t5.start(); 
t6.start(); 



class MyThread extends Thread { 
private User u; 
private int y = 0; 

MyThread(String name, User u, int y) { 
super(name); 
this.u = u; 
this.y = y; 


public void run() { 
u.oper(y); 



class User { 
private String code; 
private int cash; 

User(String code, int cash) { 
this.code = code; 
this.cash = cash; 


public String getCode() { 
return code; 


public void setCode(String code) { 
this.code = code; 


/** 
業務方法 
* @param x 添加x萬元 
*/ 
public synchronized void oper(int x) { 
try { 
Thread.sleep(10L); 
this.cash += x; 
System.out.println(Thread.currentThread().getName() + "運行結束,增加“" + x + "”,當前用戶賬戶餘額爲:" + cash); 
Thread.sleep(10L); 
catch (InterruptedException e) { 
e.printStackTrace(); 



@Override 
public String toString() { 
return "User{" + 
"code='" + code + '\'' + 
", cash=" + cash + 
'}'; 

}

輸出結果:

線程A運行結束,增加“20”,當前用戶賬戶餘額爲:120 
線程F運行結束,增加“21”,當前用戶賬戶餘額爲:141 
線程E運行結束,增加“32”,當前用戶賬戶餘額爲:173 
線程C運行結束,增加“-80”,當前用戶賬戶餘額爲:93 
線程B運行結束,增加“-60”,當前用戶賬戶餘額爲:33 
線程D運行結束,增加“-30”,當前用戶賬戶餘額爲:

Process finished with exit code 0

反面教材,不同步的情況,也就是去掉oper(int x)方法的synchronized修飾符,然後運行程序,結果如下:

線程A運行結束,增加“20”,當前用戶賬戶餘額爲:61 
線程D運行結束,增加“-30”,當前用戶賬戶餘額爲:63 
線程B運行結束,增加“-60”,當前用戶賬戶餘額爲:
線程F運行結束,增加“21”,當前用戶賬戶餘額爲:61 
線程E運行結束,增加“32”,當前用戶賬戶餘額爲:93 
線程C運行結束,增加“-80”,當前用戶賬戶餘額爲:61 

Process finished with exit code 0

很顯然,上面的結果是錯誤的,導致錯誤的原因是多個線程併發訪問了競爭資源u,並對u的屬性做了改動。

可見同步的重要性。

注意:

通過前文可知,線程退出同步方法時將釋放掉方法所屬對象的鎖,但還應該注意的是,同步方法中還可以使用特定的方法對線程進行調度。這些方法來自於java.lang.Object類。

void notify() 
喚醒在此對象監視器上等待的單個線程。 
void notifyAll() 
喚醒在此對象監視器上等待的所有線程。 
void wait() 
導致當前的線程等待,直到其他線程調用此對象的 notify() 方法或 notifyAll() 方法。 
void wait(long timeout) 
導致當前的線程等待,直到其他線程調用此對象的 notify() 方法或 notifyAll() 方法,或者超過指定的時間量。 
void wait(long timeout, int nanos) 
導致當前的線程等待,直到其他線程調用此對象的 notify() 方法或 notifyAll() 方法,或者其他某個線程中斷當前線程,或者已超過某個實際時間量。 

結合以上方法,處理多線程同步與互斥問題非常重要,著名的生產者-消費者例子就是一個經典的例子,任何語言多線程必學的例子。


十一、java線程:同步塊

對於同步,除了同步方法外,還可以使用同步代碼塊,有時候同步代碼塊會帶來比同步方法更好的效果。

追其同步的根本的目的,是控制競爭資源的正確的訪問,因此只要在訪問競爭資源的時候保證同一時刻只能一個線程訪問即可,因此Java引入了同步代碼快的策略,以提高性能。

在上個例子的基礎上,對oper方法做了改動,由同步方法改爲同步代碼塊模式,程序的執行邏輯並沒有問題。

/** 
* Java線程:線程的同步-同步代碼塊 

* @author leizhimin 2009-11-4 11:23:32 
*/ 
public class Test { 
public static void main(String[] args) { 
User u = new User("張三", 100); 
MyThread t1 = new MyThread("線程A", u, 20); 
MyThread t2 = new MyThread("線程B", u, -60); 
MyThread t3 = new MyThread("線程C", u, -80); 
MyThread t4 = new MyThread("線程D", u, -30); 
MyThread t5 = new MyThread("線程E", u, 32); 
MyThread t6 = new MyThread("線程F", u, 21); 

t1.start(); 
t2.start(); 
t3.start(); 
t4.start(); 
t5.start(); 
t6.start(); 



class MyThread extends Thread { 
private User u; 
private int y = 0; 

MyThread(String name, User u, int y) { 
super(name); 
this.u = u; 
this.y = y; 


public void run() { 
u.oper(y); 



class User { 
private String code; 
private int cash; 

User(String code, int cash) { 
this.code = code; 
this.cash = cash; 


public String getCode() { 
return code; 


public void setCode(String code) { 
this.code = code; 


/** 
業務方法 

* @param x 添加x萬元 
*/ 
public void oper(int x) { 
try { 
Thread.sleep(10L); 
synchronized (this) { 
this.cash += x; 
System.out.println(Thread.currentThread().getName() + "運行結束,增加“" + x + "”,當前用戶賬戶餘額爲:" + cash); 

Thread.sleep(10L); 
catch (InterruptedException e) { 
e.printStackTrace(); 



@Override 
public String toString() { 
return "User{" + 
"code='" + code + '\'' + 
", cash=" + cash + 
'}'; 

}

線程E運行結束,增加“32”,當前用戶賬戶餘額爲:132 
線程B運行結束,增加“-60”,當前用戶賬戶餘額爲:72 
線程D運行結束,增加“-30”,當前用戶賬戶餘額爲:42 
線程F運行結束,增加“21”,當前用戶賬戶餘額爲:63 
線程C運行結束,增加“-80”,當前用戶賬戶餘額爲:-17 
線程A運行結束,增加“20”,當前用戶賬戶餘額爲:

Process finished with exit code 0

注意:

在使用synchronized關鍵字時候,應該儘可能避免在synchronized方法或synchronized塊中使用sleep或者yield方法,因爲synchronized程序塊佔有着對象鎖,你休息那麼其他的線程只能一邊等着你醒來執行完了才能執行。不但嚴重影響效率,也不合邏輯。

同樣,在同步程序塊內調用yeild方法讓出CPU資源也沒有意義,因爲你佔用着鎖,其他互斥線程還是無法訪問同步程序塊。當然與同步程序塊無關的線程可以獲得更多的執行時間。


十二、java線程:同步與鎖

a、同步問題提出

線程的同步是爲了防止多個線程訪問一個數據對象時,對數據造成的破壞。

例如:兩個線程ThreadA、ThreadB都操作同一個對象Foo對象,並修改Foo對象上的數據。

public class Foo { 
private int x = 100; 

public int getX() { 
return x; 


public int fix(int y) { 
x = x - y; 
return x; 

}

public class MyRunnable implements Runnable { 
private Foo foo = new Foo(); 

public static void main(String[] args) { 
MyRunnable r = new MyRunnable(); 
Thread ta = new Thread(r, "Thread-A"); 
Thread tb = new Thread(r, "Thread-B"); 
ta.start(); 
tb.start(); 


public void run() { 
for (int i = 0; i < 3; i++) { 
this.fix(30); 
try { 
Thread.sleep(1); 
catch (InterruptedException e) { 
e.printStackTrace(); 

System.out.println(Thread.currentThread().getName() + " : 當前foo對象的x= " + foo.getX()); 



public int fix(int y) { 
return foo.fix(y); 

}

運行結果:

Thread-A : 當前foo對象的x= 40 
Thread-B : 當前foo對象的x= 40 
Thread-B : 當前foo對象的x= -20 
Thread-A : 當前foo對象的x= -50 
Thread-A : 當前foo對象的x= -80 
Thread-B : 當前foo對象的x= -80 

Process finished with exit code 0

從結果發現,這樣的輸出值明顯是不合理的。原因是兩個線程不加控制的訪問Foo對象並修改其數據所致。

如果要保持結果的合理性,只需要達到一個目的,就是將對Foo的訪問加以限制,每次只能有一個線程在訪問。這樣就能保證Foo對象中數據的合理性了。

在具體的Java代碼中需要完成一下兩個操作:

把競爭訪問的資源類Foo變量x標識爲private;

同步哪些修改變量的代碼,使用synchronized關鍵字同步方法或代碼。

b、同步和鎖定

1、鎖的原理

Java中每個對象都有一個內置鎖

當程序運行到非靜態的synchronized同步方法上時,自動獲得與正在執行代碼類的當前實例(this實例)有關的鎖。獲得一個對象的鎖也稱爲獲取鎖、鎖定對象、在對象上鎖定或在對象上同步。

當程序運行到synchronized同步方法或代碼塊時才該對象鎖才起作用。

一個對象只有一個鎖。所以,如果一個線程獲得該鎖,就沒有其他線程可以獲得鎖,直到第一個線程釋放(或返回)鎖。這也意味着任何其他線程都不能進入該對象上的synchronized方法或代碼塊,直到該鎖被釋放。

釋放鎖是指持鎖線程退出了synchronized同步方法或代碼塊。

關於鎖和同步,有一下幾個要點:

1)、只能同步方法,而不能同步變量和類;

2)、每個對象只有一個鎖;當提到同步時,應該清楚在什麼上同步?也就是說,在哪個對象上同步?

3)、不必同步類中所有的方法,類可以同時擁有同步和非同步方法。

4)、如果兩個線程要執行一個類中的synchronized方法,並且兩個線程使用相同的實例來調用方法,那麼一次只能有一個線程能夠執行方法,另一個需要等待,直到鎖被釋放。也就是說:如果一個線程在對象上獲得一個鎖,就沒有任何其他線程可以進入(該對象的)類中的任何一個同步方法。

5)、如果線程擁有同步和非同步方法,則非同步方法可以被多個線程自由訪問而不受鎖的限制。

6)、線程睡眠時,它所持的任何鎖都不會釋放。 

7)、線程可以獲得多個鎖。比如,在一個對象的同步方法裏面調用另外一個對象的同步方法,則獲取了兩個對象的同步鎖。

8)、同步損害併發性,應該儘可能縮小同步範圍。同步不但可以同步整個方法,還可以同步方法中一部分代碼塊。

9)、在使用同步代碼塊時候,應該指定在哪個對象上同步,也就是說要獲取哪個對象的鎖。例如:

public int fix(int y) {
synchronized (this) {
x = x - y;
}
return x;
}

當然,同步方法也可以改寫爲非同步方法,但功能完全一樣的,例如:

public synchronized int getX() {
return x++;
}

public int getX() {
synchronized (this) {
return x;
}
}

效果是完全一樣的。

c、靜態方法同步

要同步靜態方法,需要一個用於整個類對象的鎖,這個對象是就是這個類(XXX.class)。

例如:

public static synchronized int setName(String name){

Xxx.name = name;

}

等價於
public static int setName(String name){
synchronized(Xxx.class){
Xxx.name = name;
}
}

d、如果線程不能獲得鎖會怎麼樣

如果線程試圖進入同步方法,而其鎖已經被佔用,則線程在該對象上被阻塞。實質上,線程進入該對象的的一種池中,必須在哪裏等待,直到其鎖被釋放,該線程再次變爲可運行或運行爲止。

當考慮阻塞時,一定要注意哪個對象正被用於鎖定:

1、調用同一個對象中非靜態同步方法的線程將彼此阻塞。如果是不同對象,則每個線程有自己的對象的鎖,線程間彼此互不干預。

2、調用同一個類中的靜態同步方法的線程將彼此阻塞,它們都是鎖定在相同的Class對象上。

3、靜態同步方法和非靜態同步方法將永遠不會彼此阻塞,因爲靜態方法鎖定在Class對象上,非靜態方法鎖定在該類的對象上。

4、對於同步代碼塊,要看清楚什麼對象已經用於鎖定(synchronized後面括號的內容)。在同一個對象上進行同步的線程將彼此阻塞,在不同對象上鎖定的線程將永遠不會彼此阻塞。

e、何時需要同步

在多個線程同時訪問互斥(可交換)數據時,應該同步以保護數據,確保兩個線程不會同時修改更改它。

對於非靜態字段中可更改的數據,通常使用非靜態方法訪問。

對於靜態字段中可更改的數據,通常使用靜態方法訪問。

如果需要在非靜態方法中使用靜態字段,或者在靜態字段中調用非靜態方法,問題將變得非常複雜。已經超出SJCP考試範圍了。

f、線程安全類

當一個類已經很好的同步以保護它的數據時,這個類就稱爲“線程安全的”。

即使是線程安全類,也應該特別小心,因爲操作的線程是間仍然不一定安全。

舉個形象的例子,比如一個集合是線程安全的,有兩個線程在操作同一個集合對象,當第一個線程查詢集合非空後,刪除集合中所有元素的時候。第二個線程也來執行與第一個線程相同的操作,也許在第一個線程查詢後,第二個線程也查詢出集合非空,但是當第一個執行清除後,第二個再執行刪除顯然是不對的,因爲此時集合已經爲空了。

看個代碼:

public class NameList { 
private List nameList = Collections.synchronizedList(new LinkedList()); 

public void add(String name) { 
nameList.add(name); 


public String removeFirst() { 
if (nameList.size() > 0) { 
return (String) nameList.remove(0); 
else { 
return null


}

public class Test { 
public static void main(String[] args) { 
final NameList nl = new NameList(); 
nl.add("aaa"); 
class NameDropper extends Thread{ 
public void run(){ 
String name = nl.removeFirst(); 
System.out.println(name); 



Thread t1 = new NameDropper(); 
Thread t2 = new NameDropper(); 
t1.start(); 
t2.start(); 

}

雖然集合對象

private List nameList = Collections.synchronizedList(new LinkedList());
是同步的,但是程序還不是線程安全的。

出現這種事件的原因是,上例中一個線程操作列表過程中無法阻止另外一個線程對列表的其他操作。

解決上面問題的辦法是,在操作集合對象的NameList上面做一個同步。改寫後的代碼如下:

public class NameList { 
private List nameList = Collections.synchronizedList(new LinkedList()); 

public synchronized void add(String name) { 
nameList.add(name); 


public synchronized String removeFirst() { 
if (nameList.size() > 0) { 
return (String) nameList.remove(0); 
else { 
return null


}

這樣,當一個線程訪問其中一個同步方法時,其他線程只有等待。

g、線程死鎖

死鎖對Java程序來說,是很複雜的,也很難發現問題。當兩個線程被阻塞,每個線程在等待另一個線程時就發生死鎖。

還是看一個比較直觀的死鎖例子:

public class DeadlockRisk { 
private static class Resource { 
public int value; 


private Resource resourceA = new Resource(); 
private Resource resourceB = new Resource(); 

public int read() { 
synchronized (resourceA) { 
synchronized (resourceB) { 
return resourceB.value + resourceA.value; 




public void write(int a, int b) { 
synchronized (resourceB) { 
synchronized (resourceA) { 
resourceA.value = a; 
resourceB.value = b; 



}

假設read()方法由一個線程啓動,write()方法由另外一個線程啓動。讀線程將擁有resourceA鎖,寫線程將擁有resourceB鎖,兩者都堅持等待的話就出現死鎖。

實際上,上面這個例子發生死鎖的概率很小。因爲在代碼內的某個點,CPU必須從讀線程切換到寫線程,所以,死鎖基本上不能發生。

但是,無論代碼中發生死鎖的概率有多小,一旦發生死鎖,程序就死掉。有一些設計方法能幫助避免死鎖,包括始終按照預定義的順序獲取鎖這一策略。已經超出SCJP的考試範圍。

h、線程同步小結

1、線程同步的目的是爲了保護多個線程反問一個資源時對資源的破壞。

2、線程同步方法是通過鎖來實現,每個對象都有切僅有一個鎖,這個鎖與一個特定的對象關聯,線程一旦獲取了對象鎖,其他訪問該對象的線程就無法再訪問該對象的其他同步方法。

3、對於靜態同步方法,鎖是針對這個類的,鎖對象是該類的Class對象。靜態和非靜態方法的鎖互不干預。一個線程獲得鎖,當在一個同步方法中訪問另外對象上的同步方法時,會獲取這兩個對象鎖。

4、對於同步,要時刻清醒在哪個對象上同步,這是關鍵。

5、編寫線程安全的類,需要時刻注意對多個線程競爭訪問資源的邏輯和安全做出正確的判斷,對“原子”操作做出分析,並保證原子操作期間別的線程無法訪問競爭資源。

6、當多個線程等待一個對象鎖時,沒有獲取到鎖的線程將發生阻塞。

7、死鎖是線程間相互等待鎖鎖造成的,在實際中發生的概率非常的小。真讓你寫個死鎖程序,不一定好使,呵呵。但是,一旦程序發生死鎖,程序將死掉。


十三、java線程:新特徵-鎖

在Java5中,專門提供了鎖對象,利用鎖可以方便的實現資源的封鎖,用來控制對競爭資源併發訪問的控制,這些內容主要集中在java.util.concurrent.locks 包下面,裏面有三個重要的接口Condition、Lock、ReadWriteLock。

Condition

Condition 將 Object 監視器方法(waitnotify 和 notifyAll)分解成截然不同的對象,以便通過將這些對象與任意 Lock 實現組合使用,爲每個對象提供多個等待 set (wait-set)。

Lock

Lock 實現提供了比使用 synchronized 方法和語句可獲得的更廣泛的鎖定操作。

ReadWriteLock

ReadWriteLock 維護了一對相關的鎖定,一個用於只讀操作,另一個用於寫入操作。

有關鎖的介紹,API文檔解說很多,看得很煩,還是看個例子再看文檔比較容易理解。

import java.util.concurrent.ExecutorService; 
import java.util.concurrent.Executors; 
import java.util.concurrent.locks.Lock; 
import java.util.concurrent.locks.ReentrantLock; 

/** 
* Java線程:鎖 

* @author leizhimin 2009-11-5 10:57:29 
*/ 
public class Test { 
public static void main(String[] args) { 
//創建併發訪問的賬戶 
MyCount myCount = new MyCount("95599200901215522", 10000); 
//創建一個鎖對象 
Lock lock = new ReentrantLock(); 
//創建一個線程池 
ExecutorService pool = Executors.newCachedThreadPool(); 
//創建一些併發訪問用戶,一個信用卡,存的存,取的取,好熱鬧啊 
User u1 = new User("張三", myCount, -4000, lock); 
User u2 = new User("張三他爹", myCount, 6000, lock); 
User u3 = new User("張三他弟", myCount, -8000, lock); 
User u4 = new User("張三", myCount, 800, lock); 
//在線程池中執行各個用戶的操作 
pool.execute(u1); 
pool.execute(u2); 
pool.execute(u3); 
pool.execute(u4); 
//關閉線程池 
pool.shutdown(); 



/** 
信用卡的用戶 
*/ 
class User implements Runnable { 
private String name; //用戶名 
private MyCount myCount; //所要操作的賬戶 
private int iocash; //操作的金額,當然有正負之分了 
private Lock myLock; //執行操作所需的鎖對象 

User(String name, MyCount myCount, int iocash, Lock myLock) { 
this.name = name; 
this.myCount = myCount; 
this.iocash = iocash; 
this.myLock = myLock; 


public void run() { 
//獲取鎖 
myLock.lock(); 
//執行現金業務 
System.out.println(name + "正在操作" + myCount + "賬戶,金額爲" + iocash + ",當前金額爲" + myCount.getCash()); 
myCount.setCash(myCount.getCash() + iocash); 
System.out.println(name + "操作" + myCount + "賬戶成功,金額爲" + iocash + ",當前金額爲" + myCount.getCash()); 
//釋放鎖,否則別的線程沒有機會執行了 
myLock.unlock(); 



/** 
信用卡賬戶,可隨意透支 
*/ 
class MyCount { 
private String oid; //賬號 
private int cash; //賬戶餘額 

MyCount(String oid, int cash) { 
this.oid = oid; 
this.cash = cash; 


public String getOid() { 
return oid; 


public void setOid(String oid) { 
this.oid = oid; 


public int getCash() { 
return cash; 


public void setCash(int cash) { 
this.cash = cash; 


@Override 
public String toString() { 
return "MyCount{" + 
"oid='" + oid + '\'' + 
", cash=" + cash + 
'}'; 

}

張三正在操作MyCount{oid='95599200901215522', cash=10000}賬戶,金額爲-4000,當前金額爲10000 
張三操作MyCount{oid='95599200901215522', cash=6000}賬戶成功,金額爲-4000,當前金額爲6000 
張三他爹正在操作MyCount{oid='95599200901215522', cash=6000}賬戶,金額爲6000,當前金額爲6000 
張三他爹操作MyCount{oid='95599200901215522', cash=12000}賬戶成功,金額爲6000,當前金額爲12000 
張三他弟正在操作MyCount{oid='95599200901215522', cash=12000}賬戶,金額爲-8000,當前金額爲12000 
張三他弟操作MyCount{oid='95599200901215522', cash=4000}賬戶成功,金額爲-8000,當前金額爲4000 
張三正在操作MyCount{oid='95599200901215522', cash=4000}賬戶,金額爲800,當前金額爲4000 
張三操作MyCount{oid='95599200901215522', cash=4800}賬戶成功,金額爲800,當前金額爲4800 

Process finished with exit code 0

從上面的輸出可以看到,利用鎖對象太方便了,比直接在某個不知情的對象上用鎖清晰多了。

但一定要注意的是,在獲取了鎖對象後,用完後應該儘快釋放鎖,以便別的等待該鎖的線程有機會去執行。

Java線程:新特徵-鎖(下) 

在上文中提到了Lock接口以及對象,使用它,很優雅的控制了競爭資源的安全訪問,但是這種鎖不區分讀寫,稱這種鎖爲普通鎖。爲了提高性能,Java提供了讀寫鎖,在讀的地方使用讀鎖,在寫的地方使用寫鎖,靈活控制,在一定程度上提高了程序的執行效率。

Java中讀寫鎖有個接口java.util.concurrent.locks.ReadWriteLock,也有具體的實現ReentrantReadWriteLock,詳細的API可以查看JavaAPI文檔。

下面這個例子是在文例子的基礎上,將普通鎖改爲讀寫鎖,並添加賬戶餘額查詢的功能,代碼如下:

import java.util.concurrent.ExecutorService; 
import java.util.concurrent.Executors; 
import java.util.concurrent.locks.ReadWriteLock; 
import java.util.concurrent.locks.ReentrantReadWriteLock; 

/** 
* Java線程:鎖 

* @author leizhimin 2009-11-5 10:57:29 
*/ 
public class Test { 
public static void main(String[] args) { 
//創建併發訪問的賬戶 
MyCount myCount = new MyCount("95599200901215522", 10000); 
//創建一個鎖對象 
ReadWriteLock lock = new ReentrantReadWriteLock(false); 
//創建一個線程池 
ExecutorService pool = Executors.newFixedThreadPool(2); 
//創建一些併發訪問用戶,一個信用卡,存的存,取的取,好熱鬧啊 
User u1 = new User("張三", myCount, -4000, lock, false); 
User u2 = new User("張三他爹", myCount, 6000, lock, false); 
User u3 = new User("張三他弟", myCount, -8000, lock, false); 
User u4 = new User("張三", myCount, 800, lock, false); 
User u5 = new User("張三他爹", myCount, 0, lock, true); 
//在線程池中執行各個用戶的操作 
pool.execute(u1); 
pool.execute(u2); 
pool.execute(u3); 
pool.execute(u4); 
pool.execute(u5); 
//關閉線程池 
pool.shutdown(); 



/** 
信用卡的用戶 
*/ 
class User implements Runnable { 
private String name; //用戶名 
private MyCount myCount; //所要操作的賬戶 
private int iocash; //操作的金額,當然有正負之分了 
private ReadWriteLock myLock; //執行操作所需的鎖對象 
private boolean ischeck; //是否查詢 

User(String name, MyCount myCount, int iocash, ReadWriteLock myLock, boolean ischeck) { 
this.name = name; 
this.myCount = myCount; 
this.iocash = iocash; 
this.myLock = myLock; 
this.ischeck = ischeck; 


public void run() { 
if (ischeck) { 
//獲取讀鎖 
myLock.readLock().lock(); 
System.out.println("讀:" + name + "正在查詢" + myCount + "賬戶,當前金額爲" + myCount.getCash()); 
//釋放讀鎖 
myLock.readLock().unlock(); 
else { 
//獲取寫鎖 
myLock.writeLock().lock(); 
//執行現金業務 
System.out.println("寫:" + name + "正在操作" + myCount + "賬戶,金額爲" + iocash + ",當前金額爲" + myCount.getCash()); 
myCount.setCash(myCount.getCash() + iocash); 
System.out.println("寫:" + name + "操作" + myCount + "賬戶成功,金額爲" + iocash + ",當前金額爲" + myCount.getCash()); 
//釋放寫鎖 
myLock.writeLock().unlock(); 




/** 
信用卡賬戶,可隨意透支 
*/ 
class MyCount { 
private String oid; //賬號 
private int cash; //賬戶餘額 

MyCount(String oid, int cash) { 
this.oid = oid; 
this.cash = cash; 


public String getOid() { 
return oid; 


public void setOid(String oid) { 
this.oid = oid; 


public int getCash() { 
return cash; 


public void setCash(int cash) { 
this.cash = cash; 


@Override 
public String toString() { 
return "MyCount{" + 
"oid='" + oid + '\'' + 
", cash=" + cash + 
'}'; 

}

寫:張三正在操作MyCount{oid='95599200901215522', cash=10000}賬戶,金額爲-4000,當前金額爲10000 
寫:張三操作MyCount{oid='95599200901215522', cash=6000}賬戶成功,金額爲-4000,當前金額爲6000 
寫:張三他弟正在操作MyCount{oid='95599200901215522', cash=6000}賬戶,金額爲-8000,當前金額爲6000 
寫:張三他弟操作MyCount{oid='95599200901215522', cash=-2000}賬戶成功,金額爲-8000,當前金額爲-2000 
寫:張三正在操作MyCount{oid='95599200901215522', cash=-2000}賬戶,金額爲800,當前金額爲-2000 
寫:張三操作MyCount{oid='95599200901215522', cash=-1200}賬戶成功,金額爲800,當前金額爲-1200 
讀:張三他爹正在查詢MyCount{oid='95599200901215522', cash=-1200}賬戶,當前金額爲-1200 
寫:張三他爹正在操作MyCount{oid='95599200901215522', cash=-1200}賬戶,金額爲6000,當前金額爲-1200 
寫:張三他爹操作MyCount{oid='95599200901215522', cash=4800}賬戶成功,金額爲6000,當前金額爲4800 

Process finished with exit code 0

在實際開發中,最好在能用讀寫鎖的情況下使用讀寫鎖,而不要用普通鎖,以求更好的性能。



十四、java線程:新特徵-條件變量

條件變量是Java5線程中很重要的一個概念,顧名思義,條件變量就是表示條件的一種變量。但是必須說明,這裏的條件是沒有實際含義的,僅僅是個標記而已,並且條件的含義往往通過代碼來賦予其含義。

這裏的條件和普通意義上的條件表達式有着天壤之別。

條件變量都實現了java.util.concurrent.locks.Condition接口,條件變量的實例化是通過一個Lock對象上調用newCondition()方法來獲取的,這樣,條件就和一個鎖對象綁定起來了。因此,Java中的條件變量只能和鎖配合使用,來控制併發程序訪問競爭資源的安全。

條件變量的出現是爲了更精細控制線程等待與喚醒,在Java5之前,線程的等待與喚醒依靠的是Object對象的wait()和notify()/notifyAll()方法,這樣的處理不夠精細。

而在Java5中,一個鎖可以有多個條件,每個條件上可以有多個線程等待,通過調用await()方法,可以讓線程在該條件下等待。當調用signalAll()方法,又可以喚醒該條件下的等待的線程。有關Condition接口的API可以具體參考JavaAPI文檔。

條件變量比較抽象,原因是他不是自然語言中的條件概念,而是程序控制的一種手段。

下面以一個銀行存取款的模擬程序爲例來揭蓋Java多線程條件變量的神祕面紗:

有一個賬戶,多個用戶(線程)在同時操作這個賬戶,有的存款有的取款,存款隨便存,取款有限制,不能透支,任何試圖透支的操作都將等待裏面有足夠存款才執行操作。

import java.util.concurrent.ExecutorService; 
import java.util.concurrent.Executors; 
import java.util.concurrent.locks.Condition; 
import java.util.concurrent.locks.Lock; 
import java.util.concurrent.locks.ReentrantLock; 

/** 
* Java線程:條件變量 

* @author leizhimin 2009-11-5 10:57:29 
*/ 
public class Test { 
public static void main(String[] args) { 
//創建併發訪問的賬戶 
MyCount myCount = new MyCount("95599200901215522", 10000); 
//創建一個線程池 
ExecutorService pool = Executors.newFixedThreadPool(2); 
Thread t1 = new SaveThread("張三", myCount, 2000); 
Thread t2 = new SaveThread("李四", myCount, 3600); 
Thread t3 = new DrawThread("王五", myCount, 2700); 
Thread t4 = new SaveThread("老張", myCount, 600); 
Thread t5 = new DrawThread("老牛", myCount, 1300); 
Thread t6 = new DrawThread("胖子", myCount, 800); 
//執行各個線程 
pool.execute(t1); 
pool.execute(t2); 
pool.execute(t3); 
pool.execute(t4); 
pool.execute(t5); 
pool.execute(t6); 
//關閉線程池 
pool.shutdown(); 



/** 
存款線程類 
*/ 
class SaveThread extends Thread { 
private String name; //操作人 
private MyCount myCount; //賬戶 
private int x; //存款金額 

SaveThread(String name, MyCount myCount, int x) { 
this.name = name; 
this.myCount = myCount; 
this.x = x; 


public void run() { 
myCount.saving(x, name); 



/** 
取款線程類 
*/ 
class DrawThread extends Thread { 
private String name; //操作人 
private MyCount myCount; //賬戶 
private int x; //存款金額 

DrawThread(String name, MyCount myCount, int x) { 
this.name = name; 
this.myCount = myCount; 
this.x = x; 


public void run() { 
myCount.drawing(x, name); 




/** 
普通銀行賬戶,不可透支 
*/ 
class MyCount { 
private String oid; //賬號 
private int cash; //賬戶餘額 
private Lock lock = new ReentrantLock(); //賬戶鎖 
private Condition _save = lock.newCondition(); //存款條件 
private Condition _draw = lock.newCondition(); //取款條件 

MyCount(String oid, int cash) { 
this.oid = oid; 
this.cash = cash; 


/** 
存款 

* @param x 操作金額 
* @param name 操作人 
*/ 
public void saving(int x, String name) { 
lock.lock(); //獲取鎖 
if (x > 0) { 
cash += x; //存款 
System.out.println(name + "存款" + x + ",當前餘額爲" + cash); 

_draw.signalAll(); //喚醒所有等待線程。 
lock.unlock(); //釋放鎖 


/** 
取款 

* @param x 操作金額 
* @param name 操作人 
*/ 
public void drawing(int x, String name) { 
lock.lock(); //獲取鎖 
try { 
if (cash - x < 0) { 
_draw.await(); //阻塞取款操作 
else { 
cash -= x; //取款 
System.out.println(name + "取款" + x + ",當前餘額爲" + cash); 

_save.signalAll(); //喚醒所有存款操作 
catch (InterruptedException e) { 
e.printStackTrace(); 
finally { 
lock.unlock(); //釋放鎖 


}

李四存款3600,當前餘額爲13600 
張三存款2000,當前餘額爲15600 
老張存款600,當前餘額爲16200 
老牛取款1300,當前餘額爲14900 
胖子取款800,當前餘額爲14100 
王五取款2700,當前餘額爲11400 

Process finished with exit code 0

假如我們不用鎖和條件變量,如何實現此功能呢?下面是實現代碼:

import java.util.concurrent.ExecutorService; 
import java.util.concurrent.Executors; 

/** 
* Java線程:不用條件變量 

* @author leizhimin 2009-11-5 10:57:29 
*/ 
public class Test { 
public static void main(String[] args) { 
//創建併發訪問的賬戶 
MyCount myCount = new MyCount("95599200901215522", 10000); 
//創建一個線程池 
ExecutorService pool = Executors.newFixedThreadPool(2); 
Thread t1 = new SaveThread("張三", myCount, 2000); 
Thread t2 = new SaveThread("李四", myCount, 3600); 
Thread t3 = new DrawThread("王五", myCount, 2700); 
Thread t4 = new SaveThread("老張", myCount, 600); 
Thread t5 = new DrawThread("老牛", myCount, 1300); 
Thread t6 = new DrawThread("胖子", myCount, 800); 
//執行各個線程 
pool.execute(t1); 
pool.execute(t2); 
pool.execute(t3); 
pool.execute(t4); 
pool.execute(t5); 
pool.execute(t6); 
//關閉線程池 
pool.shutdown(); 



/** 
存款線程類 
*/ 
class SaveThread extends Thread { 
private String name; //操作人 
private MyCount myCount; //賬戶 
private int x; //存款金額 

SaveThread(String name, MyCount myCount, int x) { 
this.name = name; 
this.myCount = myCount; 
this.x = x; 


public void run() { 
myCount.saving(x, name); 



/** 
取款線程類 
*/ 
class DrawThread extends Thread { 
private String name; //操作人 
private MyCount myCount; //賬戶 
private int x; //存款金額 

DrawThread(String name, MyCount myCount, int x) { 
this.name = name; 
this.myCount = myCount; 
this.x = x; 


public void run() { 
myCount.drawing(x, name); 




/** 
普通銀行賬戶,不可透支 
*/ 
class MyCount { 
private String oid; //賬號 
private int cash; //賬戶餘額 

MyCount(String oid, int cash) { 
this.oid = oid; 
this.cash = cash; 


/** 
存款 

* @param x 操作金額 
* @param name 操作人 
*/ 
public synchronized void saving(int x, String name) { 
if (x > 0) { 
cash += x; //存款 
System.out.println(name + "存款" + x + ",當前餘額爲" + cash); 

notifyAll(); //喚醒所有等待線程。 


/** 
取款 

* @param x 操作金額 
* @param name 操作人 
*/ 
public synchronized void drawing(int x, String name) { 
if (cash - x < 0) { 
try { 
wait(); 
catch (InterruptedException e1) { 
e1.printStackTrace(); 

else { 
cash -= x; //取款 
System.out.println(name + "取款" + x + ",當前餘額爲" + cash); 

notifyAll(); //喚醒所有存款操作 

}

輸出結果爲:

李四存款3600,當前餘額爲13600 
王五取款2700,當前餘額爲10900 
老張存款600,當前餘額爲11500 
老牛取款1300,當前餘額爲10200 
胖子取款800,當前餘額爲9400 
張三存款2000,當前餘額爲11400 

Process finished with exit code 0

結合先前同步代碼知識,舉一反三,將此例改爲同步代碼塊來實現,代碼如下:

import java.util.concurrent.ExecutorService; 
import java.util.concurrent.Executors; 

/** 
* Java線程:改爲同步代碼塊 

* @author leizhimin 2009-11-5 10:57:29 
*/ 
public class Test { 
public static void main(String[] args) { 
//創建併發訪問的賬戶 
MyCount myCount = new MyCount("95599200901215522", 10000); 
//創建一個線程池 
ExecutorService pool = Executors.newFixedThreadPool(2); 
Thread t1 = new SaveThread("張三", myCount, 2000); 
Thread t2 = new SaveThread("李四", myCount, 3600); 
Thread t3 = new DrawThread("王五", myCount, 2700); 
Thread t4 = new SaveThread("老張", myCount, 600); 
Thread t5 = new DrawThread("老牛", myCount, 1300); 
Thread t6 = new DrawThread("胖子", myCount, 800); 
//執行各個線程 
pool.execute(t1); 
pool.execute(t2); 
pool.execute(t3); 
pool.execute(t4); 
pool.execute(t5); 
pool.execute(t6); 
//關閉線程池 
pool.shutdown(); 



/** 
存款線程類 
*/ 
class SaveThread extends Thread { 
private String name; //操作人 
private MyCount myCount; //賬戶 
private int x; //存款金額 

SaveThread(String name, MyCount myCount, int x) { 
this.name = name; 
this.myCount = myCount; 
this.x = x; 


public void run() { 
myCount.saving(x, name); 



/** 
取款線程類 
*/ 
class DrawThread extends Thread { 
private String name; //操作人 
private MyCount myCount; //賬戶 
private int x; //存款金額 

DrawThread(String name, MyCount myCount, int x) { 
this.name = name; 
this.myCount = myCount; 
this.x = x; 


public void run() { 
myCount.drawing(x, name); 




/** 
普通銀行賬戶,不可透支 
*/ 
class MyCount { 
private String oid; //賬號 
private int cash; //賬戶餘額 

MyCount(String oid, int cash) { 
this.oid = oid; 
this.cash = cash; 


/** 
存款 

* @param x 操作金額 
* @param name 操作人 
*/ 
public void saving(int x, String name) { 
if (x > 0) { 
synchronized (this) { 
cash += x; //存款 
System.out.println(name + "存款" + x + ",當前餘額爲" + cash); 
notifyAll(); //喚醒所有等待線程。 




/** 
取款 

* @param x 操作金額 
* @param name 操作人 
*/ 
public synchronized void drawing(int x, String name) { 
synchronized (this) { 
if (cash - x < 0) { 
try { 
wait(); 
catch (InterruptedException e1) { 
e1.printStackTrace(); 

else { 
cash -= x; //取款 
System.out.println(name + "取款" + x + ",當前餘額爲" + cash); 


notifyAll(); //喚醒所有存款操作 

}

李四存款3600,當前餘額爲13600 
王五取款2700,當前餘額爲10900 
老張存款600,當前餘額爲11500 
老牛取款1300,當前餘額爲10200 
胖子取款800,當前餘額爲9400 
張三存款2000,當前餘額爲11400 

Process finished with exit code 0

對比以上三種方式,從控制角度上講,第一種最靈活,第二種代碼最簡單,第三種容易犯錯。


十五、java線程:新特徵-線程池

Sun在Java5中,對Java線程的類庫做了大量的擴展,其中線程池就是Java5的新特徵之一,除了線程池之外,還有很多多線程相關的內容,爲多線程的編程帶來了極大便利。爲了編寫高效穩定可靠的多線程程序,線程部分的新增內容顯得尤爲重要。

有關Java5線程新特徵的內容全部在java.util.concurrent下面,裏面包含數目衆多的接口和類,熟悉這部分API特徵是一項艱難的學習過程。目前有關這方面的資料和書籍都少之又少,大所屬介紹線程方面書籍還停留在java5之前的知識層面上。

當然新特徵對做多線程程序沒有必須的關係,在java5之前通用可以寫出很優秀的多線程程序。只是代價不一樣而已。

線程池的基本思想還是一種對象池的思想,開闢一塊內存空間,裏面存放了衆多(未死亡)的線程,池中線程執行調度由池管理器來處理。當有線程任務時,從池中取一個,執行完成後線程對象歸池,這樣可以避免反覆創建線程對象所帶來的性能開銷,節省了系統的資源。

在Java5之前,要實現一個線程池是相當有難度的,現在Java5爲我們做好了一切,我們只需要按照提供的API來使用,即可享受線程池帶來的極大便利。

Java5的線程池分好多種:固定尺寸的線程池、可變尺寸連接池、。

在使用線程池之前,必須知道如何去創建一個線程池,在Java5中,需要了解的是java.util.concurrent.Executors類的API,這個類提供大量創建連接池的靜態方法,是必須掌握的。

一、固定大小的線程池

import java.util.concurrent.Executors; 
import java.util.concurrent.ExecutorService; 

/** 
* Java線程:線程池

* @author Administrator 2009-11-4 23:30:44 
*/ 
public class Test { 
public static void main(String[] args) { 
//創建一個可重用固定線程數的線程池 
ExecutorService pool = Executors.newFixedThreadPool(2); 
//創建實現了Runnable接口對象,Thread對象當然也實現了Runnable接口 
Thread t1 = new MyThread(); 
Thread t2 = new MyThread(); 
Thread t3 = new MyThread(); 
Thread t4 = new MyThread(); 
Thread t5 = new MyThread(); 
//將線程放入池中進行執行 
pool.execute(t1); 
pool.execute(t2); 
pool.execute(t3); 
pool.execute(t4); 
pool.execute(t5); 
//關閉線程池 
pool.shutdown(); 



class MyThread extends Thread{ 
@Override 
public void run() { 
System.out.println(Thread.currentThread().getName()+"正在執行。。。"); 

}

pool-1-thread-1正在執行。。。 
pool-1-thread-1正在執行。。。 
pool-1-thread-1正在執行。。。 
pool-1-thread-1正在執行。。。 
pool-1-thread-2正在執行。。。 

Process finished with exit code 0

二、單任務線程池

在上例的基礎上改一行創建pool對象的代碼爲:

//創建一個使用單個 worker 線程的 Executor,以無界隊列方式來運行該線程。 
ExecutorService pool = Executors.newSingleThreadExecutor(); 

輸出結果爲:

pool-1-thread-1正在執行。。。 
pool-1-thread-1正在執行。。。 
pool-1-thread-1正在執行。。。 
pool-1-thread-1正在執行。。。 
pool-1-thread-1正在執行。。。 

Process finished with exit code 0

對於以上兩種連接池,大小都是固定的,當要加入的池的線程(或者任務)超過池最大尺寸時候,則入此線程池需要排隊等待。

一旦池中有線程完畢,則排隊等待的某個線程會入池執行。

三、可變尺寸的線程池

與上面的類似,只是改動下pool的創建方式:

//創建一個可根據需要創建新線程的線程池,但是在以前構造的線程可用時將重用它們。 
ExecutorService pool = Executors.newCachedThreadPool(); 

pool-1-thread-5正在執行。。。 
pool-1-thread-1正在執行。。。 
pool-1-thread-4正在執行。。。 
pool-1-thread-3正在執行。。。 
pool-1-thread-2正在執行。。。 

Process finished with exit code 0

四、延遲連接池

import java.util.concurrent.Executors; 
import java.util.concurrent.ScheduledExecutorService; 
import java.util.concurrent.TimeUnit; 

/** 
* Java線程:線程池

* @author Administrator 2009-11-4 23:30:44 
*/ 
public class Test { 
public static void main(String[] args) { 
//創建一個線程池,它可安排在給定延遲後運行命令或者定期地執行。 
ScheduledExecutorService pool = Executors.newScheduledThreadPool(2); 
//創建實現了Runnable接口對象,Thread對象當然也實現了Runnable接口 
Thread t1 = new MyThread(); 
Thread t2 = new MyThread(); 
Thread t3 = new MyThread(); 
Thread t4 = new MyThread(); 
Thread t5 = new MyThread(); 
//將線程放入池中進行執行 
pool.execute(t1); 
pool.execute(t2); 
pool.execute(t3); 
//使用延遲執行風格的方法 
pool.schedule(t4, 10, TimeUnit.MILLISECONDS); 
pool.schedule(t5, 10, TimeUnit.MILLISECONDS); 
//關閉線程池 
pool.shutdown(); 



class MyThread extends Thread { 
@Override 
public void run() { 
System.out.println(Thread.currentThread().getName() + "正在執行。。。"); 

}

pool-1-thread-1正在執行。。。 
pool-1-thread-2正在執行。。。 
pool-1-thread-1正在執行。。。 
pool-1-thread-1正在執行。。。 
pool-1-thread-2正在執行。。。 

Process finished with exit code 0

五、單任務延遲連接池

在四代碼基礎上,做改動

//創建一個單線程執行程序,它可安排在給定延遲後運行命令或者定期地執行。 
ScheduledExecutorService pool = Executors.newSingleThreadScheduledExecutor();

pool-1-thread-1正在執行。。。 
pool-1-thread-1正在執行。。。 
pool-1-thread-1正在執行。。。 
pool-1-thread-1正在執行。。。 
pool-1-thread-1正在執行。。。 

Process finished with exit code 0 

六、自定義線程池

import java.util.concurrent.ArrayBlockingQueue; 
import java.util.concurrent.BlockingQueue; 
import java.util.concurrent.ThreadPoolExecutor; 
import java.util.concurrent.TimeUnit; 

/** 
* Java線程:線程池-自定義線程池 

* @author Administrator 2009-11-4 23:30:44 
*/ 
public class Test { 
public static void main(String[] args) { 
//創建等待隊列 
BlockingQueue<Runnable> bqueue = new ArrayBlockingQueue<Runnable>(20); 
//創建一個單線程執行程序,它可安排在給定延遲後運行命令或者定期地執行。 
ThreadPoolExecutor pool = new ThreadPoolExecutor(2,3,2,TimeUnit.MILLISECONDS,bqueue); 
//創建實現了Runnable接口對象,Thread對象當然也實現了Runnable接口 
Thread t1 = new MyThread(); 
Thread t2 = new MyThread(); 
Thread t3 = new MyThread(); 
Thread t4 = new MyThread(); 
Thread t5 = new MyThread(); 
Thread t6 = new MyThread(); 
Thread t7 = new MyThread(); 
//將線程放入池中進行執行 
pool.execute(t1); 
pool.execute(t2); 
pool.execute(t3); 
pool.execute(t4); 
pool.execute(t5); 
pool.execute(t6); 
pool.execute(t7); 
//關閉線程池 
pool.shutdown(); 



class MyThread extends Thread { 
@Override 
public void run() { 
System.out.println(Thread.currentThread().getName() + "正在執行。。。"); 
try { 
Thread.sleep(100L); 
catch (InterruptedException e) { 
e.printStackTrace(); 


}

pool-1-thread-1正在執行。。。 
pool-1-thread-2正在執行。。。 
pool-1-thread-2正在執行。。。 
pool-1-thread-1正在執行。。。 
pool-1-thread-2正在執行。。。 
pool-1-thread-1正在執行。。。 
pool-1-thread-2正在執行。。。 

Process finished with exit code 0

創建自定義線程池的構造方法很多,本例中參數的含義如下:

ThreadPoolExecutor

public ThreadPoolExecutor(int corePoolSize,

                          int maximumPoolSize,

                          long keepAliveTime,

                          TimeUnit unit,

                          BlockingQueue<Runnable> workQueue)

用給定的初始參數和默認的線程工廠及處理程序創建新的 ThreadPoolExecutor。使用 Executors 工廠方法之一比使用此通用構造方法方便得多。 

參數:

corePoolSize - 池中所保存的線程數,包括空閒線程。 

maximumPoolSize - 池中允許的最大線程數。 

keepAliveTime - 當線程數大於核心時,此爲終止前多餘的空閒線程等待新任務的最長時間。 

unit - keepAliveTime 參數的時間單位。 

workQueue - 執行前用於保持任務的隊列。此隊列僅保持由 execute 方法提交的 Runnable 任務。 

拋出:

IllegalArgumentException - 如果 corePoolSize 或 keepAliveTime 小於零,或者 maximumPoolSize 小於或等於零,或者 corePoolSize 大於 maximumPoolSize。 

NullPointerException - 如果 workQueue 爲 null

自定義連接池稍微麻煩些,不過通過創建的ThreadPoolExecutor線程池對象,可以獲取到當前線程池的尺寸、正在執行任務的線程數、工作隊列等等。

有關Java5線程池的內容到此就沒有了,更多的內容還需要研讀API來獲取。


十六、java線程:新特徵-信號量

Java的信號量實際上是一個功能完畢的計數器,對控制一定資源的消費與回收有着很重要的意義,信號量常常用於多線程的代碼中,並能監控有多少數目的線程等待獲取資源,並且通過信號量可以得知可用資源的數目等等,這裏總是在強調“數目”二字,但不能指出來有哪些在等待,哪些資源可用。

因此,本人認爲,這個信號量類如果能返回數目,還能知道哪些對象在等待,哪些資源可使用,就非常完美了,僅僅拿到這些概括性的數字,對精確控制意義不是很大。目前還沒想到更好的用法。

下面是一個簡單例子:

import java.util.concurrent.ExecutorService; 
import java.util.concurrent.Executors; 
import java.util.concurrent.Semaphore; 

/** 
* Java線程:新特徵-信號量 

* @author leizhimin 2009-11-5 13:44:45 
*/ 
public class Test { 
public static void main(String[] args) { 
MyPool myPool = new MyPool(20); 
//創建線程池 
ExecutorService threadPool = Executors.newFixedThreadPool(2); 
MyThread t1 = new MyThread("任務A", myPool, 3); 
MyThread t2 = new MyThread("任務B", myPool, 12); 
MyThread t3 = new MyThread("任務C", myPool, 7); 
//在線程池中執行任務 
threadPool.execute(t1); 
threadPool.execute(t2); 
threadPool.execute(t3); 
//關閉池 
threadPool.shutdown(); 



/** 
一個池 
*/ 
class MyPool { 
private Semaphore sp; //池相關的信號量 

/** 
池的大小,這個大小會傳遞給信號量 

* @param size 池的大小 
*/ 
MyPool(int size) { 
this.sp = new Semaphore(size); 


public Semaphore getSp() { 
return sp; 


public void setSp(Semaphore sp) { 
this.sp = sp; 



class MyThread extends Thread { 
private String threadname; //線程的名稱 
private MyPool pool; //自定義池 
private int x; //申請信號量的大小 

MyThread(String threadname, MyPool pool, int x) { 
this.threadname = threadname; 
this.pool = pool; 
this.x = x; 


public void run() { 
try { 
//從此信號量獲取給定數目的許可 
pool.getSp().acquire(x); 
//todo:也許這裏可以做更復雜的業務 
System.out.println(threadname + "成功獲取了" + x + "個許可!"); 
catch (InterruptedException e) { 
e.printStackTrace(); 
finally { 
//釋放給定數目的許可,將其返回到信號量。 
pool.getSp().release(x); 
System.out.println(threadname + "釋放了" + x + "個許可!"); 


}

任務B成功獲取了12個許可! 
任務B釋放了12個許可! 
任務A成功獲取了3個許可! 
任務C成功獲取了7個許可! 
任務C釋放了7個許可! 
任務A釋放了3個許可! 

Process finished with exit code 0

從結果可以看出,信號量僅僅是對池資源進行監控,但不保證線程的安全,因此,在使用時候,應該自己控制線程的安全訪問池資源。


十七、java線程:新特徵-帶返回值的線程

在Java5之前,線程是沒有返回值的,常常爲了“有”返回值,破費周折,而且代碼很不好寫。或者乾脆繞過這道坎,走別的路了。

現在Java終於有可返回值的任務(也可以叫做線程)了。

可返回值的任務必須實現Callable接口,類似的,無返回值的任務必須Runnable接口。

執行Callable任務後,可以獲取一個Future的對象,在該對象上調用get就可以獲取到Callable任務返回的Object了。

下面是個很簡單的例子:

import java.util.concurrent.*; 

/** 
* Java線程:有返回值的線程 

* @author Administrator 2009-11-5 0:41:50 
*/ 
public class Test { 
public static void main(String[] args) throws ExecutionException, InterruptedException { 
//創建一個線程池 
ExecutorService pool = Executors.newFixedThreadPool(2); 
//創建兩個有返回值的任務 
Callable c1 = new MyCallable("A"); 
Callable c2 = new MyCallable("B"); 
//執行任務並獲取Future對象 
Future f1 = pool.submit(c1); 
Future f2 = pool.submit(c2); 
//Future對象上獲取任務的返回值,並輸出到控制檯 
System.out.println(">>>"+f1.get().toString()); 
System.out.println(">>>"+f2.get().toString()); 
//關閉線程池 
pool.shutdown(); 



class MyCallable implements Callable{ 
private String oid; 

MyCallable(String oid) { 
this.oid = oid; 


@Override 
public Object call() throws Exception { 
return oid+"任務返回的內容"

}

>>>A任務返回的內容 
>>>B任務返回的內容 

Process finished with exit code 0

非常的簡單,要深入瞭解還需要看Callable和Future接口的API啊。


十八、java線程:新特徵-原子量

所謂的原子量即操作變量的操作是“原子的”,該操作不可再分,因此是線程安全的。

爲何要使用原子變量呢,原因是多個線程對單個變量操作也會引起一些問題。在Java5之前,可以通過volatile、synchronized關鍵字來解決併發訪問的安全問題,但這樣太麻煩。

Java5之後,專門提供了用來進行單變量多線程併發安全訪問的工具包java.util.concurrent.atomic,其中的類也很簡單。

下面給出一個反面例子(切勿模仿):

import java.util.concurrent.ExecutorService; 
import java.util.concurrent.Executors; 
import java.util.concurrent.atomic.AtomicLong; 

/** 
* Java線程:新特徵-原子量 

* @author leizhimin 2009-11-6 9:53:11 
*/ 
public class Test { 
public static void main(String[] args) { 
ExecutorService pool = Executors.newFixedThreadPool(2); 
Runnable t1 = new MyRunnable("張三", 2000); 
Runnable t2 = new MyRunnable("李四", 3600); 
Runnable t3 = new MyRunnable("王五", 2700); 
Runnable t4 = new MyRunnable("老張", 600); 
Runnable t5 = new MyRunnable("老牛", 1300); 
Runnable t6 = new MyRunnable("胖子", 800); 
//執行各個線程 
pool.execute(t1); 
pool.execute(t2); 
pool.execute(t3); 
pool.execute(t4); 
pool.execute(t5); 
pool.execute(t6); 
//關閉線程池 
pool.shutdown(); 



class MyRunnable implements Runnable { 
private static AtomicLong aLong = new AtomicLong(10000); //原子量,每個線程都可以自由操作 
private String name; //操作人 
private int x; //操作數額 

MyRunnable(String name, int x) { 
this.name = name; 
this.x = x; 


public void run() { 
System.out.println(name + "執行了" + x + ",當前餘額:" + aLong.addAndGet(x)); 

}

運行結果:

李四執行了3600,當前餘額:13600 
王五執行了2700,當前餘額:16300 
老張執行了600,當前餘額:16900 
老牛執行了1300,當前餘額:18200 
胖子執行了800,當前餘額:19000 
張三執行了2000,當前餘額:21000 

Process finished with exit code 0

張三執行了2000,當前餘額:12000 
王五執行了2700,當前餘額:18300 
老張執行了600,當前餘額:18900 
老牛執行了1300,當前餘額:20200 
胖子執行了800,當前餘額:21000 
李四執行了3600,當前餘額:15600 

Process finished with exit code 0

張三執行了2000,當前餘額:12000 
李四執行了3600,當前餘額:15600 
老張執行了600,當前餘額:18900 
老牛執行了1300,當前餘額:20200 
胖子執行了800,當前餘額:21000 
王五執行了2700,當前餘額:18300 

Process finished with exit code 0

從運行結果可以看出,雖然使用了原子量,但是程序併發訪問還是有問題,那究竟問題出在哪裏了?

這裏要注意的一點是,原子量雖然可以保證單個變量在某一個操作過程的安全,但無法保證你整個代碼塊,或者整個程序的安全性。因此,通常還應該使用鎖等同步機制來控制整個程序的安全性。

下面是對這個錯誤修正:

import java.util.concurrent.ExecutorService; 
import java.util.concurrent.Executors; 
import java.util.concurrent.locks.Lock; 
import java.util.concurrent.locks.ReentrantLock; 
import java.util.concurrent.atomic.AtomicLong; 

/** 
* Java線程:新特徵-原子量 

* @author leizhimin 2009-11-6 9:53:11 
*/ 
public class Test { 
public static void main(String[] args) { 
ExecutorService pool = Executors.newFixedThreadPool(2); 
Lock lock = new ReentrantLock(false); 
Runnable t1 = new MyRunnable("張三", 2000,lock); 
Runnable t2 = new MyRunnable("李四", 3600,lock); 
Runnable t3 = new MyRunnable("王五", 2700,lock); 
Runnable t4 = new MyRunnable("老張", 600,lock); 
Runnable t5 = new MyRunnable("老牛", 1300,lock); 
Runnable t6 = new MyRunnable("胖子", 800,lock); 
//執行各個線程 
pool.execute(t1); 
pool.execute(t2); 
pool.execute(t3); 
pool.execute(t4); 
pool.execute(t5); 
pool.execute(t6); 
//關閉線程池 
pool.shutdown(); 



class MyRunnable implements Runnable { 
private static AtomicLong aLong = new AtomicLong(10000); //原子量,每個線程都可以自由操作 
private String name; //操作人 
private int x; //操作數額 
private Lock lock; 

MyRunnable(String name, int x,Lock lock) { 
this.name = name; 
this.x = x; 
this.lock = lock; 


public void run() { 
lock.lock(); 
System.out.println(name + "執行了" + x + ",當前餘額:" + aLong.addAndGet(x)); 
lock.unlock(); 

}

執行結果:

張三執行了2000,當前餘額:12000 
王五執行了2700,當前餘額:14700 
老張執行了600,當前餘額:15300 
老牛執行了1300,當前餘額:16600 
胖子執行了800,當前餘額:17400 
李四執行了3600,當前餘額:21000 

Process finished with exit code 0

這裏使用了一個對象鎖,來控制對併發代碼的訪問。不管運行多少次,執行次序如何,最終餘額均爲21000,這個結果是正確的。

有關原子量的用法很簡單,關鍵是對原子量的認識,原子僅僅是保證變量操作的原子性,但整個程序還需要考慮線程安全的。


十九、java線程:新特徵-障礙器

Java5中,添加了障礙器類,爲了適應一種新的設計需求,比如一個大型的任務,常常需要分配好多子任務去執行,只有當所有子任務都執行完成時候,才能執行主任務,這時候,就可以選擇障礙器了。

障礙器是多線程併發控制的一種手段,用法很簡單。下面給個例子:

import java.util.concurrent.BrokenBarrierException; 
import java.util.concurrent.CyclicBarrier; 

/** 
* Java線程:新特徵-障礙器 

* @author leizhimin 2009-11-6 10:50:10 
*/ 
public class Test { 
public static void main(String[] args) { 
//創建障礙器,並設置MainTask爲所有定數量的線程都達到障礙點時候所要執行的任務(Runnable) 
CyclicBarrier cb = new CyclicBarrier(7, new MainTask()); 
new SubTask("A", cb).start(); 
new SubTask("B", cb).start(); 
new SubTask("C", cb).start(); 
new SubTask("D", cb).start(); 
new SubTask("E", cb).start(); 
new SubTask("F", cb).start(); 
new SubTask("G", cb).start(); 



/** 
主任務 
*/ 
class MainTask implements Runnable { 
public void run() { 
System.out.println(">>>>主任務執行了!<<<<"); 



/** 
子任務 
*/ 
class SubTask extends Thread { 
private String name; 
private CyclicBarrier cb; 

SubTask(String name, CyclicBarrier cb) { 
this.name = name; 
this.cb = cb; 


public void run() { 
System.out.println("[子任務" + name + "]開始執行了!"); 
for (int i = 0; i < 999999; i++) ; //模擬耗時的任務 
System.out.println("[子任務" + name + "]開始執行完成了,並通知障礙器已經完成!"); 
try { 
//通知障礙器已經完成 
cb.await(); 
catch (InterruptedException e) { 
e.printStackTrace(); 
catch (BrokenBarrierException e) { 
e.printStackTrace(); 


}

運行結果:

[子任務E]開始執行了! 
[子任務E]開始執行完成了,並通知障礙器已經完成! 
[子任務F]開始執行了! 
[子任務G]開始執行了! 
[子任務F]開始執行完成了,並通知障礙器已經完成! 
[子任務G]開始執行完成了,並通知障礙器已經完成! 
[子任務C]開始執行了! 
[子任務B]開始執行了! 
[子任務C]開始執行完成了,並通知障礙器已經完成! 
[子任務D]開始執行了! 
[子任務A]開始執行了! 
[子任務D]開始執行完成了,並通知障礙器已經完成! 
[子任務B]開始執行完成了,並通知障礙器已經完成! 
[子任務A]開始執行完成了,並通知障礙器已經完成! 
>>>>主任務執行了!<<<< 

Process finished with exit code 0

從執行結果可以看出,所有子任務完成的時候,主任務執行了,達到了控制的目標。



二十、java線程:新特徵-阻塞隊列

阻塞隊列是Java5線程新特徵中的內容,Java定義了阻塞隊列的接口java.util.concurrent.BlockingQueue,阻塞隊列的概念是,一個指定長度的隊列,如果隊列滿了,添加新元素的操作會被阻塞等待,直到有空位爲止。同樣,當隊列爲空時候,請求隊列元素的操作同樣會阻塞等待,直到有可用元素爲止。

有了這樣的功能,就爲多線程的排隊等候的模型實現開闢了便捷通道,非常有用。

java.util.concurrent.BlockingQueue繼承了java.util.Queue接口,可以參看API文檔。

下面給出一個簡單應用的例子:

import java.util.concurrent.BlockingQueue; 
import java.util.concurrent.ArrayBlockingQueue; 

/** 
* Java線程:新特徵-阻塞隊列 

* @author leizhimin 2009-11-5 14:59:15 
*/ 
public class Test { 
public static void main(String[] args) throws InterruptedException { 
BlockingQueue bqueue = new ArrayBlockingQueue(20); 
for (int i = 0; i < 30; i++) { 
//將指定元素添加到此隊列中,如果沒有可用空間,將一直等待(如果有必要)。 
bqueue.put(i); 
System.out.println("向阻塞隊列中添加了元素:" + i); 

System.out.println("程序到此運行結束,即將退出----"); 

}

輸出結果:

向阻塞隊列中添加了元素:0 
向阻塞隊列中添加了元素:1 
向阻塞隊列中添加了元素:2 
向阻塞隊列中添加了元素:3 
向阻塞隊列中添加了元素:4 
向阻塞隊列中添加了元素:5 
向阻塞隊列中添加了元素:6 
向阻塞隊列中添加了元素:7 
向阻塞隊列中添加了元素:8 
向阻塞隊列中添加了元素:9 
向阻塞隊列中添加了元素:10 
向阻塞隊列中添加了元素:11 
向阻塞隊列中添加了元素:12 
向阻塞隊列中添加了元素:13 
向阻塞隊列中添加了元素:14 
向阻塞隊列中添加了元素:15 
向阻塞隊列中添加了元素:16 
向阻塞隊列中添加了元素:17 
向阻塞隊列中添加了元素:18 
向阻塞隊列中添加了元素:19 

可以看出,輸出到元素19時候,就一直處於等待狀態,因爲隊列滿了,程序阻塞了。

這裏沒有用多線程來演示,沒有這個必要。

另外,阻塞隊列還有更多實現類,用來滿足各種複雜的需求:ArrayBlockingQueue, DelayQueue, LinkedBlockingQueue, PriorityBlockingQueue, SynchronousQueue ,具體的API差別也很小。


二十一、java線程:新特徵-阻塞棧

對於阻塞棧,與阻塞隊列相似。不同點在於棧是“後入先出”的結構,每次操作的是棧頂,而隊列是“先進先出”的結構,每次操作的是隊列頭。

這裏要特別說明一點的是,阻塞棧是Java6的新特徵。、

Java爲阻塞棧定義了接口:java.util.concurrent.BlockingDeque,其實現類也比較多,具體可以查看JavaAPI文檔。

下面看一個簡單例子:

import java.util.concurrent.BlockingDeque; 
import java.util.concurrent.LinkedBlockingDeque; 

/** 
* Java線程:新特徵-阻塞棧 

* @author leizhimin 2009-11-5 15:34:29 
*/ 
public class Test { 
public static void main(String[] args) throws InterruptedException { 
BlockingDeque bDeque = new LinkedBlockingDeque(20); 
for (int i = 0; i < 30; i++) { 
//將指定元素添加到此阻塞棧中,如果沒有可用空間,將一直等待(如果有必要)。 
bDeque.putFirst(i); 
System.out.println("向阻塞棧中添加了元素:" + i); 

System.out.println("程序到此運行結束,即將退出----"); 

}

輸出結果:

向阻塞棧中添加了元素:0 
向阻塞棧中添加了元素:1 
向阻塞棧中添加了元素:2 
向阻塞棧中添加了元素:3 
向阻塞棧中添加了元素:4 
向阻塞棧中添加了元素:5 
向阻塞棧中添加了元素:6 
向阻塞棧中添加了元素:7 
向阻塞棧中添加了元素:8 
向阻塞棧中添加了元素:9 
向阻塞棧中添加了元素:10 
向阻塞棧中添加了元素:11 
向阻塞棧中添加了元素:12 
向阻塞棧中添加了元素:13 
向阻塞棧中添加了元素:14 
向阻塞棧中添加了元素:15 
向阻塞棧中添加了元素:16 
向阻塞棧中添加了元素:17 
向阻塞棧中添加了元素:18 
向阻塞棧中添加了元素:19 

從上面結果可以看到,程序並沒結束,二是阻塞住了,原因是棧已經滿了,後面追加元素的操作都被阻塞了。

發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章