Java线程详解
本文轉(zhuǎn)載自http://www.cnblogs.com/riskyer/p/3263032.html
Java線程:概念與原理
一、操作系統(tǒng)中線程和進(jìn)程的概念
現(xiàn)在的操作系統(tǒng)是多任務(wù)操作系統(tǒng)。多線程是實現(xiàn)多任務(wù)的一種方式。
進(jìn)程是指一個內(nèi)存中運行的應(yīng)用程序,每個進(jìn)程都有自己獨立的一塊內(nèi)存空間,一個進(jìn)程中可以啟動多個線程。比如在Windows系統(tǒng)中,一個運行的exe就是一個進(jìn)程。
線程是指進(jìn)程中的一個執(zhí)行流程,一個進(jìn)程中可以運行多個線程。比如java.exe進(jìn)程中可以運行很多線程。線程總是屬于某個進(jìn)程,進(jìn)程中的多個線程共享進(jìn)程的內(nèi)存。
“同時”執(zhí)行是人的感覺,在線程之間實際上輪換執(zhí)行。
二、Java中的線程
在Java中,“線程”指兩件不同的事情:
1、java.lang.Thread類的一個實例;
2、線程的執(zhí)行。
使用java.lang.Thread類或者java.lang.Runnable接口編寫代碼來定義、實例化和啟動新線程。
一個Thread類實例只是一個對象,像Java中的任何其他對象一樣,具有變量和方法,生死于堆上。
Java中,每個線程都有一個調(diào)用棧,即使不在程序中創(chuàng)建任何新的線程,線程也在后臺運行著。
一個Java應(yīng)用總是從main()方法開始運行,mian()方法運行在一個線程內(nèi),它被稱為主線程。
一旦創(chuàng)建一個新的線程,就產(chǎn)生一個新的調(diào)用棧。
線程總體分兩類:用戶線程和守候線程。當(dāng)所有用戶線程執(zhí)行完畢的時候,JVM自動關(guān)閉。但是守候線程卻不獨立于JVM,守候線程一般是由操作系統(tǒng)或者用戶自己創(chuàng)建的。
Java線程:創(chuàng)建與啟動
一、定義線程
1、擴(kuò)展java.lang.Thread類。
此類中有個run()方法,應(yīng)該注意其用法:public void?run()
如果該線程是使用獨立的Runnable運行對象構(gòu)造的,則調(diào)用該Runnable對象的run方法;否則,該方法不執(zhí)行任何操作并返回。
Thread的子類應(yīng)該重寫該方法。
2、實現(xiàn)java.lang.Runnable接口。void?run()
使用實現(xiàn)接口Runnable的對象創(chuàng)建一個線程時,啟動該線程將導(dǎo)致在獨立執(zhí)行的線程中調(diào)用對象的run方法。方法run的常規(guī)協(xié)定是,它可能執(zhí)行任何所需的操作。
二、實例化線程
1、如果是擴(kuò)展java.lang.Thread類的線程,則直接new即可。
2、如果是實現(xiàn)了java.lang.Runnable接口的類,則用Thread的構(gòu)造方法:
- 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)
三、啟動線程
在線程的Thread對象上調(diào)用start()方法,而不是run()或者別的方法。在調(diào)用start()方法之前:線程處于新狀態(tài)中,新狀態(tài)指有一個Thread對象,但還沒有一個真正的線程。
在調(diào)用start()方法之后:發(fā)生了一系列復(fù)雜的事情:
注意:對Java來說,run()方法沒有任何特別之處。像main()方法一樣,它只是新線程知道調(diào)用的方法名稱(和簽名)。因此,在Runnable上或者Thread上調(diào)用run方法是合法的。但并不啟動新的線程。
四、例子
1、實現(xiàn)Runnable接口的多線程例子
1 /** 2 * 實現(xiàn)Runnable接口的類 3 * 4 * @author leizhimin 2008-9-13 18:12:10 5 */ 6 publi cclass DoSomething implements Runnable { 7 private String name; 8 9 public DoSomething(String name) { 10 this.name = name; 11 } 12 13 public void run() { 14 for (int i = 0; i < 5; i++) { 15 for (long k = 0; k < 100000000; k++) ; 16 System.out.println(name + ": " + i); 17 } 18 } 19 } 1 /** 2 * 測試Runnable類實現(xiàn)的多線程程序 3 * 4 * @author leizhimin 2008-9-13 18:15:02 5 */ 6 public class TestRunnable { 7 public static void main(String[] args) { 8 DoSomething ds1 = new DoSomething("阿三"); 9 DoSomething ds2 = new DoSomething("李四"); 10 11 Thread t1 = new Thread(ds1); 12 Thread t2 = new Thread(ds2); 13 14 t1.start(); 15 t2.start(); 16 } 17 }執(zhí)行結(jié)果:
李四: 0
阿三: 0?
李四: 1?
阿三: 1?
李四: 2?
李四: 3?
阿三: 2?
李四: 4?
阿三: 3?
阿三: 4?
Process finished with exit code 0
2、擴(kuò)展Thread類實現(xiàn)的多線程例子
1 /** 2 * 測試擴(kuò)展Thread類實現(xiàn)的多線程程序 3 * 4 * @author leizhimin 2008-9-13 18:22:13 5 */ 6 public class TestThreadextends Thread{ 7 public TestThread(String name) { 8 super(name); 9 } 10 11 public void run() { 12 for(int i = 0;i<5;i++){ 13 for(long k= 0; k <100000000;k++); 14 System.out.println(this.getName()+" :"+i); 15 } 16 } 17 18 public static void main(String[] args) { 19 Thread t1 = new TestThread("阿三"); 20 Thread t2 = new TestThread("李四"); 21 t1.start(); 22 t2.start(); 23 } 24 }?執(zhí)行結(jié)果:
阿三?:0
李四?:0?
阿三?:1?
李四?:1?
阿三?:2?
李四?:2?
阿三?:3?
阿三?:4?
李四?:3?
李四?:4?
Process finished with exit code 0
對于上面的多線程程序代碼來說,輸出的結(jié)果是不確定的。其中的一條語句for(long k= 0; k <100000000;k++);是用來模擬一個非常耗時的操作的。
五、一些常見問題
1、線程的名字,一個運行中的線程總是有名字的,名字有兩個來源,一個是虛擬機自己給的名字,一個是你自己的定的名字。在沒有指定線程名字的情況下,虛擬機總會為線程指定名字,并且主線程的名字總是mian,非主線程的名字不確定。
2、線程都可以設(shè)置名字,也可以獲取線程的名字,連主線程也不例外。
3、獲取當(dāng)前線程的對象的方法是:Thread.currentThread();
4、在上面的代碼中,只能保證:每個線程都將啟動,每個線程都將運行直到完成。一系列線程以某種順序啟動并不意味著將按該順序執(zhí)行。對于任何一組啟動的線程來說,調(diào)度程序不能保證其執(zhí)行次序,持續(xù)時間也無法保證。
5、當(dāng)線程目標(biāo)run()方法結(jié)束時該線程完成。
6、一旦線程啟動,它就永遠(yuǎn)不能再重新啟動。只有一個新的線程可以被啟動,并且只能一次。一個可運行的線程或死線程可以被重新啟動。
7、線程的調(diào)度是JVM的一部分,在一個CPU的機器上上,實際上一次只能運行一個線程。一次只有一個線程棧執(zhí)行。JVM線程調(diào)度程序決定實際運行哪個處于可運行狀態(tài)的線程。
眾多可運行線程中的某一個會被選中做為當(dāng)前線程。可運行線程被選擇運行的順序是沒有保障的。
8、盡管通常采用隊列形式,但這是沒有保障的。隊列形式是指當(dāng)一個線程完成“一輪”時,它移到可運行隊列的尾部等待,直到它最終排隊到該隊列的前端為止,它才能被再次選中。事實上,我們把它稱為可運行池而不是一個可運行隊列,目的是幫助認(rèn)識線程并不都是以某種有保障的順序排列唱呢個一個隊列的事實。
9、盡管我們沒有無法控制線程調(diào)度程序,但可以通過別的方式來影響線程調(diào)度的方式。
Java線程:線程棧模型與線程的變量
要理解線程調(diào)度的原理,以及線程執(zhí)行過程,必須理解線程棧模型。
線程棧是指某時刻時內(nèi)存中線程調(diào)度的棧信息,當(dāng)前調(diào)用的方法總是位于棧頂。線程棧的內(nèi)容是隨著程序的運行動態(tài)變化的,因此研究線程棧必須選擇一個運行的時刻(實際上指代碼運行到什么地方)。
下面通過一個示例性的代碼說明線程(調(diào)用)棧的變化過程。
這幅圖描述在代碼執(zhí)行到兩個不同時刻1、2時候,虛擬機線程調(diào)用棧示意圖。
當(dāng)程序執(zhí)行到t.start();時候,程序多出一個分支(增加了一個調(diào)用棧B),這樣,棧A、棧B并行執(zhí)行。
從這里就可以看出方法調(diào)用和線程啟動的區(qū)別了。
Java線程:線程狀態(tài)的轉(zhuǎn)換
一、線程狀態(tài)
線程的狀態(tài)轉(zhuǎn)換是線程控制的基礎(chǔ)。線程狀態(tài)總的可分為五大狀態(tài):分別是生、死、可運行、運行、等待/阻塞。用一個圖來描述如下:
1、新狀態(tài):線程對象已經(jīng)創(chuàng)建,還沒有在其上調(diào)用start()方法。
2、可運行狀態(tài):當(dāng)線程有資格運行,但調(diào)度程序還沒有把它選定為運行線程時線程所處的狀態(tài)。當(dāng)start()方法調(diào)用時,線程首先進(jìn)入可運行狀態(tài)。在線程運行之后或者從阻塞、等待或睡眠狀態(tài)回來后,也返回到可運行狀態(tài)。
3、運行狀態(tài):線程調(diào)度程序從可運行池中選擇一個線程作為當(dāng)前線程時線程所處的狀態(tài)。這也是線程進(jìn)入運行狀態(tài)的唯一一種方式。
4、等待/阻塞/睡眠狀態(tài):這是線程有資格運行時它所處的狀態(tài)。實際上這個三狀態(tài)組合為一種,其共同點是:線程仍舊是活的,但是當(dāng)前沒有條件運行。換句話說,它是可運行的,但是如果某件事件出現(xiàn),他可能返回到可運行狀態(tài)。
5、死亡態(tài):當(dāng)線程的run()方法完成時就認(rèn)為它死去。這個線程對象也許是活的,但是,它已經(jīng)不是一個單獨執(zhí)行的線程。線程一旦死亡,就不能復(fù)生。如果在一個死去的線程上調(diào)用start()方法,會拋出java.lang.IllegalThreadStateException異常。
有關(guān)詳細(xì)狀態(tài)轉(zhuǎn)換圖可以參看本人的“Java多線程編程總結(jié)”中的圖
二、阻止線程執(zhí)行
對于線程的阻止,考慮一下三個方面,不考慮IO阻塞的情況:
- 睡眠;
- 等待;
因為需要一個對象的鎖定而被阻塞。
1、睡眠
Thread.sleep(long millis)和Thread.sleep(long millis, int nanos)靜態(tài)方法強制當(dāng)前正在執(zhí)行的線程休眠(暫停執(zhí)行),以“減慢線程”。當(dāng)線程睡眠時,它入睡在某個地方,在蘇醒之前不會返回到可運行狀態(tài)。當(dāng)睡眠時間到期,則返回到可運行狀態(tài)。
線程睡眠的原因:線程執(zhí)行太快,或者需要強制進(jìn)入下一輪,因為Java規(guī)范不保證合理的輪換。
睡眠的實現(xiàn):調(diào)用靜態(tài)方法。
try {Thread.sleep(123); } catch (InterruptedException e) {e.printStackTrace(); }睡眠的位置:為了讓其他線程有機會執(zhí)行,可以將Thread.sleep()的調(diào)用放線程run()之內(nèi)。這樣才能保證該線程執(zhí)行過程中會睡眠。
例如,在前面的例子中,將一個耗時的操作改為睡眠,以減慢線程的執(zhí)行。可以這么寫:
public void run() { for(int i = 0;i<5;i++){// 很耗時的操作,用來減慢線程的執(zhí)行// for(long k= 0; k <100000000;k++);try {Thread.sleep(3);} catch (InterruptedException e) {e.printStackTrace(); .}System.out.println(this.getName()+" :"+i);} }運行結(jié)果:
阿三?:0
李四?:0?
阿三?:1?
阿三?:2?
阿三?:3?
李四?:1?
李四?:2?
阿三?:4?
李四?:3?
李四?:4?
Process finished with exit code 0
這樣,線程在每次執(zhí)行過程中,總會睡眠3毫秒,睡眠了,其他的線程就有機會執(zhí)行了。
注意:
- 線程睡眠是幫助所有線程獲得運行機會的最好方法。
- 線程睡眠到期自動蘇醒,并返回到可運行狀態(tài),不是運行狀態(tài)。sleep()中指定的時間是線程不會運行的最短時間。因此,sleep()方法不能保證該線程睡眠到期后就開始執(zhí)行。
- sleep()是靜態(tài)方法,只能控制當(dāng)前正在運行的線程。
下面給個例子:
/** * 一個計數(shù)器,計數(shù)到100,在每個數(shù)字之間暫停1秒,每隔10個數(shù)字輸出一個字符串 */ public class MyThreadextends Thread {public void 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(); } } } public static void 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
2、線程的優(yōu)先級和線程讓步y(tǒng)ield()
線程的讓步是通過Thread.yield()來實現(xiàn)的。yield()方法的作用是:暫停當(dāng)前正在執(zhí)行的線程對象,并執(zhí)行其他線程。
要理解yield(),必須了解線程的優(yōu)先級的概念。線程總是存在優(yōu)先級,優(yōu)先級范圍在1~10之間。JVM線程調(diào)度程序是基于優(yōu)先級的搶先調(diào)度機制。在大多數(shù)情況下,當(dāng)前運行的線程優(yōu)先級將大于或等于線程池中任何線程的優(yōu)先級。但這僅僅是大多數(shù)情況。
注意:當(dāng)設(shè)計多線程應(yīng)用程序的時候,一定不要依賴于線程的優(yōu)先級。因為線程調(diào)度優(yōu)先級操作是沒有保障的,只能把線程優(yōu)先級作用作為一種提高程序效率的方法,但是要保證程序不依賴這種操作。
當(dāng)線程池中線程都具有相同的優(yōu)先級,調(diào)度程序的JVM實現(xiàn)自由選擇它喜歡的線程。這時候調(diào)度程序的操作有兩種可能:一是選擇一個線程運行,直到它阻塞或者運行完成為止。二是時間分片,為池內(nèi)的每個線程提供均等的運行機會。
設(shè)置線程的優(yōu)先級:線程默認(rèn)的優(yōu)先級是創(chuàng)建它的執(zhí)行線程的優(yōu)先級。可以通過setPriority(int newPriority)更改線程的優(yōu)先級。例如:
??????? Thread t = new MyThread();
??????? t.setPriority(8);
??????? t.start();
線程優(yōu)先級為1~10之間的正整數(shù),JVM從不會改變一個線程的優(yōu)先級。然而,1~10之間的值是沒有保證的。一些JVM可能不能識別10個不同的值,而將這些優(yōu)先級進(jìn)行每兩個或多個合并,變成少于10個的優(yōu)先級,則兩個或多個優(yōu)先級的線程可能被映射為一個優(yōu)先級。
線程默認(rèn)優(yōu)先級是5,Thread類中有三個常量,定義線程優(yōu)先級范圍:
static int MAX_PRIORITY?
??????????線程可以具有的最高優(yōu)先級。
static int MIN_PRIORITY?
??????????線程可以具有的最低優(yōu)先級。
static int NORM_PRIORITY?
??????????分配給線程的默認(rèn)優(yōu)先級。
3、Thread.yield()方法
Thread.yield()方法作用是:暫停當(dāng)前正在執(zhí)行的線程對象,并執(zhí)行其他線程。
yield()應(yīng)該做的是讓當(dāng)前運行線程回到可運行狀態(tài),以允許具有相同優(yōu)先級的其他線程獲得運行機會。因此,使用yield()的目的是讓相同優(yōu)先級的線程之間能適當(dāng)?shù)妮嗈D(zhuǎn)執(zhí)行。但是,實際中無法保證yield()達(dá)到讓步目的,因為讓步的線程還有可能被線程調(diào)度程序再次選中。
結(jié)論:yield()從未導(dǎo)致線程轉(zhuǎn)到等待/睡眠/阻塞狀態(tài)。在大多數(shù)情況下,yield()將導(dǎo)致線程從運行狀態(tài)轉(zhuǎn)到可運行狀態(tài),但有可能沒有效果。
4、join()方法
Thread的非靜態(tài)方法join()讓一個線程B“加入”到另外一個線程A的尾部。在A執(zhí)行完畢之前,B不能工作。例如:
??????? Thread t = new MyThread();
??????? t.start();
??????? t.join();
另外,join()方法還有帶超時限制的重載版本。例如t.join(5000);則讓線程等待5000毫秒,如果超過這個時間,則停止等待,變?yōu)榭蛇\行狀態(tài)。
線程的加入join()對線程棧導(dǎo)致的結(jié)果是線程棧發(fā)生了變化,當(dāng)然這些變化都是瞬時的。下面給示意圖:
?
?
?
小結(jié)
到目前位置,介紹了線程離開運行狀態(tài)的3種方法:
1、調(diào)用Thread.sleep():使當(dāng)前線程睡眠至少多少毫秒(盡管它可能在指定的時間之前被中斷)。
2、調(diào)用Thread.yield():不能保障太多事情,盡管通常它會讓當(dāng)前運行線程回到可運行性狀態(tài),使得有相同優(yōu)先級的線程有機會執(zhí)行。
3、調(diào)用join()方法:保證當(dāng)前線程停止執(zhí)行,直到該線程所加入的線程完成為止。然而,如果它加入的線程沒有存活,則當(dāng)前線程不需要停止。
?
除了以上三種方式外,還有下面幾種特殊情況可能使線程離開運行狀態(tài):
1、線程的run()方法完成。
2、在對象上調(diào)用wait()方法(不是在線程上調(diào)用)。
3、線程不能在對象上獲得鎖定,它正試圖運行該對象的方法代碼。
4、線程調(diào)度程序可以決定將當(dāng)前運行狀態(tài)移動到可運行狀態(tài),以便讓另一個線程獲得運行機會,而不需要任何理由。
Java線程:線程的同步與鎖
一、同步問題提出
線程的同步是為了防止多個線程訪問一個數(shù)據(jù)對象時,對數(shù)據(jù)造成的破壞。
例如:兩個線程ThreadA、ThreadB都操作同一個對象Foo對象,并修改Foo對象上的數(shù)據(jù)。
public class?Foo {
????private int?x = 100;
????public int?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() +?" :當(dāng)前foo對象的x值= "?+ foo.getX());
????????}?
????}?
????publicint?fix(int?y) {
????????return?foo.fix(y);
????}?
}
?
運行結(jié)果:
Thread-A :?當(dāng)前foo對象的x值= 40
Thread-B :?當(dāng)前foo對象的x值= 40
Thread-B :?當(dāng)前foo對象的x值= -20
Thread-A :?當(dāng)前foo對象的x值= -50
Thread-A :?當(dāng)前foo對象的x值= -80
Thread-B :?當(dāng)前foo對象的x值= -80
Process finished with exit code 0
?
從結(jié)果發(fā)現(xiàn),這樣的輸出值明顯是不合理的。原因是兩個線程不加控制的訪問Foo對象并修改其數(shù)據(jù)所致。
如果要保持結(jié)果的合理性,只需要達(dá)到一個目的,就是將對Foo的訪問加以限制,每次只能有一個線程在訪問。這樣就能保證Foo對象中數(shù)據(jù)的合理性了。
在具體的Java代碼中需要完成一下兩個操作:
把競爭訪問的資源類Foo變量x標(biāo)識為private;
同步哪些修改變量的代碼,使用synchronized關(guān)鍵字同步方法或代碼。
二、同步和鎖定
1、鎖的原理
Java中每個對象都有一個內(nèi)置鎖
當(dāng)程序運行到非靜態(tài)的synchronized同步方法上時,自動獲得與正在執(zhí)行代碼類的當(dāng)前實例(this實例)有關(guān)的鎖。獲得一個對象的鎖也稱為獲取鎖、鎖定對象、在對象上鎖定或在對象上同步。
當(dāng)程序運行到synchronized同步方法或代碼塊時才該對象鎖才起作用。
一個對象只有一個鎖。所以,如果一個線程獲得該鎖,就沒有其他線程可以獲得鎖,直到第一個線程釋放(或返回)鎖。這也意味著任何其他線程都不能進(jìn)入該對象上的synchronized方法或代碼塊,直到該鎖被釋放。
釋放鎖是指持鎖線程退出了synchronized同步方法或代碼塊。
關(guān)于鎖和同步,有一下幾個要點:
1)、只能同步方法,而不能同步變量和類;
2)、每個對象只有一個鎖;當(dāng)提到同步時,應(yīng)該清楚在什么上同步?也就是說,在哪個對象上同步?
3)、不必同步類中所有的方法,類可以同時擁有同步和非同步方法。
4)、如果兩個線程要執(zhí)行一個類中的synchronized方法,并且兩個線程使用相同的實例來調(diào)用方法,那么一次只能有一個線程能夠執(zhí)行方法,另一個需要等待,直到鎖被釋放。也就是說:如果一個線程在對象上獲得一個鎖,就沒有任何其他線程可以進(jìn)入(該對象的)類中的任何一個同步方法。
5)、如果線程擁有同步和非同步方法,則非同步方法可以被多個線程自由訪問而不受鎖的限制。
6)、線程睡眠時,它所持的任何鎖都不會釋放。
7)、線程可以獲得多個鎖。比如,在一個對象的同步方法里面調(diào)用另外一個對象的同步方法,則獲取了兩個對象的同步鎖。
8)、同步損害并發(fā)性,應(yīng)該盡可能縮小同步范圍。同步不但可以同步整個方法,還可以同步方法中一部分代碼塊。
9)、在使用同步代碼塊時候,應(yīng)該指定在哪個對象上同步,也就是說要獲取哪個對象的鎖。例如:
??? public int fix(int y) {
??????? synchronized (this) {
??????????? x = x - y;
??????? }
??????? return x;
??? }
當(dāng)然,同步方法也可以改寫為非同步方法,但功能完全一樣的,例如:
??? public synchronized int getX() {
??????? return x++;
??? }
與
??? public int getX() {
??????? synchronized (this) {
??????????? return x;
??????? }
??? }
效果是完全一樣的。
三、靜態(tài)方法同步
要同步靜態(tài)方法,需要一個用于整個類對象的鎖,這個對象是就是這個類(XXX.class)。
例如:
public static synchronized int setName(String name){
????? Xxx.name = name;
}
等價于
public static int setName(String name){
????? synchronized(Xxx.class){
??????????? Xxx.name = name;
????? }
}
四、如果線程不能不能獲得鎖會怎么樣
如果線程試圖進(jìn)入同步方法,而其鎖已經(jīng)被占用,則線程在該對象上被阻塞。實質(zhì)上,線程進(jìn)入該對象的的一種池中,必須在哪里等待,直到其鎖被釋放,該線程再次變?yōu)榭蛇\行或運行為止。
當(dāng)考慮阻塞時,一定要注意哪個對象正被用于鎖定:
1、調(diào)用同一個對象中非靜態(tài)同步方法的線程將彼此阻塞。如果是不同對象,則每個線程有自己的對象的鎖,線程間彼此互不干預(yù)。
2、調(diào)用同一個類中的靜態(tài)同步方法的線程將彼此阻塞,它們都是鎖定在相同的Class對象上。
3、靜態(tài)同步方法和非靜態(tài)同步方法將永遠(yuǎn)不會彼此阻塞,因為靜態(tài)方法鎖定在Class對象上,非靜態(tài)方法鎖定在該類的對象上。
4、對于同步代碼塊,要看清楚什么對象已經(jīng)用于鎖定(synchronized后面括號的內(nèi)容)。在同一個對象上進(jìn)行同步的線程將彼此阻塞,在不同對象上鎖定的線程將永遠(yuǎn)不會彼此阻塞。
五、何時需要同步
在多個線程同時訪問互斥(可交換)數(shù)據(jù)時,應(yīng)該同步以保護(hù)數(shù)據(jù),確保兩個線程不會同時修改更改它。
對于非靜態(tài)字段中可更改的數(shù)據(jù),通常使用非靜態(tài)方法訪問。
對于靜態(tài)字段中可更改的數(shù)據(jù),通常使用靜態(tài)方法訪問。
如果需要在非靜態(tài)方法中使用靜態(tài)字段,或者在靜態(tài)字段中調(diào)用非靜態(tài)方法,問題將變得非常復(fù)雜。已經(jīng)超出SJCP考試范圍了。
六、線程安全類
當(dāng)一個類已經(jīng)很好的同步以保護(hù)它的數(shù)據(jù)時,這個類就稱為“線程安全的”。
即使是線程安全類,也應(yīng)該特別小心,因為操作的線程是間仍然不一定安全。
舉個形象的例子,比如一個集合是線程安全的,有兩個線程在操作同一個集合對象,當(dāng)?shù)谝粋€線程查詢集合非空后,刪除集合中所有元素的時候。第二個線程也來執(zhí)行與第一個線程相同的操作,也許在第一個線程查詢后,第二個線程也查詢出集合非空,但是當(dāng)?shù)谝粋€執(zhí)行清除后,第二個再執(zhí)行刪除顯然是不對的,因為此時集合已經(jīng)為空了。
看個代碼:
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());
是同步的,但是程序還不是線程安全的。
出現(xiàn)這種事件的原因是,上例中一個線程操作列表過程中無法阻止另外一個線程對列表的其他操作。
解決上面問題的辦法是,在操作集合對象的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;
????????}?
????}?
}
這樣,當(dāng)一個線程訪問其中一個同步方法時,其他線程只有等待。
七、線程死鎖
死鎖對Java程序來說,是很復(fù)雜的,也很難發(fā)現(xiàn)問題。當(dāng)兩個線程被阻塞,每個線程在等待另一個線程時就發(fā)生死鎖。
還是看一個比較直觀的死鎖例子:
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;?
????????????}?
????????}?
????}?
}
假設(shè)read()方法由一個線程啟動,write()方法由另外一個線程啟動。讀線程將擁有resourceA鎖,寫線程將擁有resourceB鎖,兩者都堅持等待的話就出現(xiàn)死鎖。
實際上,上面這個例子發(fā)生死鎖的概率很小。因為在代碼內(nèi)的某個點,CPU必須從讀線程切換到寫線程,所以,死鎖基本上不能發(fā)生。
但是,無論代碼中發(fā)生死鎖的概率有多小,一旦發(fā)生死鎖,程序就死掉。有一些設(shè)計方法能幫助避免死鎖,包括始終按照預(yù)定義的順序獲取鎖這一策略。已經(jīng)超出SCJP的考試范圍。
八、線程同步小結(jié)
1、線程同步的目的是為了保護(hù)多個線程反問一個資源時對資源的破壞。
2、線程同步方法是通過鎖來實現(xiàn),每個對象都有切僅有一個鎖,這個鎖與一個特定的對象關(guān)聯(lián),線程一旦獲取了對象鎖,其他訪問該對象的線程就無法再訪問該對象的其他同步方法。
3、對于靜態(tài)同步方法,鎖是針對這個類的,鎖對象是該類的Class對象。靜態(tài)和非靜態(tài)方法的鎖互不干預(yù)。一個線程獲得鎖,當(dāng)在一個同步方法中訪問另外對象上的同步方法時,會獲取這兩個對象鎖。
4、對于同步,要時刻清醒在哪個對象上同步,這是關(guān)鍵。
5、編寫線程安全的類,需要時刻注意對多個線程競爭訪問資源的邏輯和安全做出正確的判斷,對“原子”操作做出分析,并保證原子操作期間別的線程無法訪問競爭資源。
6、當(dāng)多個線程等待一個對象鎖時,沒有獲取到鎖的線程將發(fā)生阻塞。
7、死鎖是線程間相互等待鎖鎖造成的,在實際中發(fā)生的概率非常的小。真讓你寫個死鎖程序,不一定好使,呵呵。但是,一旦程序發(fā)生死鎖,程序?qū)⑺赖簟?/p>
Java線程:線程的交互
線程交互是比較復(fù)雜的問題,SCJP要求不很基礎(chǔ):給定一個場景,編寫代碼來恰當(dāng)使用等待、通知和通知所有線程。
一、線程交互的基礎(chǔ)知識
SCJP所要求的線程交互知識點需要從java.lang.Object的類的三個方法來學(xué)習(xí):
?void notify()?
??????????喚醒在此對象監(jiān)視器上等待的單個線程。
?void notifyAll()?
??????????喚醒在此對象監(jiān)視器上等待的所有線程。
?void wait()?
??????????導(dǎo)致當(dāng)前的線程等待,直到其他線程調(diào)用此對象的?notify()方法或?notifyAll()方法。
當(dāng)然,wait()還有另外兩個重載方法:
?void wait(long timeout)?
??????????導(dǎo)致當(dāng)前的線程等待,直到其他線程調(diào)用此對象的?notify()方法或?notifyAll()方法,或者超過指定的時間量。?
?void wait(long timeout, int nanos)?
??????????導(dǎo)致當(dāng)前的線程等待,直到其他線程調(diào)用此對象的?notify()方法或?notifyAll()方法,或者其他某個線程中斷當(dāng)前線程,或者已超過某個實際時間量。
?
以上這些方法是幫助線程傳遞線程關(guān)心的時間狀態(tài)。
關(guān)于等待/通知,要記住的關(guān)鍵點是:
必須從同步環(huán)境內(nèi)調(diào)用wait()、notify()、notifyAll()方法。線程不能調(diào)用對象上等待或通知的方法,除非它擁有那個對象的鎖。
wait()、notify()、notifyAll()都是Object的實例方法。與每個對象具有鎖一樣,每個對象可以有一個線程列表,他們等待來自該信號(通知)。線程通過執(zhí)行對象上的wait()方法獲得這個等待列表。從那時候起,它不再執(zhí)行任何其他指令,直到調(diào)用對象的notify()方法為止。如果多個線程在同一個對象上等待,則將只選擇一個線程(不保證以何種順序)繼續(xù)執(zhí)行。如果沒有線程等待,則不采取任何特殊操作。
下面看個例子就明白了:
/**?
*?計算輸出其他線程鎖計算的數(shù)據(jù)
*?
* @author leizhimin 2008-9-15 13:20:38?
*/?
publicclass?ThreadA {
????publicstaticvoid?main(String[] args) {
????????ThreadB b =?new?ThreadB();
????????//啟動計算線程
????????b.start();?
????????//線程A擁有b對象上的鎖。線程為了調(diào)用wait()或notify()方法,該線程必須是那個對象鎖的擁有者
????????synchronized?(b) {
????????????try?{
????????????????System.out.println("等待對象b完成計算。。。");
????????????????//當(dāng)前線程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;?
????????????}?
????????????//(完成計算了)喚醒在此對象監(jiān)視器上等待的單個線程,在本例中線程A被喚醒
????????????notify();?
????????}?
????}?
}
?
等待對象b完成計算。。。
b對象計算的總和是:5050?
Process finished with exit code 0
千萬注意:
當(dāng)在對象上調(diào)用wait()方法時,執(zhí)行該代碼的線程立即放棄它在對象上的鎖。然而調(diào)用notify()時,并不意味著這時線程會放棄其鎖。如果線程榮然在完成同步代碼,則線程在移出之前不會放棄鎖。因此,只要調(diào)用notify()并不意味著這時該鎖變得可用。
二、多個線程在等待一個對象鎖時候使用notifyAll()
在多數(shù)情況下,最好通知等待某個對象的所有線程。如果這樣做,可以在對象上使用notifyAll()讓所有在此對象上等待的線程沖出等待區(qū),返回到可運行狀態(tài)。
下面給個例子:
/**?
*?計算線程?
*?
* @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();?
????????}?
}
?
/**?
*?獲取計算結(jié)果并輸出?
*?
* @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() +?"等待計算結(jié)果。。。");
????????????????????????????????c.wait();?
????????????????????????}?catch?(InterruptedException e) {
????????????????????????????????e.printStackTrace();?
????????????????????????}?
????????????????????????System.out.println(Thread.currentThread() +"計算結(jié)果為:"?+ c.total);
????????????????}?
????????}?
????????publicstaticvoid?main(String[] args) {
????????????????Calculator calculator =?new?Calculator();
????????????????//啟動三個線程,分別獲取計算結(jié)果
????????????????new?ReaderResult(calculator).start();
????????????????new?ReaderResult(calculator).start();
????????????????new?ReaderResult(calculator).start();
????????????????//啟動計算線程
????????????????calculator.start();?
????????}?
}
?
運行結(jié)果:
Thread[Thread-1,5,main]等待計算結(jié)果。。。
Thread[Thread-2,5,main]等待計算結(jié)果。。。
Thread[Thread-3,5,main]等待計算結(jié)果。。。
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]計算結(jié)果為:5050
Thread[Thread-2,5,main]計算結(jié)果為:5050
Thread[Thread-3,5,main]計算結(jié)果為:5050
Process finished with exit code 0
?
運行結(jié)果表明,程序中有異常,并且多次運行結(jié)果可能有多種輸出結(jié)果。這就是說明,這個多線程的交互程序還存在問題。究竟是出了什么問題,需要深入的分析和思考,下面將做具體分析。
?
實際上,上面這個代碼中,我們期望的是讀取結(jié)果的線程在計算線程調(diào)用notifyAll()之前等待即可。但是,如果計算線程先執(zhí)行,并在讀取結(jié)果線程等待之前調(diào)用了notify()方法,那么又會發(fā)生什么呢?這種情況是可能發(fā)生的。因為無法保證線程的不同部分將按照什么順序來執(zhí)行。幸運的是當(dāng)讀取線程運行時,它只能馬上進(jìn)入等待狀態(tài)----它沒有做任何事情來檢查等待的事件是否已經(jīng)發(fā)生。? ----因此,如果計算線程已經(jīng)調(diào)用了notifyAll()方法,那么它就不會再次調(diào)用notifyAll(),----并且等待的讀取線程將永遠(yuǎn)保持等待。這當(dāng)然是開發(fā)者所不愿意看到的問題。
?
因此,當(dāng)?shù)却氖录l(fā)生時,需要能夠檢查notifyAll()通知事件是否已經(jīng)發(fā)生。
通常,解決上面問題的最佳方式是將
Java線程:線程的調(diào)度-休眠
Java線程調(diào)度是Java多線程的核心,只有良好的調(diào)度,才能充分發(fā)揮系統(tǒng)的性能,提高程序的執(zhí)行效率。
這里要明確的一點,不管程序員怎么編寫調(diào)度,只能最大限度的影響線程執(zhí)行的次序,而不能做到精準(zhǔn)控制。
線程休眠的目的是使線程讓出CPU的最簡單的做法之一,線程休眠時候,會將CPU資源交給其他線程,以便能輪換執(zhí)行,當(dāng)休眠一定時間后,線程會蘇醒,進(jìn)入準(zhǔn)備狀態(tài)等待執(zhí)行。
線程休眠的方法是Thread.sleep(long millis)和Thread.sleep(long millis, int nanos),均為靜態(tài)方法,那調(diào)用sleep休眠的哪個線程呢?簡單說,哪個線程調(diào)用sleep,就休眠哪個線程。
/**?
* Java線程:線程的調(diào)度-休眠
*?
* @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 +?"次執(zhí)行!");
????????????????????????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 +?"次執(zhí)行!");
????????????????????????try?{
????????????????????????????????Thread.sleep(50);?
????????????????????????}?catch?(InterruptedException e) {
????????????????????????????????e.printStackTrace();?
????????????????????????}?
????????????????}?
????????}?
}
?
線程2第0次執(zhí)行!
線程1第0次執(zhí)行!
線程1第1次執(zhí)行!
線程2第1次執(zhí)行!
線程1第2次執(zhí)行!
線程2第2次執(zhí)行!
Process finished with exit code 0
?
從上面的結(jié)果輸出可以看出,無法精準(zhǔn)保證線程執(zhí)行次序。
Java線程:線程的調(diào)度-優(yōu)先級
與線程休眠類似,線程的優(yōu)先級仍然無法保障線程的執(zhí)行次序。只不過,優(yōu)先級高的線程獲取CPU資源的概率較大,優(yōu)先級低的并非沒機會執(zhí)行。
線程的優(yōu)先級用1-10之間的整數(shù)表示,數(shù)值越大優(yōu)先級越高,默認(rèn)的優(yōu)先級為5。
在一個線程中開啟另外一個新線程,則新開線程稱為該線程的子線程,子線程初始優(yōu)先級與父線程相同。
/**?
* Java線程:線程的調(diào)度-優(yōu)先級
*?
* @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 +?"次執(zhí)行!");
????????????????????????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 +?"次執(zhí)行!");
????????????????????????try?{
????????????????????????????????Thread.sleep(100);?
????????????????????????}?catch?(InterruptedException e) {
????????????????????????????????e.printStackTrace();?
????????????????????????}?
????????????????}?
????????}?
}
?
線程1第0次執(zhí)行!
線程2第0次執(zhí)行!
線程2第1次執(zhí)行!
線程1第1次執(zhí)行!
線程2第2次執(zhí)行!
線程1第2次執(zhí)行!
線程1第3次執(zhí)行!
線程2第3次執(zhí)行!
線程2第4次執(zhí)行!
線程1第4次執(zhí)行!
線程1第5次執(zhí)行!
線程2第5次執(zhí)行!
線程1第6次執(zhí)行!
線程2第6次執(zhí)行!
線程1第7次執(zhí)行!
線程2第7次執(zhí)行!
線程1第8次執(zhí)行!
線程2第8次執(zhí)行!
線程1第9次執(zhí)行!
線程2第9次執(zhí)行!
Process finished with exit code 0
Java線程:線程的調(diào)度-讓步
線程的讓步含義就是使當(dāng)前運行著線程讓出CPU資源,但是然給誰不知道,僅僅是讓出,線程狀態(tài)回到可運行狀態(tài)。
線程的讓步使用Thread.yield()方法,yield()為靜態(tài)方法,功能是暫停當(dāng)前正在執(zhí)行的線程對象,并執(zhí)行其他線程。
/**?
* Java線程:線程的調(diào)度-讓步
*?
* @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 +?"次執(zhí)行!");
????????????????}?
????????}?
}?
class?MyRunnableimplements?Runnable {
????????publicvoid?run() {
????????????????for?(int?i = 0; i < 10; i++) {
????????????????????????System.out.println("線程2第"?+ i +?"次執(zhí)行!");
????????????????????????Thread.yield();?
????????????????}?
????????}?
}
?
線程2第0次執(zhí)行!
線程2第1次執(zhí)行!
線程2第2次執(zhí)行!
線程2第3次執(zhí)行!
線程1第0次執(zhí)行!
線程1第1次執(zhí)行!
線程1第2次執(zhí)行!
線程1第3次執(zhí)行!
線程1第4次執(zhí)行!
線程1第5次執(zhí)行!
線程1第6次執(zhí)行!
線程1第7次執(zhí)行!
線程1第8次執(zhí)行!
線程1第9次執(zhí)行!
線程2第4次執(zhí)行!
線程2第5次執(zhí)行!
線程2第6次執(zhí)行!
線程2第7次執(zhí)行!
線程2第8次執(zhí)行!
線程2第9次執(zhí)行!
Process finished with exit code 0
Java線程:線程的調(diào)度-合并
線程的合并的含義就是將幾個并行線程的線程合并為一個單線程執(zhí)行,應(yīng)用場景是當(dāng)一個線程必須等待另一個線程執(zhí)行完畢才能執(zhí)行時可以使用join方法。
join為非靜態(tài)方法,定義如下:
void?join()????
????等待該線程終止。????
void?join(long?millis)????
????等待該線程終止的時間最長為?millis毫秒。????
void?join(long?millis,int?nanos)????
????等待該線程終止的時間最長為?millis毫秒?+ nanos?納秒。
?
/**?
* Java線程:線程的調(diào)度-合并
*?
* @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 +"次執(zhí)行!");
????????????????????????if?(i > 2)try?{?
????????????????????????????????//t1線程合并到主線程中,主線程停止執(zhí)行過程,轉(zhuǎn)而執(zhí)行t1線程,直到t1執(zhí)行完畢后繼續(xù)。
????????????????????????????????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 +?"次執(zhí)行!");
????????????????}?
????????}?
}
?
主線程第0次執(zhí)行!
主線程第1次執(zhí)行!
主線程第2次執(zhí)行!
線程1第0次執(zhí)行!
主線程第3次執(zhí)行!
線程1第1次執(zhí)行!
線程1第2次執(zhí)行!
線程1第3次執(zhí)行!
線程1第4次執(zhí)行!
線程1第5次執(zhí)行!
線程1第6次執(zhí)行!
線程1第7次執(zhí)行!
線程1第8次執(zhí)行!
線程1第9次執(zhí)行!
主線程第4次執(zhí)行!
主線程第5次執(zhí)行!
主線程第6次執(zhí)行!
主線程第7次執(zhí)行!
主線程第8次執(zhí)行!
主線程第9次執(zhí)行!
主線程第10次執(zhí)行!
主線程第11次執(zhí)行!
主線程第12次執(zhí)行!
主線程第13次執(zhí)行!
主線程第14次執(zhí)行!
主線程第15次執(zhí)行!
主線程第16次執(zhí)行!
主線程第17次執(zhí)行!
主線程第18次執(zhí)行!
主線程第19次執(zhí)行!
Process finished with exit code 0
Java線程:線程的調(diào)度-守護(hù)線程
守護(hù)線程與普通線程寫法上基本么啥區(qū)別,調(diào)用線程對象的方法setDaemon(true),則可以將其設(shè)置為守護(hù)線程。
?
守護(hù)線程使用的情況較少,但并非無用,舉例來說,JVM的垃圾回收、內(nèi)存管理等線程都是守護(hù)線程。還有就是在做數(shù)據(jù)庫應(yīng)用時候,使用的數(shù)據(jù)庫連接池,連接池本身也包含著很多后臺線程,監(jiān)控連接個數(shù)、超時時間、狀態(tài)等等。
?
setDaemon方法的詳細(xì)說明:
publicfinalvoid?setDaemon(boolean?on)將該線程標(biāo)記為守護(hù)線程或用戶線程。當(dāng)正在運行的線程都是守護(hù)線程時,Java虛擬機退出。????
??該方法必須在啟動線程前調(diào)用。????
??該方法首先調(diào)用該線程的?checkAccess方法,且不帶任何參數(shù)。這可能拋出?SecurityException(在當(dāng)前線程中)。????
??參數(shù):
????on -?如果為true,則將該線程標(biāo)記為守護(hù)線程。????
??拋出:????
????IllegalThreadStateException -?如果該線程處于活動狀態(tài)。????
????SecurityException -?如果當(dāng)前線程無法修改該線程。
??另請參見:
????isDaemon(), checkAccess()
?
/**?
* Java線程:線程的調(diào)度-守護(hù)線程
*?
* @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);????????//設(shè)置為守護(hù)線程
????????????????t2.start();?
????????????????t1.start();?
????????}?
}?
class?MyCommon?extends?Thread {?
????????publicvoid?run() {
????????????????for?(int?i = 0; i < 5; i++) {
????????????????????????System.out.println("線程1第"?+ i +?"次執(zhí)行!");
????????????????????????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 +"次執(zhí)行!");
????????????????????????try?{
????????????????????????????????Thread.sleep(7);?
????????????????????????}?catch?(InterruptedException e) {
????????????????????????????????e.printStackTrace();?
????????????????????????}?
????????????????}?
????????}?
}
?
后臺線程第0次執(zhí)行!
線程1第0次執(zhí)行!
線程1第1次執(zhí)行!
后臺線程第1次執(zhí)行!
后臺線程第2次執(zhí)行!
線程1第2次執(zhí)行!
線程1第3次執(zhí)行!
后臺線程第3次執(zhí)行!
線程1第4次執(zhí)行!
后臺線程第4次執(zhí)行!
后臺線程第5次執(zhí)行!
后臺線程第6次執(zhí)行!
后臺線程第7次執(zhí)行!
Process finished with exit code 0
?
從上面的執(zhí)行結(jié)果可以看出:
前臺線程是保證執(zhí)行完畢的,后臺線程還沒有執(zhí)行完畢就退出了。
?
實際上:JRE判斷程序是否執(zhí)行結(jié)束的標(biāo)準(zhǔn)是所有的前臺執(zhí)線程行完畢了,而不管后臺線程的狀態(tài),因此,在使用后臺縣城時候一定要注意這個問題。
Java線程:線程的同步-同步方法
線程的同步是保證多線程安全訪問競爭資源的一種手段。
線程的同步是Java多線程編程的難點,往往開發(fā)者搞不清楚什么是競爭資源、什么時候需要考慮同步,怎么同步等等問題,當(dāng)然,這些問題沒有很明確的答案,但有些原則問題需要考慮,是否有競爭資源被同時改動的問題?
?
在本文之前,請參閱《Java線程:線程的同步與鎖》,本文是在此基礎(chǔ)上所寫的。
?
對于同步,在具體的Java代碼中需要完成一下兩個操作:
把競爭訪問的資源標(biāo)識為private;
同步哪些修改變量的代碼,使用synchronized關(guān)鍵字同步方法或代碼。
當(dāng)然這不是唯一控制并發(fā)安全的途徑。
?
synchronized關(guān)鍵字使用說明
synchronized只能標(biāo)記非抽象的方法,不能標(biāo)識成員變量。
?
為了演示同步方法的使用,構(gòu)建了一個信用卡賬戶,起初信用額為100w,然后模擬透支、存款等多個操作。顯然銀行賬戶User對象是個競爭資源,而多個并發(fā)操作的是賬戶方法oper(int x),當(dāng)然應(yīng)該在此方法上加上同步,并將賬戶的余額設(shè)為私有變量,禁止直接訪問。
?
?
/**?
* 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;
????????}?
????????/**?
???????? *?業(yè)務(wù)方法
???????? * @param x?添加x萬元
???????? */?
????????publicsynchronizedvoid?oper(int?x) {
????????????????try?{
????????????????????????Thread.sleep(10L);?
????????????????????????this.cash += x;
????????????????????????System.out.println(Thread.currentThread().getName() +?"運行結(jié)束,增加“"?+ x +"”,當(dāng)前用戶賬戶余額為:"?+ cash);
????????????????????????Thread.sleep(10L);?
????????????????}?catch?(InterruptedException e) {
????????????????????????e.printStackTrace();?
????????????????}?
????????}?
????????@Override?
????????public?String toString() {
????????????????return"User{"?+
????????????????????????????????"code='"?+ code + '\'' +
????????????????????????????????", cash="?+ cash +
????????????????????????????????'}';?
????????}?
}
?
輸出結(jié)果:
線程A運行結(jié)束,增加“20”,當(dāng)前用戶賬戶余額為:120
線程F運行結(jié)束,增加“21”,當(dāng)前用戶賬戶余額為:141
線程E運行結(jié)束,增加“32”,當(dāng)前用戶賬戶余額為:173
線程C運行結(jié)束,增加“-80”,當(dāng)前用戶賬戶余額為:93
線程B運行結(jié)束,增加“-60”,當(dāng)前用戶賬戶余額為:33
線程D運行結(jié)束,增加“-30”,當(dāng)前用戶賬戶余額為:3
Process finished with exit code 0
?
?
反面教材,不同步的情況,也就是去掉oper(int x)方法的synchronized修飾符,然后運行程序,結(jié)果如下:
線程A運行結(jié)束,增加“20”,當(dāng)前用戶賬戶余額為:61
線程D運行結(jié)束,增加“-30”,當(dāng)前用戶賬戶余額為:63
線程B運行結(jié)束,增加“-60”,當(dāng)前用戶賬戶余額為:3
線程F運行結(jié)束,增加“21”,當(dāng)前用戶賬戶余額為:61
線程E運行結(jié)束,增加“32”,當(dāng)前用戶賬戶余額為:93
線程C運行結(jié)束,增加“-80”,當(dāng)前用戶賬戶余額為:61
Process finished with exit code 0
?
很顯然,上面的結(jié)果是錯誤的,導(dǎo)致錯誤的原因是多個線程并發(fā)訪問了競爭資源u,并對u的屬性做了改動。
?
可見同步的重要性。
?
?
注意:
通過前文可知,線程退出同步方法時將釋放掉方法所屬對象的鎖,但還應(yīng)該注意的是,同步方法中還可以使用特定的方法對線程進(jìn)行調(diào)度。這些方法來自于java.lang.Object類。
?
void?notify()????
????????????????????喚醒在此對象監(jiān)視器上等待的單個線程。????
void?notifyAll()????
????????????????????喚醒在此對象監(jiān)視器上等待的所有線程。????
void?wait()????
????????????????????導(dǎo)致當(dāng)前的線程等待,直到其他線程調(diào)用此對象的?notify()方法或?notifyAll()方法。????
void?wait(long?timeout)????
????????????????????導(dǎo)致當(dāng)前的線程等待,直到其他線程調(diào)用此對象的?notify()方法或?notifyAll()方法,或者超過指定的時間量。????
void?wait(long?timeout,int?nanos)????
????????????????????導(dǎo)致當(dāng)前的線程等待,直到其他線程調(diào)用此對象的?notify()方法或?notifyAll()方法,或者其他某個線程中斷當(dāng)前線程,或者已超過某個實際時間量。
?
結(jié)合以上方法,處理多線程同步與互斥問題非常重要,著名的生產(chǎn)者-消費者例子就是一個經(jīng)典的例子,任何語言多線程必學(xué)的例子。
Java線程:線程的同步-同步塊
對于同步,除了同步方法外,還可以使用同步代碼塊,有時候同步代碼塊會帶來比同步方法更好的效果。
?
追其同步的根本的目的,是控制競爭資源的正確的訪問,因此只要在訪問競爭資源的時候保證同一時刻只能一個線程訪問即可,因此Java引入了同步代碼快的策略,以提高性能。
?
在上個例子的基礎(chǔ)上,對oper方法做了改動,由同步方法改為同步代碼塊模式,程序的執(zhí)行邏輯并沒有問題。
?
?
/**?
* 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;
????????}?
????????/**?
???????? *?業(yè)務(wù)方法
???????? *?
???????? * @param x?添加x萬元
???????? */?
????????publicvoid?oper(int?x) {
????????????????try?{
????????????????????????Thread.sleep(10L);?
????????????????????????synchronized?(this) {
????????????????????????????????this.cash += x;
????????????????????????????????System.out.println(Thread.currentThread().getName() +"運行結(jié)束,增加“"?+ x +"”,當(dāng)前用戶賬戶余額為:"?+ cash);
????????????????????????}?
????????????????????????Thread.sleep(10L);?
????????????????}?catch?(InterruptedException e) {
????????????????????????e.printStackTrace();?
????????????????}?
????????}?
????????@Override?
????????public?String toString() {
????????????????return"User{"?+
????????????????????????????????"code='"?+ code + '\'' +
????????????????????????????????", cash="?+ cash +
????????????????????????????????'}';?
????????}?
}
?
線程E運行結(jié)束,增加“32”,當(dāng)前用戶賬戶余額為:132
線程B運行結(jié)束,增加“-60”,當(dāng)前用戶賬戶余額為:72
線程D運行結(jié)束,增加“-30”,當(dāng)前用戶賬戶余額為:42
線程F運行結(jié)束,增加“21”,當(dāng)前用戶賬戶余額為:63
線程C運行結(jié)束,增加“-80”,當(dāng)前用戶賬戶余額為:-17
線程A運行結(jié)束,增加“20”,當(dāng)前用戶賬戶余額為:3
Process finished with exit code 0
?
注意:
在使用synchronized關(guān)鍵字時候,應(yīng)該盡可能避免在synchronized方法或synchronized塊中使用sleep或者yield方法,因為synchronized程序塊占有著對象鎖,你休息那么其他的線程只能一邊等著你醒來執(zhí)行完了才能執(zhí)行。不但嚴(yán)重影響效率,也不合邏輯。
同樣,在同步程序塊內(nèi)調(diào)用yeild方法讓出CPU資源也沒有意義,因為你占用著鎖,其他互斥線程還是無法訪問同步程序塊。當(dāng)然與同步程序塊無關(guān)的線程可以獲得更多的執(zhí)行時間。
Java線程:并發(fā)協(xié)作-生產(chǎn)者消費者模型
對于多線程程序來說,不管任何編程語言,生產(chǎn)者和消費者模型都是最經(jīng)典的。就像學(xué)習(xí)每一門編程語言一樣,Hello World!都是最經(jīng)典的例子。
?
實際上,準(zhǔn)確說應(yīng)該是“生產(chǎn)者-消費者-倉儲”模型,離開了倉儲,生產(chǎn)者消費者模型就顯得沒有說服力了。
對于此模型,應(yīng)該明確一下幾點:
1、生產(chǎn)者僅僅在倉儲未滿時候生產(chǎn),倉滿則停止生產(chǎn)。
2、消費者僅僅在倉儲有產(chǎn)品時候才能消費,倉空則等待。
3、當(dāng)消費者發(fā)現(xiàn)倉儲沒產(chǎn)品可消費時候會通知生產(chǎn)者生產(chǎn)。
4、生產(chǎn)者在生產(chǎn)出可消費產(chǎn)品時候,應(yīng)該通知等待的消費者去消費。
?
此模型將要結(jié)合java.lang.Object的wait與notify、notifyAll方法來實現(xiàn)以上的需求。這是非常重要的。
?
/**?
* Java線程:并發(fā)協(xié)作-生產(chǎn)者消費者模型
*?
* @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;????//當(dāng)前庫存量
????????Godown() {?
????????}?
????????Godown(int?curnum) {
????????????????this.curnum = curnum;
????????}?
????????/**?
???????? *?生產(chǎn)指定數(shù)量的產(chǎn)品
???????? *?
???????? * @param neednum?
???????? */?
????????publicsynchronizedvoid?produce(int?neednum) {
????????????????//測試是否需要生產(chǎn)
????????????????while?(neednum + curnum > max_size) {
????????????????????????System.out.println("要生產(chǎn)的產(chǎn)品數(shù)量"?+ neednum +"超過剩余庫存量"?+ (max_size - curnum) +",暫時不能執(zhí)行生產(chǎn)任務(wù)!");
????????????????????????try?{
????????????????????????????????//當(dāng)前的生產(chǎn)線程等待
????????????????????????????????wait();?
????????????????????????}?catch?(InterruptedException e) {
????????????????????????????????e.printStackTrace();?
????????????????????????}?
????????????????}?
????????????????//滿足生產(chǎn)條件,則進(jìn)行生產(chǎn),這里簡單的更改當(dāng)前庫存量
????????????????curnum += neednum;?
????????????????System.out.println("已經(jīng)生產(chǎn)了"?+ neednum +"個產(chǎn)品,現(xiàn)倉儲量為"?+ curnum);
????????????????//喚醒在此對象監(jiān)視器上等待的所有線程
????????????????notifyAll();?
????????}?
????????/**?
???????? *?消費指定數(shù)量的產(chǎn)品
???????? *?
???????? * @param neednum?
???????? */?
????????publicsynchronizedvoid?consume(int?neednum) {
????????????????//測試是否可消費
????????????????while?(curnum < neednum) {
????????????????????????try?{
????????????????????????????????//當(dāng)前的生產(chǎn)線程等待
????????????????????????????????wait();?
????????????????????????}?catch?(InterruptedException e) {
????????????????????????????????e.printStackTrace();?
????????????????????????}?
????????????????}?
????????????????//滿足消費條件,則進(jìn)行消費,這里簡單的更改當(dāng)前庫存量
????????????????curnum -= neednum;?
????????????????System.out.println("已經(jīng)消費了"?+ neednum +"個產(chǎn)品,現(xiàn)倉儲量為"?+ curnum);
????????????????//喚醒在此對象監(jiān)視器上等待的所有線程
????????????????notifyAll();?
????????}?
}?
/**?
*?生產(chǎn)者?
*/?
class?Producer?extends?Thread {?
????????privateint?neednum;????????????????//生產(chǎn)產(chǎn)品的數(shù)量
????????private?Godown godown;????????????//倉庫
????????Producer(int?neednum, Godown godown) {
????????????????this.neednum = neednum;
????????????????this.godown = godown;
????????}?
????????publicvoid?run() {
????????????????//生產(chǎn)指定數(shù)量的產(chǎn)品
????????????????godown.produce(neednum);?
????????}?
}?
/**?
*?消費者?
*/?
class?Consumer?extends?Thread {?
????????privateint?neednum;????????????????//生產(chǎn)產(chǎn)品的數(shù)量
????????private?Godown godown;????????????//倉庫
????????Consumer(int?neednum, Godown godown) {
????????????????this.neednum = neednum;
????????????????this.godown = godown;
????????}?
????????publicvoid?run() {
????????????????//消費指定數(shù)量的產(chǎn)品
????????????????godown.consume(neednum);?
????????}?
}
?
已經(jīng)生產(chǎn)了10個產(chǎn)品,現(xiàn)倉儲量為40
已經(jīng)生產(chǎn)了10個產(chǎn)品,現(xiàn)倉儲量為50
已經(jīng)消費了50個產(chǎn)品,現(xiàn)倉儲量為0
已經(jīng)生產(chǎn)了80個產(chǎn)品,現(xiàn)倉儲量為80
已經(jīng)消費了30個產(chǎn)品,現(xiàn)倉儲量為50
已經(jīng)生產(chǎn)了10個產(chǎn)品,現(xiàn)倉儲量為60
已經(jīng)消費了20個產(chǎn)品,現(xiàn)倉儲量為40
已經(jīng)生產(chǎn)了10個產(chǎn)品,現(xiàn)倉儲量為50
已經(jīng)生產(chǎn)了10個產(chǎn)品,現(xiàn)倉儲量為60
已經(jīng)生產(chǎn)了10個產(chǎn)品,現(xiàn)倉儲量為70
Process finished with exit code 0
?
說明:
對于本例,要說明的是當(dāng)發(fā)現(xiàn)不能滿足生產(chǎn)或者消費條件的時候,調(diào)用對象的wait方法,wait方法的作用是釋放當(dāng)前線程的所獲得的鎖,并調(diào)用對象的notifyAll()方法,通知(喚醒)該對象上其他等待線程,使得其繼續(xù)執(zhí)行。這樣,整個生產(chǎn)者、消費者線程得以正確的協(xié)作執(zhí)行。
notifyAll()?方法,起到的是一個通知作用,不釋放鎖,也不獲取鎖。只是告訴該對象上等待的線程“可以競爭執(zhí)行了,都醒來去執(zhí)行吧”。
?
本例僅僅是生產(chǎn)者消費者模型中最簡單的一種表示,本例中,如果消費者消費的倉儲量達(dá)不到滿足,而又沒有生產(chǎn)者,則程序會一直處于等待狀態(tài),這當(dāng)然是不對的。實際上可以將此例進(jìn)行修改,修改為,根據(jù)消費驅(qū)動生產(chǎn),同時生產(chǎn)兼顧倉庫,如果倉不滿就生產(chǎn),并對每次最大消費量做個限制,這樣就不存在此問題了,當(dāng)然這樣的例子更復(fù)雜,更難以說明這樣一個簡單模型。
?
我喜歡簡單的例子。
Java線程:并發(fā)協(xié)作-死鎖
線程發(fā)生死鎖可能性很小,即使看似可能發(fā)生死鎖的代碼,在運行時發(fā)生死鎖的可能性也是小之又小。
發(fā)生死鎖的原因一般是兩個對象的鎖相互等待造成的。
在《Java線程:線程的同步與鎖》一文中,簡述死鎖的概念與簡單例子,但是所給的例子是不完整的,這里給出一個完整的例子。
?
/**?
* Java線程:并發(fā)協(xié)作-死鎖
*?
* @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;?
????????????????????????}?
????????????????}?
????????}?
}
?
下面死鎖的情況發(fā)生了,真是難得一見啊:
?
?
Java線程:volatile關(guān)鍵字
Java??語言包含兩種內(nèi)在的同步機制:同步塊(或方法)和?volatile變量。這兩種機制的提出都是為了實現(xiàn)代碼線程的安全性。其中?Volatile變量的同步性較差(但有時它更簡單并且開銷更低),而且其使用也更容易出錯。
談及到volatile關(guān)鍵字,不得不提的一篇文章是:《Java理論與實踐:正確使用?Volatile?變量》,這篇文章對volatile關(guān)鍵字的用法做了相當(dāng)精辟的闡述。
之所以要單獨提出volatile這個不常用的關(guān)鍵字原因是這個關(guān)鍵字在高性能的多線程程序中也有很重要的用途,只是這個關(guān)鍵字用不好會出很多問題。
首先考慮一個問題,為什么變量需要volatile來修飾呢?
要搞清楚這個問題,首先應(yīng)該明白計算機內(nèi)部都做什么了。比如做了一個i++操作,計算機內(nèi)部做了三次處理:讀取-修改-寫入。
同樣,對于一個long型數(shù)據(jù),做了個賦值操作,在32系統(tǒng)下需要經(jīng)過兩步才能完成,先修改低32位,然后修改高32位。
假想一下,當(dāng)將以上的操作放到一個多線程環(huán)境下操作時候,有可能出現(xiàn)的問題,是這些步驟執(zhí)行了一部分,而另外一個線程就已經(jīng)引用了變量值,這樣就導(dǎo)致了讀取臟數(shù)據(jù)的問題。
通過這個設(shè)想,就不難理解volatile關(guān)鍵字了。
volatile可以用在任何變量前面,但不能用于final變量前面,因為final型的變量是禁止修改的。也不存在線程安全的問題。
更多的內(nèi)容,請參看::《Java理論與實踐:正確使用?Volatile?變量》一文,寫得很好。
Java線程:新特征-線程池
Sun在Java5中,對Java線程的類庫做了大量的擴(kuò)展,其中線程池就是Java5的新特征之一,除了線程池之外,還有很多多線程相關(guān)的內(nèi)容,為多線程的編程帶來了極大便利。為了編寫高效穩(wěn)定可靠的多線程程序,線程部分的新增內(nèi)容顯得尤為重要。
有關(guān)Java5線程新特征的內(nèi)容全部在java.util.concurrent下面,里面包含數(shù)目眾多的接口和類,熟悉這部分API特征是一項艱難的學(xué)習(xí)過程。目前有關(guān)這方面的資料和書籍都少之又少,大所屬介紹線程方面書籍還停留在java5之前的知識層面上。
當(dāng)然新特征對做多線程程序沒有必須的關(guān)系,在java5之前通用可以寫出很優(yōu)秀的多線程程序。只是代價不一樣而已。
線程池的基本思想還是一種對象池的思想,開辟一塊內(nèi)存空間,里面存放了眾多(未死亡)的線程,池中線程執(zhí)行調(diào)度由池管理器來處理。當(dāng)有線程任務(wù)時,從池中取一個,執(zhí)行完成后線程對象歸池,這樣可以避免反復(fù)創(chuàng)建線程對象所帶來的性能開銷,節(jié)省了系統(tǒng)的資源。
在Java5之前,要實現(xiàn)一個線程池是相當(dāng)有難度的,現(xiàn)在Java5為我們做好了一切,我們只需要按照提供的API來使用,即可享受線程池帶來的極大便利。
Java5的線程池分好多種:固定尺寸的線程池、可變尺寸連接池、。
在使用線程池之前,必須知道如何去創(chuàng)建一個線程池,在Java5中,需要了解的是java.util.concurrent.Executors類的API,這個類提供大量創(chuàng)建連接池的靜態(tài)方法,是必須掌握的。
一、固定大小的線程池
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) {
????????????????//創(chuàng)建一個可重用固定線程數(shù)的線程池
????????????????ExecutorService pool = Executors.newFixedThreadPool(2);?
????????????????//創(chuàng)建實現(xiàn)了Runnable接口對象,Thread對象當(dāng)然也實現(xiàn)了Runnable接口
????????????????Thread t1 =?new?MyThread();
????????????????Thread t2 =?new?MyThread();
????????????????Thread t3 =?new?MyThread();
????????????????Thread t4 =?new?MyThread();
????????????????Thread t5 =?new?MyThread();
????????????????//將線程放入池中進(jìn)行執(zhí)行
????????????????pool.execute(t1);?
????????????????pool.execute(t2);?
????????????????pool.execute(t3);?
????????????????pool.execute(t4);?
????????????????pool.execute(t5);?
????????????????//關(guān)閉線程池
????????????????pool.shutdown();?
????????}?
}?
class?MyThread?extends?Thread{?
????????@Override?
????????publicvoid?run() {
????????????????System.out.println(Thread.currentThread().getName()+"正在執(zhí)行。。。");
????????}?
}
?
pool-1-thread-1正在執(zhí)行。。。
pool-1-thread-1正在執(zhí)行。。。
pool-1-thread-1正在執(zhí)行。。。
pool-1-thread-1正在執(zhí)行。。。
pool-1-thread-2正在執(zhí)行。。。
Process finished with exit code 0
?
二、單任務(wù)線程池
?
在上例的基礎(chǔ)上改一行創(chuàng)建pool對象的代碼為:
????????????????//創(chuàng)建一個使用單個?worker線程的?Executor,以無界隊列方式來運行該線程。
????????????????ExecutorService pool = Executors.newSingleThreadExecutor();
?
輸出結(jié)果為:
pool-1-thread-1正在執(zhí)行。。。
pool-1-thread-1正在執(zhí)行。。。
pool-1-thread-1正在執(zhí)行。。。
pool-1-thread-1正在執(zhí)行。。。
pool-1-thread-1正在執(zhí)行。。。
Process finished with exit code 0
?
對于以上兩種連接池,大小都是固定的,當(dāng)要加入的池的線程(或者任務(wù))超過池最大尺寸時候,則入此線程池需要排隊等待。
一旦池中有線程完畢,則排隊等待的某個線程會入池執(zhí)行。
?
三、可變尺寸的線程池
與上面的類似,只是改動下pool的創(chuàng)建方式:
????????????????//創(chuàng)建一個可根據(jù)需要創(chuàng)建新線程的線程池,但是在以前構(gòu)造的線程可用時將重用它們。
????????????????ExecutorService pool = Executors.newCachedThreadPool();
?
pool-1-thread-5正在執(zhí)行。。。
pool-1-thread-1正在執(zhí)行。。。
pool-1-thread-4正在執(zhí)行。。。
pool-1-thread-3正在執(zhí)行。。。
pool-1-thread-2正在執(zhí)行。。。
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?
*/?
publicclass?Test {
????????publicstaticvoid?main(String[] args) {
????????????????//創(chuàng)建一個線程池,它可安排在給定延遲后運行命令或者定期地執(zhí)行。
????????????????ScheduledExecutorService pool = Executors.newScheduledThreadPool(2);
????????????????//創(chuàng)建實現(xiàn)了Runnable接口對象,Thread對象當(dāng)然也實現(xiàn)了Runnable接口
????????????????Thread t1 =?new?MyThread();
????????????????Thread t2 =?new?MyThread();
????????????????Thread t3 =?new?MyThread();
????????????????Thread t4 =?new?MyThread();
????????????????Thread t5 =?new?MyThread();
????????????????//將線程放入池中進(jìn)行執(zhí)行
????????????????pool.execute(t1);?
????????????????pool.execute(t2);?
????????????????pool.execute(t3);?
????????????????//使用延遲執(zhí)行風(fēng)格的方法
????????????????pool.schedule(t4, 10, TimeUnit.MILLISECONDS);?
????????????????pool.schedule(t5, 10, TimeUnit.MILLISECONDS);?
????????????????//關(guān)閉線程池
????????????????pool.shutdown();?
????????}?
}?
class?MyThread?extends?Thread {?
????????@Override?
????????publicvoid?run() {
????????????????System.out.println(Thread.currentThread().getName() +?"正在執(zhí)行。。。");
????????}?
}
?
pool-1-thread-1正在執(zhí)行。。。
pool-1-thread-2正在執(zhí)行。。。
pool-1-thread-1正在執(zhí)行。。。
pool-1-thread-1正在執(zhí)行。。。
pool-1-thread-2正在執(zhí)行。。。
Process finished with exit code 0
?
五、單任務(wù)延遲連接池
?
在四代碼基礎(chǔ)上,做改動
????????????????//創(chuàng)建一個單線程執(zhí)行程序,它可安排在給定延遲后運行命令或者定期地執(zhí)行。
????????????????ScheduledExecutorService pool = Executors.newSingleThreadScheduledExecutor();
?
pool-1-thread-1正在執(zhí)行。。。
pool-1-thread-1正在執(zhí)行。。。
pool-1-thread-1正在執(zhí)行。。。
pool-1-thread-1正在執(zhí)行。。。
pool-1-thread-1正在執(zhí)行。。。
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?
*/?
publicclass?Test {
????????publicstaticvoid?main(String[] args) {
????????????????//創(chuàng)建等待隊列
????????????????BlockingQueue<Runnable> bqueue =?newArrayBlockingQueue<Runnable>(20);
????????????????//創(chuàng)建一個單線程執(zhí)行程序,它可安排在給定延遲后運行命令或者定期地執(zhí)行。
????????????????ThreadPoolExecutor pool =?newThreadPoolExecutor(2,3,2,TimeUnit.MILLISECONDS,bqueue);
????????????????//創(chuàng)建實現(xiàn)了Runnable接口對象,Thread對象當(dāng)然也實現(xiàn)了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();
????????????????//將線程放入池中進(jìn)行執(zhí)行
????????????????pool.execute(t1);?
????????????????pool.execute(t2);?
????????????????pool.execute(t3);?
????????????????pool.execute(t4);?
????????????????pool.execute(t5);?
????????????????pool.execute(t6);?
????????????????pool.execute(t7);?
????????????????//關(guān)閉線程池
????????????????pool.shutdown();?
????????}?
}?
class?MyThread?extends?Thread {?
????????@Override?
????????publicvoid?run() {
????????????????System.out.println(Thread.currentThread().getName() +?"正在執(zhí)行。。。");
????????????????try?{
????????????????????????Thread.sleep(100L);?
????????????????}?catch?(InterruptedException e) {
????????????????????????e.printStackTrace();?
????????????????}?
????????}?
}
?
pool-1-thread-1正在執(zhí)行。。。
pool-1-thread-2正在執(zhí)行。。。
pool-1-thread-2正在執(zhí)行。。。
pool-1-thread-1正在執(zhí)行。。。
pool-1-thread-2正在執(zhí)行。。。
pool-1-thread-1正在執(zhí)行。。。
pool-1-thread-2正在執(zhí)行。。。
Process finished with exit code 0
?
創(chuàng)建自定義線程池的構(gòu)造方法很多,本例中參數(shù)的含義如下:
ThreadPoolExecutor
public?ThreadPoolExecutor(int?corePoolSize,
?????????????????????????int?maximumPoolSize,
?????????????????????????long?keepAliveTime,
??????????????????????????TimeUnit?unit,
?????????????????????????BlockingQueue<Runnable>?workQueue)
用給定的初始參數(shù)和默認(rèn)的線程工廠及處理程序創(chuàng)建新的ThreadPoolExecutor。使用Executors工廠方法之一比使用此通用構(gòu)造方法方便得多。
參數(shù):
corePoolSize?-池中所保存的線程數(shù),包括空閑線程。
maximumPoolSize?-池中允許的最大線程數(shù)。
keepAliveTime?-當(dāng)線程數(shù)大于核心時,此為終止前多余的空閑線程等待新任務(wù)的最長時間。
unit?- keepAliveTime參數(shù)的時間單位。
workQueue?-執(zhí)行前用于保持任務(wù)的隊列。此隊列僅保持由execute方法提交的Runnable任務(wù)。
拋出:
IllegalArgumentException?-如果?corePoolSize或?keepAliveTime小于零,或者?maximumPoolSize小于或等于零,或者?corePoolSize大于?maximumPoolSize。
NullPointerException?-如果workQueue為?null
?
自定義連接池稍微麻煩些,不過通過創(chuàng)建的ThreadPoolExecutor線程池對象,可以獲取到當(dāng)前線程池的尺寸、正在執(zhí)行任務(wù)的線程數(shù)、工作隊列等等。
?
有關(guān)Java5線程池的內(nèi)容到此就沒有了,更多的內(nèi)容還需要研讀API來獲取。
Java線程:新特征-有返回值的線程
在Java5之前,線程是沒有返回值的,常常為了“有”返回值,破費周折,而且代碼很不好寫。或者干脆繞過這道坎,走別的路了。
?
現(xiàn)在Java終于有可返回值的任務(wù)(也可以叫做線程)了。
?
可返回值的任務(wù)必須實現(xiàn)Callable接口,類似的,無返回值的任務(wù)必須Runnable接口。
?
執(zhí)行Callable任務(wù)后,可以獲取一個Future的對象,在該對象上調(diào)用get就可以獲取到Callable任務(wù)返回的Object了。
?
下面是個很簡單的例子:
?
import?java.util.concurrent.*;
/**?
* Java線程:有返回值的線程
*?
* @author Administrator 2009-11-5 0:41:50?
*/?
publicclass?Test {
????????publicstaticvoid?main(String[] args)throws?ExecutionException, InterruptedException {
????????????????//創(chuàng)建一個線程池
????????????????ExecutorService pool = Executors.newFixedThreadPool(2);?
????????????????//創(chuàng)建兩個有返回值的任務(wù)
????????????????Callable c1 =?new?MyCallable("A");
????????????????Callable c2 =?new?MyCallable("B");
????????????????//執(zhí)行任務(wù)并獲取Future對象
????????????????Future f1 = pool.submit(c1);?
????????????????Future f2 = pool.submit(c2);?
????????????????//從Future對象上獲取任務(wù)的返回值,并輸出到控制臺
????????????????System.out.println(">>>"+f1.get().toString());
????????????????System.out.println(">>>"+f2.get().toString());
????????????????//關(guān)閉線程池
????????????????pool.shutdown();?
????????}?
}?
class?MyCallableimplements?Callable{
????????private?String oid;
????????MyCallable(String oid) {?
????????????????this.oid = oid;
????????}?
????????@Override?
????????public?Object call()throws?Exception {
????????????????return?oid+"任務(wù)返回的內(nèi)容";
????????}?
}
?
>>>A任務(wù)返回的內(nèi)容
>>>B任務(wù)返回的內(nèi)容?
Process finished with exit code 0
?
非常的簡單,要深入了解還需要看Callable和Future接口的API啊。
Java線程:新特征-鎖(上)
在Java5中,專門提供了鎖對象,利用鎖可以方便的實現(xiàn)資源的封鎖,用來控制對競爭資源并發(fā)訪問的控制,這些內(nèi)容主要集中在java.util.concurrent.locks包下面,里面有三個重要的接口Condition、Lock、ReadWriteLock。
?
?
?
?
Condition
?
Condition將Object監(jiān)視器方法(wait、notify和?notifyAll)分解成截然不同的對象,以便通過將這些對象與任意Lock實現(xiàn)組合使用,為每個對象提供多個等待?set(wait-set)。
?
?
?
Lock
?
Lock實現(xiàn)提供了比使用synchronized方法和語句可獲得的更廣泛的鎖定操作。
?
?
ReadWriteLock
?
ReadWriteLock維護(hù)了一對相關(guān)的鎖定,一個用于只讀操作,另一個用于寫入操作。
?
?
有關(guān)鎖的介紹,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) {
????????????????//創(chuàng)建并發(fā)訪問的賬戶
????????????????MyCount myCount =?new?MyCount("95599200901215522", 10000);
????????????????//創(chuàng)建一個鎖對象
????????????????Lock lock =?new?ReentrantLock();
????????????????//創(chuàng)建一個線程池
????????????????ExecutorService pool = Executors.newCachedThreadPool();?
????????????????//創(chuàng)建一些并發(fā)訪問用戶,一個信用卡,存的存,取的取,好熱鬧啊
????????????????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);
????????????????//在線程池中執(zhí)行各個用戶的操作
????????????????pool.execute(u1);?
????????????????pool.execute(u2);?
????????????????pool.execute(u3);?
????????????????pool.execute(u4);?
????????????????//關(guān)閉線程池
????????????????pool.shutdown();?
????????}?
}?
/**?
*?信用卡的用戶?
*/?
class?User?implements?Runnable {?
????????private?String name;????????????????//用戶名
????????private?MyCount myCount;????????//所要操作的賬戶
????????privateint?iocash;????????????????//操作的金額,當(dāng)然有正負(fù)之分了
????????private?Lock myLock;????????????????//執(zhí)行操作所需的鎖對象
????????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();?
????????????????//執(zhí)行現(xiàn)金業(yè)務(wù)
????????????????System.out.println(name +?"正在操作"?+ myCount +"賬戶,金額為"?+ iocash +",當(dāng)前金額為"?+ myCount.getCash());
????????????????myCount.setCash(myCount.getCash() + iocash);?
????????????????System.out.println(name +?"操作"?+ myCount +"賬戶成功,金額為"?+ iocash +",當(dāng)前金額為"?+ myCount.getCash());
????????????????//釋放鎖,否則別的線程沒有機會執(zhí)行了
????????????????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,當(dāng)前金額為10000
張三操作MyCount{oid='95599200901215522', cash=6000}賬戶成功,金額為-4000,當(dāng)前金額為6000
張三他爹正在操作MyCount{oid='95599200901215522', cash=6000}賬戶,金額為6000,當(dāng)前金額為6000
張三他爹操作MyCount{oid='95599200901215522', cash=12000}賬戶成功,金額為6000,當(dāng)前金額為12000
張三他弟正在操作MyCount{oid='95599200901215522', cash=12000}賬戶,金額為-8000,當(dāng)前金額為12000
張三他弟操作MyCount{oid='95599200901215522', cash=4000}賬戶成功,金額為-8000,當(dāng)前金額為4000
張三正在操作MyCount{oid='95599200901215522', cash=4000}賬戶,金額為800,當(dāng)前金額為4000
張三操作MyCount{oid='95599200901215522', cash=4800}賬戶成功,金額為800,當(dāng)前金額為4800
Process finished with exit code 0
?
從上面的輸出可以看到,利用鎖對象太方便了,比直接在某個不知情的對象上用鎖清晰多了。
?
但一定要注意的是,在獲取了鎖對象后,用完后應(yīng)該盡快釋放鎖,以便別的等待該鎖的線程有機會去執(zhí)行。
Java線程:新特征-鎖(下)
在上文中提到了Lock接口以及對象,使用它,很優(yōu)雅的控制了競爭資源的安全訪問,但是這種鎖不區(qū)分讀寫,稱這種鎖為普通鎖。為了提高性能,Java提供了讀寫鎖,在讀的地方使用讀鎖,在寫的地方使用寫鎖,靈活控制,在一定程度上提高了程序的執(zhí)行效率。
?
Java中讀寫鎖有個接口java.util.concurrent.locks.ReadWriteLock,也有具體的實現(xiàn)ReentrantReadWriteLock,詳細(xì)的API可以查看JavaAPI文檔。
?
下面這個例子是在文例子的基礎(chǔ)上,將普通鎖改為讀寫鎖,并添加賬戶余額查詢的功能,代碼如下:
?
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) {
????????????????//創(chuàng)建并發(fā)訪問的賬戶
????????????????MyCount myCount =?new?MyCount("95599200901215522", 10000);
????????????????//創(chuàng)建一個鎖對象
????????????????ReadWriteLock lock =?new?ReentrantReadWriteLock(false);
????????????????//創(chuàng)建一個線程池
????????????????ExecutorService pool = Executors.newFixedThreadPool(2);?
????????????????//創(chuàng)建一些并發(fā)訪問用戶,一個信用卡,存的存,取的取,好熱鬧啊
????????????????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);?
????????????????//在線程池中執(zhí)行各個用戶的操作
????????????????pool.execute(u1);?
????????????????pool.execute(u2);?
????????????????pool.execute(u3);?
????????????????pool.execute(u4);?
????????????????pool.execute(u5);?
????????????????//關(guān)閉線程池
????????????????pool.shutdown();?
????????}?
}?
/**?
*?信用卡的用戶?
*/?
class?User?implements?Runnable {?
????????private?String name;????????????????//用戶名
????????private?MyCount myCount;????????//所要操作的賬戶
????????privateint?iocash;????????????????//操作的金額,當(dāng)然有正負(fù)之分了
????????private?ReadWriteLock myLock;????????????????//執(zhí)行操作所需的鎖對象
????????privateboolean?ischeck;????????//是否查詢
????????User(String name, MyCount myCount,?int?iocash, ReadWriteLock myLock,booleanischeck) {
????????????????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 +"賬戶,當(dāng)前金額為"?+ myCount.getCash());
????????????????????????//釋放讀鎖
????????????????????????myLock.readLock().unlock();?
????????????????}?else?{
????????????????????????//獲取寫鎖
????????????????????????myLock.writeLock().lock();?
????????????????????????//執(zhí)行現(xiàn)金業(yè)務(wù)
????????????????????????System.out.println("寫:"?+ name +"正在操作"?+ myCount +"賬戶,金額為"?+ iocash +",當(dāng)前金額為"?+ myCount.getCash());
????????????????????????myCount.setCash(myCount.getCash() + iocash);?
????????????????????????System.out.println("寫:"?+ name +"操作"?+ myCount +"賬戶成功,金額為"?+ iocash +",當(dāng)前金額為"?+ 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,當(dāng)前金額為10000
寫:張三操作MyCount{oid='95599200901215522', cash=6000}賬戶成功,金額為-4000,當(dāng)前金額為6000
寫:張三他弟正在操作MyCount{oid='95599200901215522', cash=6000}賬戶,金額為-8000,當(dāng)前金額為6000
寫:張三他弟操作MyCount{oid='95599200901215522', cash=-2000}賬戶成功,金額為-8000,當(dāng)前金額為-2000
寫:張三正在操作MyCount{oid='95599200901215522', cash=-2000}賬戶,金額為800,當(dāng)前金額為-2000
寫:張三操作MyCount{oid='95599200901215522', cash=-1200}賬戶成功,金額為800,當(dāng)前金額為-1200
讀:張三他爹正在查詢MyCount{oid='95599200901215522', cash=-1200}賬戶,當(dāng)前金額為-1200
寫:張三他爹正在操作MyCount{oid='95599200901215522', cash=-1200}賬戶,金額為6000,當(dāng)前金額為-1200
寫:張三他爹操作MyCount{oid='95599200901215522', cash=4800}賬戶成功,金額為6000,當(dāng)前金額為4800
Process finished with exit code 0
?
在實際開發(fā)中,最好在能用讀寫鎖的情況下使用讀寫鎖,而不要用普通鎖,以求更好的性能。
Java線程:新特征-信號量
Java的信號量實際上是一個功能完畢的計數(shù)器,對控制一定資源的消費與回收有著很重要的意義,信號量常常用于多線程的代碼中,并能監(jiān)控有多少數(shù)目的線程等待獲取資源,并且通過信號量可以得知可用資源的數(shù)目等等,這里總是在強調(diào)“數(shù)目”二字,但不能指出來有哪些在等待,哪些資源可用。
?
因此,本人認(rèn)為,這個信號量類如果能返回數(shù)目,還能知道哪些對象在等待,哪些資源可使用,就非常完美了,僅僅拿到這些概括性的數(shù)字,對精確控制意義不是很大。目前還沒想到更好的用法。
?
下面是一個簡單例子:
?
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);
????????????????//創(chuàng)建線程池
????????????????ExecutorService threadPool = Executors.newFixedThreadPool(2);?
????????????????MyThread t1 =?new?MyThread("任務(wù)A", myPool, 3);?
????????????????MyThread t2 =?new?MyThread("任務(wù)B", myPool, 12);?
????????????????MyThread t3 =?new?MyThread("任務(wù)C", myPool, 7);?
????????????????//在線程池中執(zhí)行任務(wù)
????????????????threadPool.execute(t1);?
????????????????threadPool.execute(t2);?
????????????????threadPool.execute(t3);?
????????????????//關(guān)閉池
????????????????threadPool.shutdown();?
????????}?
}?
/**?
*?一個池?
*/?
class?MyPool {?
????????private?Semaphore sp;????//池相關(guān)的信號量
????????/**?
???????? *?池的大小,這個大小會傳遞給信號量
???????? *?
???????? * @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;????????????????????????//自定義池
????????privateintx;????????????????????????????????????//申請信號量的大小
????????MyThread(String threadname, MyPool pool,?int?x) {
????????????????this.threadname = threadname;
????????????????this.pool = pool;
????????????????this.x = x;
????????}?
????????publicvoid?run() {
????????????????try?{
????????????????????????//從此信號量獲取給定數(shù)目的許可
????????????????????????pool.getSp().acquire(x);?
????????????????????????//todo:也許這里可以做更復(fù)雜的業(yè)務(wù)
????????????????????????System.out.println(threadname +?"成功獲取了"?+ x +"個許可!");
????????????????}?catch?(InterruptedException e) {
????????????????????????e.printStackTrace();?
????????????????}?finally?{
????????????????????????//釋放給定數(shù)目的許可,將其返回到信號量。
????????????????????????pool.getSp().release(x);?
????????????????????????System.out.println(threadname +?"釋放了"?+ x +"個許可!");
????????????????}?
????????}?
}
?
任務(wù)B成功獲取了12個許可!
任務(wù)B釋放了12個許可!
任務(wù)A成功獲取了3個許可!
任務(wù)C成功獲取了7個許可!
任務(wù)C釋放了7個許可!
任務(wù)A釋放了3個許可!
Process finished with exit code 0
?
從結(jié)果可以看出,信號量僅僅是對池資源進(jìn)行監(jiān)控,但不保證線程的安全,因此,在使用時候,應(yīng)該自己控制線程的安全訪問池資源。
?
?
Java線程:新特征-阻塞隊列
阻塞隊列是Java5線程新特征中的內(nèi)容,Java定義了阻塞隊列的接口java.util.concurrent.BlockingQueue,阻塞隊列的概念是,一個指定長度的隊列,如果隊列滿了,添加新元素的操作會被阻塞等待,直到有空位為止。同樣,當(dāng)隊列為空時候,請求隊列元素的操作同樣會阻塞等待,直到有可用元素為止。
?
有了這樣的功能,就為多線程的排隊等候的模型實現(xiàn)開辟了便捷通道,非常有用。
?
java.util.concurrent.BlockingQueue繼承了java.util.Queue接口,可以參看API文檔。
?
下面給出一個簡單應(yīng)用的例子:
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("程序到此運行結(jié)束,即將退出----");
????????}?
}
?
輸出結(jié)果:
向阻塞隊列中添加了元素:0
向阻塞隊列中添加了元素:1?
向阻塞隊列中添加了元素:2?
向阻塞隊列中添加了元素:3?
向阻塞隊列中添加了元素:4?
向阻塞隊列中添加了元素:5?
向阻塞隊列中添加了元素:6?
向阻塞隊列中添加了元素:7?
向阻塞隊列中添加了元素:8?
向阻塞隊列中添加了元素:9?
向阻塞隊列中添加了元素:10?
向阻塞隊列中添加了元素:11?
向阻塞隊列中添加了元素:12?
向阻塞隊列中添加了元素:13?
向阻塞隊列中添加了元素:14?
向阻塞隊列中添加了元素:15?
向阻塞隊列中添加了元素:16?
向阻塞隊列中添加了元素:17?
向阻塞隊列中添加了元素:18?
向阻塞隊列中添加了元素:19
?
可以看出,輸出到元素19時候,就一直處于等待狀態(tài),因為隊列滿了,程序阻塞了。
?
這里沒有用多線程來演示,沒有這個必要。
?
另外,阻塞隊列還有更多實現(xiàn)類,用來滿足各種復(fù)雜的需求:ArrayBlockingQueue, DelayQueue, LinkedBlockingQueue, PriorityBlockingQueue, SynchronousQueue,具體的API差別也很小。
Java線程:新特征-阻塞棧
對于阻塞棧,與阻塞隊列相似。不同點在于棧是“后入先出”的結(jié)構(gòu),每次操作的是棧頂,而隊列是“先進(jìn)先出”的結(jié)構(gòu),每次操作的是隊列頭。
?
這里要特別說明一點的是,阻塞棧是Java6的新特征。、
?
Java為阻塞棧定義了接口:java.util.concurrent.BlockingDeque,其實現(xiàn)類也比較多,具體可以查看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("程序到此運行結(jié)束,即將退出----");
????????}?
}
?
輸出結(jié)果:
向阻塞棧中添加了元素:0
向阻塞棧中添加了元素:1?
向阻塞棧中添加了元素:2?
向阻塞棧中添加了元素:3?
向阻塞棧中添加了元素:4?
向阻塞棧中添加了元素:5?
向阻塞棧中添加了元素:6?
向阻塞棧中添加了元素:7?
向阻塞棧中添加了元素:8?
向阻塞棧中添加了元素:9?
向阻塞棧中添加了元素:10?
向阻塞棧中添加了元素:11?
向阻塞棧中添加了元素:12?
向阻塞棧中添加了元素:13?
向阻塞棧中添加了元素:14?
向阻塞棧中添加了元素:15?
向阻塞棧中添加了元素:16?
向阻塞棧中添加了元素:17?
向阻塞棧中添加了元素:18?
向阻塞棧中添加了元素:19
?
從上面結(jié)果可以看到,程序并沒結(jié)束,二是阻塞住了,原因是棧已經(jīng)滿了,后面追加元素的操作都被阻塞了。
Java線程:新特征-條件變量
條件變量是Java5線程中很重要的一個概念,顧名思義,條件變量就是表示條件的一種變量。但是必須說明,這里的條件是沒有實際含義的,僅僅是個標(biāo)記而已,并且條件的含義往往通過代碼來賦予其含義。
?
這里的條件和普通意義上的條件表達(dá)式有著天壤之別。
?
條件變量都實現(xiàn)了java.util.concurrent.locks.Condition接口,條件變量的實例化是通過一個Lock對象上調(diào)用newCondition()方法來獲取的,這樣,條件就和一個鎖對象綁定起來了。因此,Java中的條件變量只能和鎖配合使用,來控制并發(fā)程序訪問競爭資源的安全。
?
條件變量的出現(xiàn)是為了更精細(xì)控制線程等待與喚醒,在Java5之前,線程的等待與喚醒依靠的是Object對象的wait()和notify()/notifyAll()方法,這樣的處理不夠精細(xì)。
?
而在Java5中,一個鎖可以有多個條件,每個條件上可以有多個線程等待,通過調(diào)用await()方法,可以讓線程在該條件下等待。當(dāng)調(diào)用signalAll()方法,又可以喚醒該條件下的等待的線程。有關(guān)Condition接口的API可以具體參考JavaAPI文檔。
?
條件變量比較抽象,原因是他不是自然語言中的條件概念,而是程序控制的一種手段。
?
下面以一個銀行存取款的模擬程序為例來揭蓋Java多線程條件變量的神秘面紗:
?
有一個賬戶,多個用戶(線程)在同時操作這個賬戶,有的存款有的取款,存款隨便存,取款有限制,不能透支,任何試圖透支的操作都將等待里面有足夠存款才執(zhí)行操作。
?
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) {
????????????????//創(chuàng)建并發(fā)訪問的賬戶
????????????????MyCount myCount =?new?MyCount("95599200901215522", 10000);
????????????????//創(chuàng)建一個線程池
????????????????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);?
????????????????//執(zhí)行各個線程
????????????????pool.execute(t1);?
????????????????pool.execute(t2);?
????????????????pool.execute(t3);?
????????????????pool.execute(t4);?
????????????????pool.execute(t5);?
????????????????pool.execute(t6);?
????????????????//關(guān)閉線程池
????????????????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 +",當(dāng)前余額為"?+ 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 +",當(dāng)前余額為"?+ cash);
????????????????????????}?
????????????????????????_save.signalAll();?????????????//喚醒所有存款操作
????????????????}?catch?(InterruptedException e) {
????????????????????????e.printStackTrace();?
????????????????}?finally?{
????????????????????????lock.unlock();?????????????????????//釋放鎖
????????????????}?
????????}?
}
?
?
李四存款3600,當(dāng)前余額為13600
張三存款2000,當(dāng)前余額為15600
老張存款600,當(dāng)前余額為16200
老牛取款1300,當(dāng)前余額為14900
胖子取款800,當(dāng)前余額為14100
王五取款2700,當(dāng)前余額為11400
Process finished with exit code 0
?
假如我們不用鎖和條件變量,如何實現(xiàn)此功能呢?下面是實現(xiàn)代碼:
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) {
????????????????//創(chuàng)建并發(fā)訪問的賬戶
????????????????MyCount myCount =?new?MyCount("95599200901215522", 10000);
????????????????//創(chuàng)建一個線程池
????????????????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);?
????????????????//執(zhí)行各個線程
????????????????pool.execute(t1);?
????????????????pool.execute(t2);?
????????????????pool.execute(t3);?
????????????????pool.execute(t4);?
????????????????pool.execute(t5);?
????????????????pool.execute(t6);?
????????????????//關(guān)閉線程池
????????????????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 +",當(dāng)前余額為"?+ 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 +",當(dāng)前余額為"?+ cash);
????????????????}?
????????????????notifyAll();?????????????//喚醒所有存款操作
????????}?
}
?
輸出結(jié)果為:
李四存款3600,當(dāng)前余額為13600
王五取款2700,當(dāng)前余額為10900
老張存款600,當(dāng)前余額為11500
老牛取款1300,當(dāng)前余額為10200
胖子取款800,當(dāng)前余額為9400
張三存款2000,當(dāng)前余額為11400
Process finished with exit code 0
?
結(jié)合先前同步代碼知識,舉一反三,將此例改為同步代碼塊來實現(xiàn),代碼如下:
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) {
????????????????//創(chuàng)建并發(fā)訪問的賬戶
????????????????MyCount myCount =?new?MyCount("95599200901215522", 10000);
????????????????//創(chuàng)建一個線程池
????????????????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);?
????????????????//執(zhí)行各個線程
????????????????pool.execute(t1);?
????????????????pool.execute(t2);?
????????????????pool.execute(t3);?
????????????????pool.execute(t4);?
????????????????pool.execute(t5);?
????????????????pool.execute(t6);?
????????????????//關(guān)閉線程池
????????????????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 +",當(dāng)前余額為"?+ 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 +",當(dāng)前余額為"?+ cash);
????????????????????????}?
????????????????}?
????????????????notifyAll();?????????????//喚醒所有存款操作
????????}?
}
?
李四存款3600,當(dāng)前余額為13600
王五取款2700,當(dāng)前余額為10900
老張存款600,當(dāng)前余額為11500
老牛取款1300,當(dāng)前余額為10200
胖子取款800,當(dāng)前余額為9400
張三存款2000,當(dāng)前余額為11400
Process finished with exit code 0
?
對比以上三種方式,從控制角度上講,第一種最靈活,第二種代碼最簡單,第三種容易犯錯。
Java線程:新特征-原子量
所謂的原子量即操作變量的操作是“原子的”,該操作不可再分,因此是線程安全的。
為何要使用原子變量呢,原因是多個線程對單個變量操作也會引起一些問題。在Java5之前,可以通過volatile、synchronized關(guān)鍵字來解決并發(fā)訪問的安全問題,但這樣太麻煩。
Java5之后,專門提供了用來進(jìn)行單變量多線程并發(fā)安全訪問的工具包java.util.concurrent.atomic,其中的類也很簡單。
下面給出一個反面例子(切勿模仿):
1 package com.bmw.mms.test; 2 3 import java.util.concurrent.ExecutorService; 4 import java.util.concurrent.Executors; 5 import java.util.concurrent.atomic.AtomicLong; 6 7 /** 8 * Java線程:新特征-原子量 9 */ 10 public class format { 11 public static void main(String[] args) { 12 ExecutorService pool = Executors.newFixedThreadPool(2); 13 Runnable t1 = new MyRunnable("張三", 2000); 14 Runnable t2 = new MyRunnable("李四", 3600); 15 Runnable t3 = new MyRunnable("王五", 2700); 16 Runnable t4 = new MyRunnable("老張", 600); 17 Runnable t5 = new MyRunnable("老牛", 1300); 18 Runnable t6 = new MyRunnable("胖子", 800); 19 // 執(zhí)行各個線程 20 pool.execute(t1); 21 pool.execute(t2); 22 pool.execute(t3); 23 pool.execute(t4); 24 pool.execute(t5); 25 pool.execute(t6); 26 // 關(guān)閉線程池 27 pool.shutdown(); 28 } 29 } 30 31 class MyRunnable implements Runnable { 32 private static AtomicLong aLong = new AtomicLong(10000); // 原子量,每個線程都可以自由操作 33 private String name; // 操作人 34 private int x; // 操作數(shù)額 35 36 MyRunnable(String name, int x) { 37 this.name = name; 38 this.x = x; 39 } 40 41 public void run() { 42 System.out.println(name + "執(zhí)行了" + x + ",當(dāng)前余額:" + aLong.addAndGet(x)); 43 } 44 }?
運行結(jié)果:
李四執(zhí)行了3600,當(dāng)前余額:13600
王五執(zhí)行了2700,當(dāng)前余額:16300
老張執(zhí)行了600,當(dāng)前余額:16900
老牛執(zhí)行了1300,當(dāng)前余額:18200
胖子執(zhí)行了800,當(dāng)前余額:19000
張三執(zhí)行了2000,當(dāng)前余額:21000
Process finished with exit code 0
?
張三執(zhí)行了2000,當(dāng)前余額:12000
王五執(zhí)行了2700,當(dāng)前余額:18300
老張執(zhí)行了600,當(dāng)前余額:18900
老牛執(zhí)行了1300,當(dāng)前余額:20200
胖子執(zhí)行了800,當(dāng)前余額:21000
李四執(zhí)行了3600,當(dāng)前余額:15600
Process finished with exit code 0
?
張三執(zhí)行了2000,當(dāng)前余額:12000
李四執(zhí)行了3600,當(dāng)前余額:15600
老張執(zhí)行了600,當(dāng)前余額:18900
老牛執(zhí)行了1300,當(dāng)前余額:20200
胖子執(zhí)行了800,當(dāng)前余額:21000
王五執(zhí)行了2700,當(dāng)前余額:18300
Process finished with exit code 0
?
從運行結(jié)果可以看出,雖然使用了原子量,但是程序并發(fā)訪問還是有問題,那究竟問題出在哪里了?
?
這里要注意的一點是,原子量雖然可以保證單個變量在某一個操作過程的安全,但無法保證你整個代碼塊,或者整個程序的安全性。因此,通常還應(yīng)該使用鎖等同步機制來控制整個程序的安全性。
?
下面是對這個錯誤修正:
1 import java.util.concurrent.ExecutorService; 2 import java.util.concurrent.Executors; 3 import java.util.concurrent.locks.Lock; 4 import java.util.concurrent.locks.ReentrantLock; 5 import java.util.concurrent.atomic.AtomicLong; 6 7 /** 8 * Java線程:新特征-原子量 9 */ 10 public class Test { 11 public static void main(String[] args) { 12 ExecutorService pool = Executors.newFixedThreadPool(2); 13 Lock lock = new ReentrantLock(false); 14 Runnable t1 = new MyRunnable("張三", 2000,lock); 15 Runnable t2 = new MyRunnable("李四", 3600,lock); 16 Runnable t3 = new MyRunnable("王五", 2700,lock); 17 Runnable t4 = new MyRunnable("老張", 600,lock); 18 Runnable t5 = new MyRunnable("老牛", 1300,lock); 19 Runnable t6 = new MyRunnable("胖子", 800,lock); 20 //執(zhí)行各個線程 21 pool.execute(t1); 22 pool.execute(t2); 23 pool.execute(t3); 24 pool.execute(t4); 25 pool.execute(t5); 26 pool.execute(t6); 27 //關(guān)閉線程池 28 pool.shutdown(); 29 } 30 } 31 32 class MyRunnable implements Runnable { 33 private static AtomicLong aLong =new AtomicLong(10000); //原子量,每個線程都可以自由操作 34 private String name; //操作人 35 privateint x; //操作數(shù)額 36 private Lock lock; 37 38 MyRunnable(String name, int x,Lock lock) { 39 this.name = name; 40 this.x = x; 41 this.lock = lock; 42 } 43 44 public void run() { 45 lock.lock(); 46 System.out.println(name + "執(zhí)行了" + x +",當(dāng)前余額:" + aLong.addAndGet(x)); 47 lock.unlock(); 48 } 49 }?
執(zhí)行結(jié)果:
張三執(zhí)行了2000,當(dāng)前余額:12000
王五執(zhí)行了2700,當(dāng)前余額:14700
老張執(zhí)行了600,當(dāng)前余額:15300
老牛執(zhí)行了1300,當(dāng)前余額:16600
胖子執(zhí)行了800,當(dāng)前余額:17400
李四執(zhí)行了3600,當(dāng)前余額:21000
Process finished with exit code 0
這里使用了一個對象鎖,來控制對并發(fā)代碼的訪問。不管運行多少次,執(zhí)行次序如何,最終余額均為21000,這個結(jié)果是正確的。
有關(guān)原子量的用法很簡單,關(guān)鍵是對原子量的認(rèn)識,原子僅僅是保證變量操作的原子性,但整個程序還需要考慮線程安全的。
Java線程:新特征-障礙器
Java5中,添加了障礙器類,為了適應(yīng)一種新的設(shè)計需求,比如一個大型的任務(wù),常常需要分配好多子任務(wù)去執(zhí)行,只有當(dāng)所有子任務(wù)都執(zhí)行完成時候,才能執(zhí)行主任務(wù),這時候,就可以選擇障礙器了。
障礙器是多線程并發(fā)控制的一種手段,用法很簡單。下面給個例子:
1 import java.util.concurrent.BrokenBarrierException; 2 import java.util.concurrent.CyclicBarrier; 3 4 /** 5 * Java線程:新特征-障礙器 6 */ 7 public class Test { 8 public static void main(String[] args) { 9 //創(chuàng)建障礙器,并設(shè)置MainTask為所有定數(shù)量的線程都達(dá)到障礙點時候所要執(zhí)行的任務(wù)(Runnable) 10 CyclicBarrier cb = new CyclicBarrier(7,new MainTask()); 11 new SubTask("A", cb).start(); 12 new SubTask("B", cb).start(); 13 new SubTask("C", cb).start(); 14 new SubTask("D", cb).start(); 15 new SubTask("E", cb).start(); 16 new SubTask("F", cb).start(); 17 new SubTask("G", cb).start(); 18 } 19 } 20 21 /** 22 * 主任務(wù) 23 */ 24 class MainTask implements Runnable { 25 public void run() { 26 System.out.println(">>>>主任務(wù)執(zhí)行了!<<<<"); 27 } 28 } 29 30 /** 31 * 子任務(wù) 32 */ 33 class SubTask extends Thread { 34 private String name; 35 private CyclicBarrier cb; 36 37 SubTask(String name, CyclicBarrier cb) { 38 this.name = name; 39 this.cb = cb; 40 } 41 42 public void run() { 43 System.out.println("[子任務(wù)" + name +"]開始執(zhí)行了!"); 44 for (int i = 0; i < 999999; i++) ; //模擬耗時的任務(wù) 45 System.out.println("[子任務(wù)" + name +"]開始執(zhí)行完成了,并通知障礙器已經(jīng)完成!"); 46 try { 47 //通知障礙器已經(jīng)完成 48 cb.await(); 49 } catch (InterruptedException e) { 50 e.printStackTrace(); 51 } catch (BrokenBarrierException e) { 52 e.printStackTrace(); 53 } 54 } 55 }運行結(jié)果:
[子任務(wù)E]開始執(zhí)行了!
[子任務(wù)E]開始執(zhí)行完成了,并通知障礙器已經(jīng)完成!
[子任務(wù)F]開始執(zhí)行了!
[子任務(wù)G]開始執(zhí)行了!
[子任務(wù)F]開始執(zhí)行完成了,并通知障礙器已經(jīng)完成!
[子任務(wù)G]開始執(zhí)行完成了,并通知障礙器已經(jīng)完成!
[子任務(wù)C]開始執(zhí)行了!
[子任務(wù)B]開始執(zhí)行了!
[子任務(wù)C]開始執(zhí)行完成了,并通知障礙器已經(jīng)完成!
[子任務(wù)D]開始執(zhí)行了!
[子任務(wù)A]開始執(zhí)行了!
[子任務(wù)D]開始執(zhí)行完成了,并通知障礙器已經(jīng)完成!
[子任務(wù)B]開始執(zhí)行完成了,并通知障礙器已經(jīng)完成!
[子任務(wù)A]開始執(zhí)行完成了,并通知障礙器已經(jīng)完成!
>>>>主任務(wù)執(zhí)行了!<<<<
Process finished with exit code 0
從執(zhí)行結(jié)果可以看出,所有子任務(wù)完成的時候,主任務(wù)執(zhí)行了,達(dá)到了控制的目標(biāo)。
轉(zhuǎn)載于:https://www.cnblogs.com/bpynfy/p/5548818.html
總結(jié)
- 上一篇: jquery.form.js的ajaxS
- 下一篇: JavaMail--发送html邮件