Java線程:概念與原理
1、操做系統中線程和進程的概念
如今的操做系統是多任務操做系統。多線程是實現多任務的一種方式。
進程是指一個內存中運行的應用程序,每一個進程都有本身獨立的一塊內存空間,一個進程中能夠啓動多個線程。好比在Windows系統中,一個運行的exe就是一個進程。
線程是指進程中的一個執行流程,一個進程中能夠運行多個線程。好比java.exe進程中能夠運行不少線程。線程老是屬於某個進程,進程中的多個線程共享進程的內存。
「同時」執行是人的感受,在線程之間實際上輪換執行。
2、Java中的線程
在Java中,「線程」指兩件不一樣的事情:
一、java.lang.Thread類的一個實例;
二、線程的執行。
使用java.lang.Thread類或者java.lang.Runnable接口編寫代碼來定義、實例化和啓動新線程。
一個Thread類實例只是一個對象,像Java中的任何其餘對象同樣,具備變量和方法,生死於堆上。
Java中,每一個線程都有一個調用棧,即便不在程序中建立任何新的線程,線程也在後臺運行着。
一個Java應用老是從main()方法開始運行,mian()方法運行在一個線程內,它被稱爲主線程。
一旦建立一個新的線程,就產生一個新的調用棧。
線程整體分兩類:用戶線程和守候線程。
當全部用戶線程執行完畢的時候,JVM自動關閉。可是守候線程卻不獨立於JVM,守候線程通常是由操做系統或者用戶本身建立的
Java線程:建立與啓動
1、定義線程
一、擴展java.lang.Thread類。
此類中有個run()方法,應該注意其用法:
public void run()
若是該線程是使用獨立的Runnable運行對象構造的,則調用該Runnable對象的run方法;不然,該方法不執行任何操做並返回。
Thread的子類應該重寫該方法。
二、實現java.lang.Runnable接口。
void run()
使用實現接口Runnable的對象建立一個線程時,啓動該線程將致使在獨立執行的線程中調用對象的run方法。
方法run的常規協定是,它可能執行任何所需的操做。
2、實例化線程
一、若是是擴展java.lang.Thread類的線程,則直接new便可。
二、若是是實現了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)
3、啓動線程
在線程的Thread對象上調用start()方法,而不是run()或者別的方法。
在調用start()方法以前:線程處於新狀態中,新狀態指有一個Thread對象,但尚未一個真正的線程。
在調用start()方法以後:發生了一系列複雜的事情
啓動新的執行線程(具備新的調用棧);
該線程重新狀態轉移到可運行狀態;
當該線程得到機會執行時,其目標run()方法將運行。
注意:對Java來講,run()方法沒有任何特別之處。像main()方法同樣,它只是新線程知道調用的方法名稱(和簽名)。所以,在Runnable上或者Thread上調用run方法是合法的。但並不啓動新的線程。
4、例子
一、實現Runnable接口的多線程例子
/**
* 實現Runnable接口的類
*
* @author leizhimin 2008-9-13 18:12:10
*/
publicclass DoSomethingimplements Runnable {
private String name;
public DoSomething(String name) {
this.name = name;
}
publicvoid 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
*/
publicclass TestRunnable {
publicstaticvoid 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
二、擴展Thread類實現的多線程例子
/**
* 測試擴展Thread類實現的多線程程序
*
* @author leizhimin 2008-9-13 18:22:13
*/
publicclass TestThreadextends Thread{
public TestThread(String name) {
super(name);
}
publicvoid run() {
for(int i = 0;i<5;i++){
for(long k= 0; k <100000000;k++);
System.out.println(this.getName()+" :"+i);
}
}
publicstaticvoid 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++);是用來模擬一個很是耗時的操做的。
5、一些常見問題
一、線程的名字,一個運行中的線程老是有名字的,名字有兩個來源,一個是虛擬機本身給的名字,一個是你本身的定的名字。在沒有指定線程名字的狀況下,虛擬機總會爲線程指定名字,而且主線程的名字老是mian,非主線程的名字不肯定。
二、線程均可以設置名字,也能夠獲取線程的名字,連主線程也不例外。
三、獲取當前線程的對象的方法是:Thread.currentThread();
四、在上面的代碼中,只能保證:每一個線程都將啓動,每一個線程都將運行直到完成。一系列線程以某種順序啓動並不意味着將按該順序執行。對於任何一組啓動的線程來講,調度程序不能保證其執行次序,持續時間也沒法保證。
五、當線程目標run()方法結束時該線程完成。
六、一旦線程啓動,它就永遠不能再從新啓動。只有一個新的線程能夠被啓動,而且只能一次。一個可運行的線程或死線程能夠被從新啓動。
七、線程的調度是JVM的一部分,在一個CPU的機器上上,實際上一次只能運行一個線程。一次只有一個線程棧執行。JVM線程調度程序決定實際運行哪一個處於可運行狀態的線程。
衆多可運行線程中的某一個會被選中作爲當前線程。可運行線程被選擇運行的順序是沒有保障的。
八、儘管一般採用隊列形式,但這是沒有保障的。隊列形式是指當一個線程完成「一輪」時,它移到可運行隊列的尾部等待,直到它最終排隊到該隊列的前端爲止,它才能被再次選中。事實上,咱們把它稱爲可運行池而不是一個可運行隊列,目的是幫助認識線程並不都是以某種有保障的順序排列唱呢個一個隊列的事實。
九、儘管咱們沒有沒法控制線程調度程序,但能夠經過別的方式來影響線程調度的方式。
Java線程:線程棧模型與線程的變量
要理解線程調度的原理,以及線程執行過程,必須理解線程棧模型。
線程棧是指某時刻時內存中線程調度的棧信息,當前調用的方法老是位於棧頂。線程棧的內容是隨着程序的運行動態變化的,所以研究線程棧必須選擇一個運行的時刻(實際上指代碼運行到什麼地方)。
下面經過一個示例性的代碼說明線程(調用)棧的變化過程。
這幅圖描述在代碼執行到兩個不一樣時刻一、2時候,虛擬機線程調用棧示意圖。
當程序執行到t.start();時候,程序多出一個分支(增長了一個調用棧B),這樣,棧A、棧B並行執行。
從這裏就能夠看出方法調用和線程啓動的區別了。
Java線程:線程狀態的轉換
1、線程狀態
線程的狀態轉換是線程控制的基礎。線程狀態總的可分爲五大狀態:分別是生、死、可運行、運行、等待/阻塞。用一個圖來描述以下:
一、新狀態:線程對象已經建立,尚未在其上調用start()方法。
二、可運行狀態:當線程有資格運行,但調度程序尚未把它選定爲運行線程時線程所處的狀態。當start()方法調用時,線程首先進入可運行狀態。在線程運行以後或者從阻塞、等待或睡眠狀態回來後,也返回到可運行狀態。
三、運行狀態:線程調度程序從可運行池中選擇一個線程做爲當前線程時線程所處的狀態。這也是線程進入運行狀態的惟一一種方式。
四、等待/阻塞/睡眠狀態:這是線程有資格運行時它所處的狀態。實際上這個三狀態組合爲一種,其共同點是:線程仍舊是活的,可是當前沒有條件運行。換句話說,它是可運行的,可是若是某件事件出現,他可能返回到可運行狀態。
五、死亡態:當線程的run()方法完成時就認爲它死去。這個線程對象也許是活的,可是,它已經不是一個單獨執行的線程。線程一旦死亡,就不能復生。若是在一個死去的線程上調用start()方法,會拋出java.lang.IllegalThreadStateException異常。
有關詳細狀態轉換圖能夠參看本人的「Java多線程編程總結」中的圖
2、阻止線程執行
對於線程的阻止,考慮一下三個方面,不考慮IO阻塞的狀況:
睡眠;
等待;
由於須要一個對象的鎖定而被阻塞。
一、睡眠
Thread.sleep(long millis)和Thread.sleep(long millis, int nanos)靜態方法強制當前正在執行的線程休眠(暫停執行),以「減慢線程」。當線程睡眠時,它入睡在某個地方,在甦醒以前不會返回到可運行狀態。當睡眠時間到期,則返回到可運行狀態。
線程睡眠的緣由:線程執行太快,或者須要強制進入下一輪,由於Java規範不保證合理的輪換。
睡眠的實現:調用靜態方法。
try {
Thread.sleep(123);
} catch (InterruptedException e) {
e.printStackTrace();
}
睡眠的位置:爲了讓其餘線程有機會執行,能夠將Thread.sleep()的調用放線程run()以內。這樣才能保證該線程執行過程當中會睡眠。
例如,在前面的例子中,將一個耗時的操做改成睡眠,以減慢線程的執行。能夠這麼寫:
public void run() {
for(int i = 0;i<5;i++){
// 很耗時的操做,用來減慢線程的執行
// for(long k= 0; k <100000000;k++);
try {
Thread.sleep(3);
} catch (InterruptedException e) {
e.printStackTrace(); .
}
System.out.println(this.getName()+" :"+i);
}
}
運行結果:
阿三 :0
李四 :0
阿三 :1
阿三 :2
阿三 :3
李四 :1
李四 :2
阿三 :4
李四 :3
李四 :4
Process finished with exit code 0
這樣,線程在每次執行過程當中,總會睡眠3毫秒,睡眠了,其餘的線程就有機會執行了。
注意:
一、線程睡眠是幫助全部線程得到運行機會的最好方法。
二、線程睡眠到期自動甦醒,並返回到可運行狀態,不是運行狀態。sleep()中指定的時間是線程不會運行的最短期。所以,sleep()方法不能保證該線程睡眠到期後就開始執行。
三、sleep()是靜態方法,只能控制當前正在運行的線程。
下面給個例子:
/**
* 一個計數器,計數到100,在每一個數字之間暫停1秒,每隔10個數字輸出一個字符串
*
* @author leizhimin 2008-9-14 9:53:49
*/
publicclass MyThreadextends Thread {
publicvoid run() {
for (int i = 0; i < 100; i++) {
if ((i) % 10 == 0) {
System.out.println("-------" + i);
}
System.out.print(i);
try {
Thread.sleep(1);
System.out.print(" 線程睡眠1毫秒!\n");
} catch (InterruptedException e) {
e.printStackTrace();
}
}
}
publicstaticvoid main(String[] args) {
new MyThread().start();
}
}
-------0
0 線程睡眠1毫秒!
1 線程睡眠1毫秒!
2 線程睡眠1毫秒!
3 線程睡眠1毫秒!
4 線程睡眠1毫秒!
5 線程睡眠1毫秒!
6 線程睡眠1毫秒!
7 線程睡眠1毫秒!
8 線程睡眠1毫秒!
9 線程睡眠1毫秒!
-------10
10 線程睡眠1毫秒!
11 線程睡眠1毫秒!
12 線程睡眠1毫秒!
13 線程睡眠1毫秒!
14 線程睡眠1毫秒!
15 線程睡眠1毫秒!
16 線程睡眠1毫秒!
17 線程睡眠1毫秒!
18 線程睡眠1毫秒!
19 線程睡眠1毫秒!
-------20
20 線程睡眠1毫秒!
21 線程睡眠1毫秒!
22 線程睡眠1毫秒!
23 線程睡眠1毫秒!
24 線程睡眠1毫秒!
25 線程睡眠1毫秒!
26 線程睡眠1毫秒!
27 線程睡眠1毫秒!
28 線程睡眠1毫秒!
29 線程睡眠1毫秒!
-------30
30 線程睡眠1毫秒!
31 線程睡眠1毫秒!
32 線程睡眠1毫秒!
33 線程睡眠1毫秒!
34 線程睡眠1毫秒!
35 線程睡眠1毫秒!
36 線程睡眠1毫秒!
37 線程睡眠1毫秒!
38 線程睡眠1毫秒!
39 線程睡眠1毫秒!
-------40
40 線程睡眠1毫秒!
41 線程睡眠1毫秒!
42 線程睡眠1毫秒!
43 線程睡眠1毫秒!
44 線程睡眠1毫秒!
45 線程睡眠1毫秒!
46 線程睡眠1毫秒!
47 線程睡眠1毫秒!
48 線程睡眠1毫秒!
49 線程睡眠1毫秒!
-------50
50 線程睡眠1毫秒!
51 線程睡眠1毫秒!
52 線程睡眠1毫秒!
53 線程睡眠1毫秒!
54 線程睡眠1毫秒!
55 線程睡眠1毫秒!
56 線程睡眠1毫秒!
57 線程睡眠1毫秒!
58 線程睡眠1毫秒!
59 線程睡眠1毫秒!
-------60
60 線程睡眠1毫秒!
61 線程睡眠1毫秒!
62 線程睡眠1毫秒!
63 線程睡眠1毫秒!
64 線程睡眠1毫秒!
65 線程睡眠1毫秒!
66 線程睡眠1毫秒!
67 線程睡眠1毫秒!
68 線程睡眠1毫秒!
69 線程睡眠1毫秒!
-------70
70 線程睡眠1毫秒!
71 線程睡眠1毫秒!
72 線程睡眠1毫秒!
73 線程睡眠1毫秒!
74 線程睡眠1毫秒!
75 線程睡眠1毫秒!
76 線程睡眠1毫秒!
77 線程睡眠1毫秒!
78 線程睡眠1毫秒!
79 線程睡眠1毫秒!
-------80
80 線程睡眠1毫秒!
81 線程睡眠1毫秒!
82 線程睡眠1毫秒!
83 線程睡眠1毫秒!
84 線程睡眠1毫秒!
85 線程睡眠1毫秒!
86 線程睡眠1毫秒!
87 線程睡眠1毫秒!
88 線程睡眠1毫秒!
89 線程睡眠1毫秒!
-------90
90 線程睡眠1毫秒!
91 線程睡眠1毫秒!
92 線程睡眠1毫秒!
93 線程睡眠1毫秒!
94 線程睡眠1毫秒!
95 線程睡眠1毫秒!
96 線程睡眠1毫秒!
97 線程睡眠1毫秒!
98 線程睡眠1毫秒!
99 線程睡眠1毫秒!
Process finished with exit code 0
二、線程的優先級和線程讓步yield()
線程的讓步是經過Thread.yield()來實現的。yield()方法的做用是:暫停當前正在執行的線程對象,並執行其餘線程。
要理解yield(),必須瞭解線程的優先級的概念。線程老是存在優先級,優先級範圍在1~10之間。JVM線程調度程序是基於優先級的搶先調度機制。在大多數狀況下,當前運行的線程優先級將大於或等於線程池中任何線程的優先級。但這僅僅是大多數狀況。
注意:當設計多線程應用程序的時候,必定不要依賴於線程的優先級。由於線程調度優先級操做是沒有保障的,只能把線程優先級做用做爲一種提升程序效率的方法,可是要保證程序不依賴這種操做。
當線程池中線程都具備相同的優先級,調度程序的JVM實現自由選擇它喜歡的線程。這時候調度程序的操做有兩種可能:一是選擇一個線程運行,直到它阻塞或者運行完成爲止。二是時間分片,爲池內的每一個線程提供均等的運行機會。
設置線程的優先級:線程默認的優先級是建立它的執行線程的優先級。能夠經過setPriority(int newPriority)更改線程的優先級。例如:
Thread t = new MyThread();
t.setPriority(8);
t.start();
線程優先級爲1~10之間的正整數,JVM從不會改變一個線程的優先級。然而,1~10之間的值是沒有保證的。一些JVM可能不能識別10個不一樣的值,而將這些優先級進行每兩個或多個合併,變成少於10個的優先級,則兩個或多個優先級的線程可能被映射爲一個優先級。
線程默認優先級是5,Thread類中有三個常量,定義線程優先級範圍:
static int MAX_PRIORITY
線程能夠具備的最高優先級。
static int MIN_PRIORITY
線程能夠具備的最低優先級。
static int NORM_PRIORITY
分配給線程的默認優先級。
三、Thread.yield()方法
Thread.yield()方法做用是:暫停當前正在執行的線程對象,並執行其餘線程。
yield()應該作的是讓當前運行線程回到可運行狀態,以容許具備相同優先級的其餘線程得到運行機會。所以,使用yield()的目的是讓相同優先級的線程之間能適當的輪轉執行。可是,實際中沒法保證yield()達到讓步目的,由於讓步的線程還有可能被線程調度程序再次選中。
結論:yield()從未致使線程轉到等待/睡眠/阻塞狀態。在大多數狀況下,yield()將致使線程從運行狀態轉到可運行狀態,但有可能沒有效果。
四、join()方法
Thread的非靜態方法join()讓一個線程B「加入」到另一個線程A的尾部。在A執行完畢以前,B不能工做。例如:
Thread t = new MyThread();
t.start();
t.join();
另外,join()方法還有帶超時限制的重載版本。例如t.join(5000);則讓線程等待5000毫秒,若是超過這個時間,則中止等待,變爲可運行狀態。
線程的加入join()對線程棧致使的結果是線程棧發生了變化,固然這些變化都是瞬時的。下面給示意圖:
小結
到目前位置,介紹了線程離開運行狀態的3種方法:
一、調用Thread.sleep():使當前線程睡眠至少多少毫秒(儘管它可能在指定的時間以前被中斷)。
二、調用Thread.yield():不能保障太多事情,儘管一般它會讓當前運行線程回到可運行性狀態,使得有相同優先級的線程有機會執行。
三、調用join()方法:保證當前線程中止執行,直到該線程所加入的線程完成爲止。然而,若是它加入的線程沒有存活,則當前線程不須要中止。
除了以上三種方式外,還有下面幾種特殊狀況可能使線程離開運行狀態:
一、線程的run()方法完成。
二、在對象上調用wait()方法(不是在線程上調用)。
三、線程不能在對象上得到鎖定,它正試圖運行該對象的方法代碼。
四、線程調度程序能夠決定將當前運行狀態移動到可運行狀態,以便讓另外一個線程得到運行機會,而不須要任何理由。
Java線程:線程的同步與鎖
1、同步問題提出
線程的同步是爲了防止多個線程訪問一個數據對象時,對數據形成的破壞。
例如:兩個線程ThreadA、ThreadB都操做同一個對象Foo對象,並修改Foo對象上的數據。
publicclass Foo {
privateint x = 100;
publicint getX() {
return x;
}
publicint fix(int y) {
x = x - y;
return x;
}
}
publicclass MyRunnableimplements Runnable {
private Foo foo =new Foo();
publicstaticvoid 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();
}
publicvoid 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());
}
}
publicint 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關鍵字同步方法或代碼。
2、同步和鎖定
一、鎖的原理
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;
}
}
效果是徹底同樣的。
3、靜態方法同步
要同步靜態方法,須要一個用於整個類對象的鎖,這個對象是就是這個類(XXX.class)。
例如:
public static synchronized int setName(String name){
Xxx.name = name;
}
等價於
public static int setName(String name){
synchronized(Xxx.class){
Xxx.name = name;
}
}
4、若是線程不能不能得到鎖會怎麼樣
若是線程試圖進入同步方法,而其鎖已經被佔用,則線程在該對象上被阻塞。實質上,線程進入該對象的的一種池中,必須在哪裏等待,直到其鎖被釋放,該線程再次變爲可運行或運行爲止。
當考慮阻塞時,必定要注意哪一個對象正被用於鎖定:
一、調用同一個對象中非靜態同步方法的線程將彼此阻塞。若是是不一樣對象,則每一個線程有本身的對象的鎖,線程間彼此互不干預。
二、調用同一個類中的靜態同步方法的線程將彼此阻塞,它們都是鎖定在相同的Class對象上。
三、靜態同步方法和非靜態同步方法將永遠不會彼此阻塞,由於靜態方法鎖定在Class對象上,非靜態方法鎖定在該類的對象上。
四、對於同步代碼塊,要看清楚什麼對象已經用於鎖定(synchronized後面括號的內容)。在同一個對象上進行同步的線程將彼此阻塞,在不一樣對象上鎖定的線程將永遠不會彼此阻塞。
5、什麼時候須要同步
在多個線程同時訪問互斥(可交換)數據時,應該同步以保護數據,確保兩個線程不會同時修改更改它。
對於非靜態字段中可更改的數據,一般使用非靜態方法訪問。
對於靜態字段中可更改的數據,一般使用靜態方法訪問。
若是須要在非靜態方法中使用靜態字段,或者在靜態字段中調用非靜態方法,問題將變得很是複雜。已經超出SJCP考試範圍了。
6、線程安全類
當一個類已經很好的同步以保護它的數據時,這個類就稱爲「線程安全的」。
即便是線程安全類,也應該特別當心,由於操做的線程是間仍然不必定安全。
舉個形象的例子,好比一個集合是線程安全的,有兩個線程在操做同一個集合對象,當第一個線程查詢集合非空後,刪除集合中全部元素的時候。第二個線程也來執行與第一個線程相同的操做,也許在第一個線程查詢後,第二個線程也查詢出集合非空,可是當第一個執行清除後,第二個再執行刪除顯然是不對的,由於此時集合已經爲空了。
看個代碼:
publicclass NameList {
private List nameList = Collections.synchronizedList(new LinkedList());
publicvoid add(String name) {
nameList.add(name);
}
public String removeFirst() {
if (nameList.size() > 0) {
return (String) nameList.remove(0);
} else {
returnnull;
}
}
}
publicclass Test {
publicstaticvoid main(String[] args) {
final NameList nl =new NameList();
nl.add("aaa");
class NameDropperextends Thread{
publicvoid 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上面作一個同步。改寫後的代碼以下:
publicclass NameList {
private List nameList = Collections.synchronizedList(new LinkedList());
publicsynchronizedvoid add(String name) {
nameList.add(name);
}
publicsynchronized String removeFirst() {
if (nameList.size() > 0) {
return (String) nameList.remove(0);
} else {
returnnull;
}
}
}
這樣,當一個線程訪問其中一個同步方法時,其餘線程只有等待。
7、線程死鎖
死鎖對Java程序來講,是很複雜的,也很難發現問題。當兩個線程被阻塞,每一個線程在等待另外一個線程時就發生死鎖。
仍是看一個比較直觀的死鎖例子:
publicclass DeadlockRisk {
privatestaticclass Resource {
publicint value;
}
private Resource resourceA =new Resource();
private Resource resourceB =new Resource();
publicint read() {
synchronized (resourceA) {
synchronized (resourceB) {
return resourceB.value + resourceA.value;
}
}
}
publicvoid write(int a,int b) {
synchronized (resourceB) {
synchronized (resourceA) {
resourceA.value = a;
resourceB.value = b;
}
}
}
}
假設read()方法由一個線程啓動,write()方法由另一個線程啓動。讀線程將擁有resourceA鎖,寫線程將擁有resourceB鎖,二者都堅持等待的話就出現死鎖。
實際上,上面這個例子發生死鎖的機率很小。由於在代碼內的某個點,CPU必須從讀線程切換到寫線程,因此,死鎖基本上不能發生。
可是,不管代碼中發生死鎖的機率有多小,一旦發生死鎖,程序就死掉。有一些設計方法能幫助避免死鎖,包括始終按照預約義的順序獲取鎖這一策略。已經超出SCJP的考試範圍。
8、線程同步小結
一、線程同步的目的是爲了保護多個線程反問一個資源時對資源的破壞。
二、線程同步方法是經過鎖來實現,每一個對象都有切僅有一個鎖,這個鎖與一個特定的對象關聯,線程一旦獲取了對象鎖,其餘訪問該對象的線程就沒法再訪問該對象的其餘同步方法。
三、對於靜態同步方法,鎖是針對這個類的,鎖對象是該類的Class對象。靜態和非靜態方法的鎖互不干預。一個線程得到鎖,當在一個同步方法中訪問另外對象上的同步方法時,會獲取這兩個對象鎖。
四、對於同步,要時刻清醒在哪一個對象上同步,這是關鍵。
五、編寫線程安全的類,須要時刻注意對多個線程競爭訪問資源的邏輯和安全作出正確的判斷,對「原子」操做作出分析,並保證原子操做期間別的線程沒法訪問競爭資源。
六、當多個線程等待一個對象鎖時,沒有獲取到鎖的線程將發生阻塞。
七、死鎖是線程間相互等待鎖鎖形成的,在實際中發生的機率很是的小。真讓你寫個死鎖程序,不必定好使,呵呵。可是,一旦程序發生死鎖,程序將死掉。
Java線程:線程的交互
線程交互是比較複雜的問題,SCJP要求不很基礎:給定一個場景,編寫代碼來恰當使用等待、通知和通知全部線程。
1、線程交互的基礎知識
SCJP所要求的線程交互知識點須要從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
*/
publicclass ThreadA {
publicstaticvoid 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
*/
publicclass ThreadBextends Thread {
int total;
publicvoid 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()並不意味着這時該鎖變得可用。
2、多個線程在等待一個對象鎖時候使用notifyAll()
在多數狀況下,最好通知等待某個對象的全部線程。若是這樣作,能夠在對象上使用notifyAll()讓全部在此對象上等待的線程衝出等待區,返回到可運行狀態。
下面給個例子:
/**
* 計算線程
*
* @author leizhimin 2008-9-20 11:15:46
*/
publicclass Calculatorextends Thread {
int total;
publicvoid run() {
synchronized (this) {
for (int i = 0; i < 101; i++) {
total += i;
}
}
//通知全部在此對象上等待的線程
notifyAll();
}
}
/**
* 獲取計算結果並輸出
*
* @author leizhimin 2008-9-20 11:15:22
*/
publicclass ReaderResultextends Thread {
Calculator c;
public ReaderResult(Calculator c) {
this.c = c;
}
publicvoid run() {
synchronized (c) {
try {
System.out.println(Thread.currentThread() + "等待計算結果。。。");
c.wait();
} catch (InterruptedException e) {
e.printStackTrace();
}
System.out.println(Thread.currentThread() + "計算結果爲:" + c.total);
}
}
publicstaticvoid 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線程:線程的調度-休眠
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
*/
publicclass Test {
publicstaticvoid main(String[] args) {
Thread t1 = new MyThread1();
Thread t2 = new Thread(new MyRunnable());
t1.start();
t2.start();
}
}
class MyThread1 extends Thread {
publicvoid run() {
for (int i = 0; i < 3; i++) {
System.out.println("線程1第" + i + "次執行!");
try {
Thread.sleep(50);
} catch (InterruptedException e) {
e.printStackTrace();
}
}
}
}
class MyRunnableimplements Runnable {
publicvoid run() {
for (int i = 0; i < 3; i++) {
System.out.println("線程2第" + i + "次執行!");
try {
Thread.sleep(50);
} catch (InterruptedException e) {
e.printStackTrace();
}
}
}
}
線程2第0次執行!
線程1第0次執行!
線程1第1次執行!
線程2第1次執行!
線程1第2次執行!
線程2第2次執行!
Process finished with exit code 0
從上面的結果輸出能夠看出,沒法精準保證線程執行次序。
Java線程:線程的調度-優先級
與線程休眠相似,線程的優先級仍然沒法保障線程的執行次序。只不過,優先級高的線程獲取CPU資源的機率較大,優先級低的並不是沒機會執行。
線程的優先級用1-10之間的整數表示,數值越大優先級越高,默認的優先級爲5。
在一個線程中開啓另一個新線程,則新開線程稱爲該線程的子線程,子線程初始優先級與父線程相同。
/**
* Java線程:線程的調度-優先級
*
* @author leizhimin 2009-11-4 9:02:40
*/
publicclass Test {
publicstaticvoid 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 {
publicvoid run() {
for (int i = 0; i < 10; i++) {
System.out.println("線程1第" + i + "次執行!");
try {
Thread.sleep(100);
} catch (InterruptedException e) {
e.printStackTrace();
}
}
}
}
class MyRunnableimplements Runnable {
publicvoid run() {
for (int i = 0; i < 10; i++) {
System.out.println("線程2第" + i + "次執行!");
try {
Thread.sleep(100);
} catch (InterruptedException e) {
e.printStackTrace();
}
}
}
}
線程1第0次執行!
線程2第0次執行!
線程2第1次執行!
線程1第1次執行!
線程2第2次執行!
線程1第2次執行!
線程1第3次執行!
線程2第3次執行!
線程2第4次執行!
線程1第4次執行!
線程1第5次執行!
線程2第5次執行!
線程1第6次執行!
線程2第6次執行!
線程1第7次執行!
線程2第7次執行!
線程1第8次執行!
線程2第8次執行!
線程1第9次執行!
線程2第9次執行!
Process finished with exit code 0
Java線程:線程的調度-讓步
線程的讓步含義就是使當前運行着線程讓出CPU資源,可是然給誰不知道,僅僅是讓出,線程狀態回到可運行狀態。
線程的讓步使用Thread.yield()方法,yield()爲靜態方法,功能是暫停當前正在執行的線程對象,並執行其餘線程。
/**
* Java線程:線程的調度-讓步
*
* @author leizhimin 2009-11-4 9:02:40
*/
publicclass Test {
publicstaticvoid main(String[] args) {
Thread t1 = new MyThread1();
Thread t2 = new Thread(new MyRunnable());
t2.start();
t1.start();
}
}
class MyThread1 extends Thread {
publicvoid run() {
for (int i = 0; i < 10; i++) {
System.out.println("線程1第" + i + "次執行!");
}
}
}
class MyRunnableimplements Runnable {
publicvoid run() {
for (int i = 0; i < 10; i++) {
System.out.println("線程2第" + i + "次執行!");
Thread.yield();
}
}
}
線程2第0次執行!
線程2第1次執行!
線程2第2次執行!
線程2第3次執行!
線程1第0次執行!
線程1第1次執行!
線程1第2次執行!
線程1第3次執行!
線程1第4次執行!
線程1第5次執行!
線程1第6次執行!
線程1第7次執行!
線程1第8次執行!
線程1第9次執行!
線程2第4次執行!
線程2第5次執行!
線程2第6次執行!
線程2第7次執行!
線程2第8次執行!
線程2第9次執行!
Process finished with exit code 0
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
*/
publicclass Test {
publicstaticvoid 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 {
publicvoid run() {
for (int i = 0; i < 10; i++) {
System.out.println("線程1第" + i + "次執行!");
}
}
}
主線程第0次執行!
主線程第1次執行!
主線程第2次執行!
線程1第0次執行!
主線程第3次執行!
線程1第1次執行!
線程1第2次執行!
線程1第3次執行!
線程1第4次執行!
線程1第5次執行!
線程1第6次執行!
線程1第7次執行!
線程1第8次執行!
線程1第9次執行!
主線程第4次執行!
主線程第5次執行!
主線程第6次執行!
主線程第7次執行!
主線程第8次執行!
主線程第9次執行!
主線程第10次執行!
主線程第11次執行!
主線程第12次執行!
主線程第13次執行!
主線程第14次執行!
主線程第15次執行!
主線程第16次執行!
主線程第17次執行!
主線程第18次執行!
主線程第19次執行!
Process finished with exit code 0
Java線程:線程的調度-守護線程
守護線程與普通線程寫法上基本麼啥區別,調用線程對象的方法setDaemon(true),則能夠將其設置爲守護線程。
守護線程使用的狀況較少,但並不是無用,舉例來講,JVM的垃圾回收、內存管理等線程都是守護線程。還有就是在作數據庫應用時候,使用的數據庫鏈接池,鏈接池自己也包含着不少後臺線程,監控鏈接個數、超時時間、狀態等等。
setDaemon方法的詳細說明:
publicfinalvoid setDaemon(boolean on)將該線程標記爲守護線程或用戶線程。當正在運行的線程都是守護線程時,Java虛擬機退出。
該方法必須在啓動線程前調用。
該方法首先調用該線程的 checkAccess方法,且不帶任何參數。這可能拋出 SecurityException(在當前線程中)。
參數:
on - 若是爲true,則將該線程標記爲守護線程。
拋出:
IllegalThreadStateException - 若是該線程處於活動狀態。
SecurityException - 若是當前線程沒法修改該線程。
另請參見:
isDaemon(), checkAccess()
/**
* Java線程:線程的調度-守護線程
*
* @author leizhimin 2009-11-4 9:02:40
*/
publicclass Test {
publicstaticvoid main(String[] args) {
Thread t1 = new MyCommon();
Thread t2 = new Thread(new MyDaemon());
t2.setDaemon(true); //設置爲守護線程
t2.start();
t1.start();
}
}
class MyCommon extends Thread {
publicvoid 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 {
publicvoid run() {
for (long i = 0; i < 9999999L; i++) {
System.out.println("後臺線程第" + i +"次執行!");
try {
Thread.sleep(7);
} catch (InterruptedException e) {
e.printStackTrace();
}
}
}
}
後臺線程第0次執行!
線程1第0次執行!
線程1第1次執行!
後臺線程第1次執行!
後臺線程第2次執行!
線程1第2次執行!
線程1第3次執行!
後臺線程第3次執行!
線程1第4次執行!
後臺線程第4次執行!
後臺線程第5次執行!
後臺線程第6次執行!
後臺線程第7次執行!
Process finished with exit code 0
從上面的執行結果能夠看出:
前臺線程是保證執行完畢的,後臺線程尚未執行完畢就退出了。
實際上:JRE判斷程序是否執行結束的標準是全部的前臺執線程行完畢了,而無論後臺線程的狀態,所以,在使用後臺縣城時候必定要注意這個問題。
Java線程:線程的同步-同步方法
線程的同步是保證多線程安全訪問競爭資源的一種手段。
線程的同步是Java多線程編程的難點,每每開發者搞不清楚什麼是競爭資源、何時須要考慮同步,怎麼同步等等問題,固然,這些問題沒有很明確的答案,但有些原則問題須要考慮,是否有競爭資源被同時改動的問題?
在本文以前,請參閱《Java線程:線程的同步與鎖》,本文是在此基礎上所寫的。
對於同步,在具體的Java代碼中須要完成一下兩個操做:
把競爭訪問的資源標識爲private;
同步哪些修改變量的代碼,使用synchronized關鍵字同步方法或代碼。
固然這不是惟一控制併發安全的途徑。
synchronized關鍵字使用說明
synchronized只能標記非抽象的方法,不能標識成員變量。
爲了演示同步方法的使用,構建了一個信用卡帳戶,起初信用額爲100w,而後模擬透支、存款等多個操做。顯然銀行帳戶User對象是個競爭資源,而多個併發操做的是帳戶方法oper(int x),固然應該在此方法上加上同步,並將帳戶的餘額設爲私有變量,禁止直接訪問。
/**
* Java線程:線程的同步
*
* @author leizhimin 2009-11-4 11:23:32
*/
publicclass Test {
publicstaticvoid 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;
privateint y = 0;
MyThread(String name, User u, int y) {
super(name);
this.u = u;
this.y = y;
}
publicvoid run() {
u.oper(y);
}
}
class User {
private String code;
privateint cash;
User(String code, int cash) {
this.code = code;
this.cash = cash;
}
public String getCode() {
return code;
}
publicvoid setCode(String code) {
this.code = code;
}
/**
* 業務方法
* @param x 添加x萬元
*/
publicsynchronizedvoid 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」,當前用戶帳戶餘額爲:3
Process finished with exit code 0
反面教材,不一樣步的狀況,也就是去掉oper(int x)方法的synchronized修飾符,而後運行程序,結果以下:
線程A運行結束,增長「20」,當前用戶帳戶餘額爲:61
線程D運行結束,增長「-30」,當前用戶帳戶餘額爲:63
線程B運行結束,增長「-60」,當前用戶帳戶餘額爲:3
線程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
*/
publicclass Test {
publicstaticvoid 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;
privateint y = 0;
MyThread(String name, User u, int y) {
super(name);
this.u = u;
this.y = y;
}
publicvoid run() {
u.oper(y);
}
}
class User {
private String code;
privateint cash;
User(String code, int cash) {
this.code = code;
this.cash = cash;
}
public String getCode() {
return code;
}
publicvoid setCode(String code) {
this.code = code;
}
/**
* 業務方法
*
* @param x 添加x萬元
*/
publicvoid 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」,當前用戶帳戶餘額爲:3
Process finished with exit code 0
注意:
在使用synchronized關鍵字時候,應該儘量避免在synchronized方法或synchronized塊中使用sleep或者yield方法,由於synchronized程序塊佔有着對象鎖,你休息那麼其餘的線程只能一邊等着你醒來執行完了才能執行。不但嚴重影響效率,也不合邏輯。
一樣,在同步程序塊內調用yeild方法讓出CPU資源也沒有意義,由於你佔用着鎖,其餘互斥線程仍是沒法訪問同步程序塊。固然與同步程序塊無關的線程能夠得到更多的執行時間。
Java線程:併發協做-生產者消費者模型
對於多線程程序來講,無論任何編程語言,生產者和消費者模型都是最經典的。就像學習每一門編程語言同樣,Hello World!都是最經典的例子。
實際上,準確說應該是「生產者-消費者-倉儲」模型,離開了倉儲,生產者消費者模型就顯得沒有說服力了。
對於此模型,應該明確一下幾點:
一、生產者僅僅在倉儲未滿時候生產,倉滿則中止生產。
二、消費者僅僅在倉儲有產品時候才能消費,倉空則等待。
三、當消費者發現倉儲沒產品可消費時候會通知生產者生產。
四、生產者在生產出可消費產品時候,應該通知等待的消費者去消費。
此模型將要結合java.lang.Object的wait與notify、notifyAll方法來實現以上的需求。這是很是重要的。
/**
* Java線程:併發協做-生產者消費者模型
*
* @author leizhimin 2009-11-4 14:54:36
*/
publicclass Test {
publicstaticvoid 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 {
publicstaticfinalint max_size = 100;//最大庫存量
publicint curnum; //當前庫存量
Godown() {
}
Godown(int curnum) {
this.curnum = curnum;
}
/**
* 生產指定數量的產品
*
* @param neednum
*/
publicsynchronizedvoid 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
*/
publicsynchronizedvoid 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 {
privateint neednum; //生產產品的數量
private Godown godown; //倉庫
Producer(int neednum, Godown godown) {
this.neednum = neednum;
this.godown = godown;
}
publicvoid run() {
//生產指定數量的產品
godown.produce(neednum);
}
}
/**
* 消費者
*/
class Consumer extends Thread {
privateint neednum; //生產產品的數量
private Godown godown; //倉庫
Consumer(int neednum, Godown godown) {
this.neednum = neednum;
this.godown = godown;
}
publicvoid run() {
//消費指定數量的產品
godown.consume(neednum);
}
}
已經生產了10個產品,現倉儲量爲40
已經生產了10個產品,現倉儲量爲50
已經消費了50個產品,現倉儲量爲0
已經生產了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
*/
publicclass Test {
publicstaticvoid 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;
privateint a, b;
MyThread(DeadlockRisk dead, int a,int b) {
this.dead = dead;
this.a = a;
this.b = b;
}
@Override
publicvoid run() {
dead.read();
dead.write(a, b);
}
}
class DeadlockRisk {
privatestaticclass Resource {
publicint value;
}
private Resource resourceA =new Resource();
private Resource resourceB =new Resource();
publicint 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;
}
}
}
publicvoid 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線程:volatile關鍵字
Java? 語言包含兩種內在的同步機制:同步塊(或方法)和 volatile變量。這兩種機制的提出都是爲了實現代碼線程的安全性。其中 Volatile變量的同步性較差(但有時它更簡單而且開銷更低),並且其使用也更容易出錯。
談及到volatile關鍵字,不得不提的一篇文章是:《Java理論與實踐:正確使用 Volatile 變量》,這篇文章對volatile關鍵字的用法作了至關精闢的闡述。
之因此要單獨提出volatile這個不經常使用的關鍵字緣由是這個關鍵字在高性能的多線程程序中也有很重要的用途,只是這個關鍵字用很差會出不少問題。
首先考慮一個問題,爲何變量須要volatile來修飾呢?
要搞清楚這個問題,首先應該明白計算機內部都作什麼了。好比作了一個i++操做,計算機內部作了三次處理:讀取-修改-寫入。
一樣,對於一個long型數據,作了個賦值操做,在32系統下須要通過兩步才能完成,先修改低32位,而後修改高32位。
假想一下,當將以上的操做放到一個多線程環境下操做時候,有可能出現的問題,是這些步驟執行了一部分,而另一個線程就已經引用了變量值,這樣就致使了讀取髒數據的問題。
經過這個設想,就不難理解volatile關鍵字了。
volatile能夠用在任何變量前面,但不能用於final變量前面,由於final型的變量是禁止修改的。也不存在線程安全的問題。
更多的內容,請參看::《Java理論與實踐:正確使用 Volatile 變量》一文,寫得很好。
Java線程:新特徵-線程池
Sun在Java5中,對Java線程的類庫作了大量的擴展,其中線程池就是Java5的新特徵之一,除了線程池以外,還有不少多線程相關的內容,爲多線程的編程帶來了極大便利。爲了編寫高效穩定可靠的多線程程序,線程部分的新增內容顯得尤其重要。
有關Java5線程新特徵的內容所有在java.util.concurrent下面,裏面包含數目衆多的接口和類,熟悉這部分API特徵是一項艱難的學習過程。目前有關這方面的資料和書籍都少之又少,大所屬介紹線程方面書籍還停留在java5以前的知識層面上。
固然新特徵對作多線程程序沒有必須的關係,在java5以前通用能夠寫出很優秀的多線程程序。只是代價不同而已。
線程池的基本思想仍是一種對象池的思想,開闢一塊內存空間,裏面存放了衆多(未死亡)的線程,池中線程執行調度由池管理器來處理。當有線程任務時,從池中取一個,執行完成後線程對象歸池,這樣能夠避免反覆建立線程對象所帶來的性能開銷,節省了系統的資源。
在Java5以前,要實現一個線程池是至關有難度的,如今Java5爲咱們作好了一切,咱們只須要按照提供的API來使用,便可享受線程池帶來的極大便利。
Java5的線程池分好多種:固定尺寸的線程池、可變尺寸鏈接池、。
在使用線程池以前,必須知道如何去建立一個線程池,在Java5中,須要瞭解的是java.util.concurrent.Executors類的API,這個類提供大量建立鏈接池的靜態方法,是必須掌握的。
1、固定大小的線程池
import java.util.concurrent.Executors;
import java.util.concurrent.ExecutorService;
/**
* Java線程:線程池-
*
* @author Administrator 2009-11-4 23:30:44
*/
publicclass Test {
publicstaticvoid 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
publicvoid 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
2、單任務線程池
在上例的基礎上改一行建立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
對於以上兩種鏈接池,大小都是固定的,當要加入的池的線程(或者任務)超過池最大尺寸時候,則入此線程池須要排隊等待。
一旦池中有線程完畢,則排隊等待的某個線程會入池執行。
3、可變尺寸的線程池
與上面的相似,只是改動下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
4、延遲鏈接池
import java.util.concurrent.Executors;
import java.util.concurrent.ScheduledExecutorService;
import java.util.concurrent.TimeUnit;
/**
* Java線程:線程池-
*
* @author Administrator 2009-11-4 23:30:44
*/
publicclass Test {
publicstaticvoid 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
publicvoid 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
5、單任務延遲鏈接池
在四代碼基礎上,作改動
//建立一個單線程執行程序,它可安排在給定延遲後運行命令或者按期地執行。
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
6、自定義線程池
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
*/
publicclass Test {
publicstaticvoid 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
publicvoid 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線程:新特徵-有返回值的線程
在Java5以前,線程是沒有返回值的,經常爲了「有」返回值,破費周折,並且代碼很很差寫。或者乾脆繞過這道坎,走別的路了。
如今Java終於有可返回值的任務(也能夠叫作線程)了。
可返回值的任務必須實現Callable接口,相似的,無返回值的任務必須Runnable接口。
執行Callable任務後,能夠獲取一個Future的對象,在該對象上調用get就能夠獲取到Callable任務返回的Object了。
下面是個很簡單的例子:
import java.util.concurrent.*;
/**
* Java線程:有返回值的線程
*
* @author Administrator 2009-11-5 0:41:50
*/
publicclass Test {
publicstaticvoid 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 MyCallableimplements 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中,專門提供了鎖對象,利用鎖能夠方便的實現資源的封鎖,用來控制對競爭資源併發訪問的控制,這些內容主要集中在java.util.concurrent.locks包下面,裏面有三個重要的接口Condition、Lock、ReadWriteLock。
Condition
Condition將Object監視器方法(wait、notify和 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
*/
publicclass Test {
publicstaticvoid 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; //所要操做的帳戶
privateint 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;
}
publicvoid 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; //帳號
privateint cash; //帳戶餘額
MyCount(String oid, int cash) {
this.oid = oid;
this.cash = cash;
}
public String getOid() {
return oid;
}
publicvoid setOid(String oid) {
this.oid = oid;
}
publicint getCash() {
return cash;
}
publicvoid 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
*/
publicclass Test {
publicstaticvoid 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; //所要操做的帳戶
privateint iocash; //操做的金額,固然有正負之分了
private ReadWriteLock myLock; //執行操做所需的鎖對象
privateboolean 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;
}
publicvoid 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; //帳號
privateint cash; //帳戶餘額
MyCount(String oid, int cash) {
this.oid = oid;
this.cash = cash;
}
public String getOid() {
return oid;
}
publicvoid setOid(String oid) {
this.oid = oid;
}
publicint getCash() {
return cash;
}
publicvoid 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線程:新特徵-信號量
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
*/
publicclass Test {
publicstaticvoid 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;
}
publicvoid setSp(Semaphore sp) {
this.sp = sp;
}
}
class MyThread extends Thread {
private String threadname; //線程的名稱
private MyPool pool; //自定義池
privateint x; //申請信號量的大小
MyThread(String threadname, MyPool pool, int x) {
this.threadname = threadname;
this.pool = pool;
this.x = x;
}
publicvoid 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定義了阻塞隊列的接口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
*/
publicclass Test {
publicstaticvoid 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
*/
publicclass Test {
publicstaticvoid 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
從上面結果能夠看到,程序並沒結束,二是阻塞住了,緣由是棧已經滿了,後面追加元素的操做都被阻塞了。
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
*/
publicclass Test {
publicstaticvoid 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 SaveThreadextends Thread {
private String name; //操做人
private MyCount myCount; //帳戶
privateint x; //存款金額
SaveThread(String name, MyCount myCount, int x) {
this.name = name;
this.myCount = myCount;
this.x = x;
}
publicvoid run() {
myCount.saving(x, name);
}
}
/**
* 取款線程類
*/
class DrawThreadextends Thread {
private String name; //操做人
private MyCount myCount; //帳戶
privateint x; //存款金額
DrawThread(String name, MyCount myCount, int x) {
this.name = name;
this.myCount = myCount;
this.x = x;
}
publicvoid run() {
myCount.drawing(x, name);
}
}
/**
* 普通銀行帳戶,不可透支
*/
class MyCount {
private String oid; //帳號
privateint 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 操做人
*/
publicvoid 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 操做人
*/
publicvoid 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
*/
publicclass Test {
publicstaticvoid 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 SaveThreadextends Thread {
private String name; //操做人
private MyCount myCount; //帳戶
privateint x; //存款金額
SaveThread(String name, MyCount myCount, int x) {
this.name = name;
this.myCount = myCount;
this.x = x;
}
publicvoid run() {
myCount.saving(x, name);
}
}
/**
* 取款線程類
*/
class DrawThreadextends Thread {
private String name; //操做人
private MyCount myCount; //帳戶
privateint x; //存款金額
DrawThread(String name, MyCount myCount, int x) {
this.name = name;
this.myCount = myCount;
this.x = x;
}
publicvoid run() {
myCount.drawing(x, name);
}
}
/**
* 普通銀行帳戶,不可透支
*/
class MyCount {
private String oid; //帳號
privateint cash; //帳戶餘額
MyCount(String oid, int cash) {
this.oid = oid;
this.cash = cash;
}
/**
* 存款
*
* @param x 操做金額
* @param name 操做人
*/
publicsynchronizedvoid saving(int x, String name) {
if (x > 0) {
cash += x; //存款
System.out.println(name + "存款" + x +",當前餘額爲" + cash);
}
notifyAll(); //喚醒全部等待線程。
}
/**
* 取款
*
* @param x 操做金額
* @param name 操做人
*/
publicsynchronizedvoid 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
*/
publicclass Test {
publicstaticvoid 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 SaveThreadextends Thread {
private String name; //操做人
private MyCount myCount; //帳戶
privateint x; //存款金額
SaveThread(String name, MyCount myCount, int x) {
this.name = name;
this.myCount = myCount;
this.x = x;
}
publicvoid run() {
myCount.saving(x, name);
}
}
/**
* 取款線程類
*/
class DrawThreadextends Thread {
private String name; //操做人
private MyCount myCount; //帳戶
privateint x; //存款金額
DrawThread(String name, MyCount myCount, int x) {
this.name = name;
this.myCount = myCount;
this.x = x;
}
publicvoid run() {
myCount.drawing(x, name);
}
}
/**
* 普通銀行帳戶,不可透支
*/
class MyCount {
private String oid; //帳號
privateint cash; //帳戶餘額
MyCount(String oid, int cash) {
this.oid = oid;
this.cash = cash;
}
/**
* 存款
*
* @param x 操做金額
* @param name 操做人
*/
publicvoid saving(int x, String name) {
if (x > 0) {
synchronized (this) {
cash += x; //存款
System.out.println(name + "存款" + x +",當前餘額爲" + cash);
notifyAll(); //喚醒全部等待線程。
}
}
}
/**
* 取款
*
* @param x 操做金額
* @param name 操做人
*/
publicsynchronizedvoid 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線程:新特徵-原子量
所謂的原子量即操做變量的操做是「原子的」,該操做不可再分,所以是線程安全的。
爲什麼要使用原子變量呢,緣由是多個線程對單個變量操做也會引發一些問題。在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
*/
publicclass Test {
publicstaticvoid 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 MyRunnableimplements Runnable {
privatestatic AtomicLong aLong =new AtomicLong(10000); //原子量,每一個線程均可以自由操做
private String name; //操做人
privateint x; //操做數額
MyRunnable(String name, int x) {
this.name = name;
this.x = x;
}
publicvoid 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
*/
publicclass Test {
publicstaticvoid 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 MyRunnableimplements Runnable {
privatestatic AtomicLong aLong =new AtomicLong(10000); //原子量,每一個線程均可以自由操做
private String name; //操做人
privateint x; //操做數額
private Lock lock;
MyRunnable(String name, int x,Lock lock) {
this.name = name;
this.x = x;
this.lock = lock;
}
publicvoid 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
*/
publicclass Test {
publicstaticvoid 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 {
publicvoid 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;
}
publicvoid 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]開始執行完成了,並通知障礙器已經完成!
>>>>主任務執行了!<<<<
前端