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