一直不敢寫(xiě)點(diǎn)什么,是因為戰戰兢兢,生怕寫(xiě)的不好甚至寫(xiě)錯了會(huì )誤人子弟。隨筆可以隨便寫(xiě)一下,不用太過(guò)計較,可是技術(shù)從來(lái)都要不得半點(diǎn)馬虎,差之毫厘,謬以千里??!但敝帚自珍又不是我的風(fēng)格,雖然文筆不好,也要勉為其難了。廢話(huà)少說(shuō),進(jìn)入正題。
從我開(kāi)始接觸Java的多線(xiàn)程起就總是覺(jué)得書(shū)上講的不是那么清楚。不是說(shuō)讀完了不會(huì )寫(xiě),而是對寫(xiě)出來(lái)的多線(xiàn)程代碼懵懵懂懂,不知道每一句會(huì )有什么影響,心里感覺(jué)忐忑。后來(lái)仔細研讀Java語(yǔ)言規范后,才慢慢搞明白一些細節。我主要想說(shuō)的,也就是這些經(jīng)驗吧。
首先要搞清楚的是線(xiàn)程的共享資源,共享資源是多線(xiàn)程中每個(gè)線(xiàn)程都要訪(fǎng)問(wèn)的類(lèi)變量或實(shí)例變量,共享資源可以是單個(gè)類(lèi)變量或實(shí)例變量,也可以是一組類(lèi)變量或實(shí)例變量。多線(xiàn)程程序可以有多個(gè)共享資源。下面描述他們之間的一對多關(guān)系(*表示多):
多線(xiàn)程程序(1)----共享資源(*)----類(lèi)變量或實(shí)例變量(1…*)
只有類(lèi)變量和實(shí)例變量可以成為共享資源,細分如下:
<!--[if !supportLists]-->1. <!--[endif]-->實(shí)現線(xiàn)程的類(lèi)(繼承Thread類(lèi)、實(shí)現Throwable接口的類(lèi))的類(lèi)變量、實(shí)例變量。
<!--[if !supportLists]-->2. <!--[endif]-->實(shí)現線(xiàn)程的類(lèi)的類(lèi)變量、實(shí)例變量的類(lèi)變量、實(shí)例變量,可以不規范的寫(xiě)為:TreadClass.ClassOrInstanceVar[.ClassOrInstanceVar]*,[]*的內容表示無(wú)限可重復。
<!--[if !supportLists]-->3. <!--[endif]-->不是實(shí)現線(xiàn)程的類(lèi),但其對象可能是線(xiàn)程的類(lèi)變量或實(shí)例變量。如Servlet、EJB。這些類(lèi)的類(lèi)變量和實(shí)例變量,不規范的寫(xiě)為:ServletOrEJB.ClassOrInstanceVar[.ClassOrInstanceVar]*。
<!--[if !supportLists]-->4. <!--[endif]-->特別注意:局部變量、做為參數傳遞的非類(lèi)變量、非實(shí)例變量不是共享資源。
那么什么是線(xiàn)程安全呢?關(guān)于這個(gè)問(wèn)題我在網(wǎng)上百度了一下(沒(méi)辦法,有時(shí)候GOOGLE用不了),發(fā)現不少人在問(wèn)這個(gè)問(wèn)題,也有不少錯誤的理解。所以我給出一個(gè)較容易理解的解釋?zhuān)?strong>在線(xiàn)程中使用共享資源時(shí),能夠保證共享資源在任何時(shí)候都是原子的、一致的,這樣的線(xiàn)程就是線(xiàn)程安全的線(xiàn)程。還不太理解?沒(méi)有關(guān)系,慢慢解釋。
首先來(lái)介紹一下共享資源的類(lèi)型(這是我自己分類(lèi)的,為了后文好解釋?zhuān)?,共享資源從其類(lèi)型可以分為三類(lèi)(下文講到變量一律指類(lèi)變量或實(shí)例變量,不再特別指出):
<!--[if !supportLists]-->1. <!--[endif]-->獨立的基本類(lèi)型共享資源,如一個(gè)簡(jiǎn)單的int變量,例:
public class Cls1 {
private int a;
public int getA(){return a;}
public void setA(int a){this.a = a;}
}
可以看到a沒(méi)有任何依賴(lài)。
public class Cls2{
private int a;
private int b;
private int c;
// 沒(méi)有對a的訪(fǎng)問(wèn)方法,a在Cls外不可見(jiàn)。
}
假設上面類(lèi)中b、c都不依賴(lài)a,則a是這種類(lèi)型。
<!--[if !supportLists]-->2. <!--[endif]-->相互依賴(lài)的基本類(lèi)型共享資源,一個(gè)類(lèi)中的幾個(gè)基本類(lèi)型變量互相依賴(lài),但從對象設計的角度又不能單獨把這幾個(gè)變量設計成一個(gè)類(lèi)。
假設上例Cls2中的b、c互相依賴(lài),則屬此種情況。
<!--[if !supportLists]-->3. <!--[endif]-->64位的基本類(lèi)型變量。這個(gè)比較特殊,因為某些機器上64變量會(huì )分成兩個(gè)32位的操作,所以和1不一樣。如double、long類(lèi)型。
<!--[if !supportLists]-->4. <!--[endif]-->類(lèi)類(lèi)型的共享資源。如下例中的obj:
public class Cls3{
private SomeObj obj;
}
public class SomeObj{
private int a;
private int b;
}
其次來(lái)看看什么是原子性、一致性。其實(shí)在這里我借用了事務(wù)ACID屬性的A和C,熟悉的朋友就不用我廢話(huà)了。所謂原子性,是指一個(gè)共享資源的所有屬性在任何時(shí)刻都是一起變化、密不可分的;所謂一致性,是指一個(gè)共享資源的所有屬性在變化之后一定會(huì )達到一個(gè)一致的狀態(tài)。
最后根據上述四種共享資源類(lèi)型,來(lái)看看如何做到線(xiàn)程安全。
<!--[if !supportLists]-->1. <!--[endif]-->不用做什么,只一個(gè)獨立的變量,任何時(shí)候它都是原子、一致的。
<!--[if !supportLists]-->2. <!--[endif]-->使用synchronized關(guān)鍵字,保證幾個(gè)變量被一起修改、一起讀取。
<!--[if !supportLists]-->3. <!--[endif]-->使用volatile關(guān)鍵字,然后就和1一樣了。
<!--[if !supportLists]-->4. <!--[endif]-->和2一樣處理。
當對訪(fǎng)問(wèn)共享資源的方法不同時(shí)使用synchronized關(guān)鍵字時(shí),是什么樣一種情況呢?這是需要特別注意的,這樣不能保證線(xiàn)程安全!看看下面例子的運行結果就知道了(自己運行啊,我不貼結果了):
/**
* $Author: $
* $Date: $
* $Revision: $
* $History: $
*
* Created by feelyou, at time
*/
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對于線(xiàn)程安全還是很有用的,如果資源不是共享的,那么應該使用ThreadLocal,但如果確實(shí)需要在線(xiàn)程間共享資源,ThreadLocal就沒(méi)有用了!
最后,來(lái)一個(gè)完整的線(xiàn)程安全的例子:
/**
* $Author: $
* $Date: $
* $Revision: $
* $History: $
*
* Created by feelyou, at time
*/
public class TestThread extends Thread {
private int a = 0; //獨立的共享資源
private int b = 0; //b、c互相依賴(lài)
private int c = 0;
private volatile long d = 0L; //64位
// private SomeObj obj = new SomeObj(); //對象類(lèi)型,大家自己寫(xiě)吧,我就不寫(xiě)了。
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.
}
}
聯(lián)系客服