一直不敢寫點什么,是因為戰戰兢兢,生怕寫的不好甚至寫錯了會誤人子弟。隨筆可以隨便寫一下,不用太過計較,可是技術從來都要不得半點馬虎,差之毫厘,謬以千里啊!但敝帚自珍又不是我的風格,雖然文筆不好,也要勉為其難了。廢話少說,進入正題。
從我開始接觸Java的多線程起就總是覺得書上講的不是那么清楚。不是說讀完了不會寫,而是對寫出來的多線程代碼懵懵懂懂,不知道每一句會有什么影響,心里感覺忐忑。后來仔細研讀Java語言規范后,才慢慢搞明白一些細節。我主要想說的,也就是這些經驗吧。
首先要搞清楚的是線程的共享資源,共享資源是多線程中每個線程都要訪問的類變量或實例變量,共享資源可以是單個類變量或實例變量,也可以是一組類變量或實例變量。多線程程序可以有多個共享資源。下面描述他們之間的一對多關系(*表示多):
多線程程序(1)----共享資源(*)----類變量或實例變量(1…*)
只有類變量和實例變量可以成為共享資源,細分如下:
1.
實現線程的類(繼承Thread類、實現Runnable接口的類)的類變量、實例變量。
2.
實現線程的類的類變量、實例變量的類變量、實例變量,可以不規范的寫為:TreadClass.ClassOrInstanceVar[.ClassOrInstanceVar]*,[]*的內容表示無限可重復。
3.
不是實現線程的類,但其對象可能是線程的類變量或實例變量。如Servlet、EJB。這些類的類變量和實例變量,不規范的寫為:ServletOrEJB.ClassOrInstanceVar[.ClassOrInstanceVar]*。
4.
特別注意:局部變量、做為參數傳遞的非類變量、非實例變量不是共享資源。
那么什么是線程安全呢?關于這個問題我在網上百度了一下(沒辦法,有時候GOOGLE用不了),發現不少人在問這個問題,也有不少錯誤的理解。所以我給出一個較容易理解的解釋:在線程中使用共享資源時,能夠保證共享資源在任何時候都是原子的、一致的,這樣的線程就是線程安全的線程。還不太理解?沒有關系,慢慢解釋。
首先來介紹一下共享資源的類型(這是我自己分類的,為了后文好解釋),共享資源從其類型可以分為三類(下文講到變量一律指類變量或實例變量,不再特別指出):
1.
獨立的基本類型共享資源,如一個簡單的int變量,例:
public class
Cls1 {
private int a;
public int getA(){return a;}
public void setA(int a){this.a = a;}
}
可以看到a沒有任何依賴。
public class
Cls2{
private int a;
private int b;
private int c;
// 沒有對a的訪問方法,a在Cls外不可見。
}
假設上面類中b、c都不依賴a,則a是這種類型。
2.
相互依賴的基本類型共享資源,一個類中的幾個基本類型變量互相依賴,但從對象設計的角度又不能單獨把這幾個變量設計成一個類。
假設上例Cls2中的b、c互相依賴,則屬此種情況。
3.
64位的基本類型變量。這個比較特殊,因為某些機器上64變量會分成兩個32位的操作,所以和1不一樣。如double、long類型。
4.
類類型的共享資源。如下例中的obj:
public class
Cls3{
private SomeObj obj;
}
public class
SomeObj{
private int a;
private int b;
}
其次來看看什么是原子性、一致性。其實在這里我借用了事務ACID屬性的A和C,熟悉的朋友就不用我廢話了。所謂原子性,是指一個共享資源的所有屬性在任何時刻都是一起變化、密不可分的;所謂一致性,是指一個共享資源的所有屬性在變化之后一定會達到一個一致的狀態。
最后根據上述四種共享資源類型,來看看如何做到線程安全。
1.
不用做什么,只一個獨立的變量,任何時候它都是原子、一致的。
2.
使用synchronized關鍵字,保證幾個變量被一起修改、一起讀取。
3.
使用volatile關鍵字,然后就和1一樣了。
4.
和2一樣處理。
當對訪問共享資源的方法不同時使用synchronized關鍵字時,是什么樣一種情況呢?這是需要特別注意的,這樣不能保證線程安全!看看下面例子的運行結果就知道了(自己運行啊,我不貼結果了):
/**
* $Author: $
* $Date: $
* $Revision: $
* $History: $
*
* Created by feelyou, at time
22:31:53, 2005-11-16.
*/
public class TestThread extends Thread {
private int a = 0;
private int b = 0;
public static void
main(String[] args) {
TestThread test = new
TestThread();
for (int i = 0; i < 10;
i++) {
Thread thread = new
Thread(test, "thread-" + i);
thread.start();
}
}
public synchronized void
doWrite() {
a++;
try {
sleep((int)(Math.random()*100));
}
catch
(InterruptedException e) {
}
b++;
try {
sleep((int)(Math.random()*100));
}
catch
(InterruptedException e) {
}
}
public void print() {
System.out.println("" + Thread.currentThread().getName() +
":a:" + a);
System.out.println("" + Thread.currentThread().getName() +
":b:" + b);
}
public void run() {
super.run(); //To change body of overridden methods use
File | Settings | File Templates.
for (int i = 0; i < 10;
i++) {
doWrite();
print();
}
}
public synchronized void
start() {
super.start(); //To change body of overridden methods use
File | Settings | File Templates.
}
}
ThreadLocal?ThreadLocal對于線程安全還是很有用的,如果資源不是共享的,那么應該使用ThreadLocal,但如果確實需要在線程間共享資源,ThreadLocal就沒有用了!
最后,來一個完整的線程安全的例子:
/**
* $Author: $
* $Date: $
* $Revision: $
* $History: $
*
* Created by feelyou, at time
22:31:53, 2005-11-16.
*/
public class TestThread extends Thread {
private int a = 0; //獨立的共享資源
private int b = 0; //b、c互相依賴
private int c = 0;
private volatile long d =
0L; //64位
// private SomeObj obj = new
SomeObj(); //對象類型,大家自己寫吧,我就不寫了。
public static void
main(String[] args) {
TestThread test = new
TestThread();
for (int i = 0; i < 10;
i++) {
Thread thread = new
Thread(test, "thread-" + i);
thread.start();
}
}
public synchronized void
doWrite() {
b++;
try {
sleep((int)(Math.random()*100));
}
catch
(InterruptedException e) {
}
c++;
try {
sleep((int)(Math.random()*100));
}
catch
(InterruptedException e) {
}
}
public synchronized void
print() {
System.out.println("" + Thread.currentThread().getName() +
":b:" + b);
System.out.println("" + Thread.currentThread().getName() +
":c:" + c);
}
private void setA(int a) {
this.a = a;
}
private int getA() {
return a;
}
public long getD() {
return d;
}
public void setD(long d) {
this.d = d;
}
public void run() {
super.run(); //To change body of overridden methods use
File | Settings | File Templates.
for (int i = 0; i < 10;
i++) {
doWrite();
print();
setA(i);
System.out.println(getA());
setD(18456187413L * i);
System.out.println(getD());
}
}
public synchronized void
start() {
super.start(); //To change body of overridden methods use
File | Settings | File Templates.
}
}
寫的比較匆忙,如果有錯誤,還請大家指正,謝謝!