在多線程程序中,如果多個(gè)線程同時(shí)對(duì)同一個(gè)對(duì)象進(jìn)行讀寫(xiě),由于讀寫(xiě)操作會(huì)在內(nèi)存中先復(fù)制一份緩存數(shù)據(jù),修改完緩存數(shù)據(jù)后再用緩存數(shù)據(jù)覆蓋原來(lái)的數(shù)據(jù),所以寫(xiě)操作不會(huì)馬上影響到原始數(shù)據(jù),這時(shí)候其他線程讀取到的數(shù)據(jù)就是舊的數(shù)據(jù)(也成為“臟數(shù)據(jù)”),這個(gè)數(shù)據(jù)的讀寫(xiě)就是線程不安全了。在數(shù)據(jù)庫(kù)里面所謂的“臟讀”也是類(lèi)似的原理。
例子程序:
class UnSafeClass{
// 線程不安全的類(lèi)
private int a = 0;
public void add(){
a+=1;
System.out.println("UnSafeClass: " + Thread.currentThread().getName() + ":a is " + a);
}
}
class SafeClass{
// 線程安全的類(lèi)
private int a = 0;
public void add(){
synchronized (this) {
a++;
System.out.println("SafeClass: " + Thread.currentThread().getName() + ":a is " + a);
}
}
}
class TestUnSafeThread implements Runnable{
private static UnSafeClass unSafeClass = new UnSafeClass();
@Override
public void run(){
for(int i=0; i<10; i++) {
try {
unSafeClass.add();
Thread.sleep(1000);
} catch (InterruptedException e) {
System.out.println(e);
}
}
}
}
class TestSafeThread implements Runnable{
private static SafeClass safeClass = new SafeClass();
@Override
public void run() {
for(int i=0;i<10;i++) {
try {
safeClass.add();
Thread.sleep(1000);
} catch (InterruptedException e) {
System.out.println(e);
}
}
}
}
public class SafeThreadTest{
public static void main(String[] args){
System.out.println("SafeThreadTest-------------------");
Thread[] t1 = new Thread[3];
for(int i=0;i<t1.length;i++){
t1[i] = new Thread(new TestUnSafeThread());
t1[i].start();
}
Thread[] t2 = new Thread[3];
for(int i=0;i<t2.length;i++){
t2[i] = new Thread(new TestSafeThread());
t2[i].start();
}
}
}
程序輸出結(jié)果

由結(jié)果可以看出來(lái),線程不安全的類(lèi)讀寫(xiě)存在臟讀的情況(不一定每次都會(huì)出現(xiàn)),而線程安全的類(lèi)(即加鎖)讀取沒(méi)有存在臟讀的情況。
對(duì)象的無(wú)狀態(tài)與有狀態(tài)
有狀態(tài)就是有數(shù)據(jù)存儲(chǔ)功能。有狀態(tài)對(duì)象(Stateful Bean),就是有實(shí)例變量的對(duì)象,可以保存數(shù)據(jù),是非線程安全的。在不同方法調(diào)用間不保留任何狀態(tài)。
無(wú)狀態(tài)就是一次操作,不能保存數(shù)據(jù)。無(wú)狀態(tài)對(duì)象(Stateless Bean),就是沒(méi)有實(shí)例變量的對(duì)象.不能保存數(shù)據(jù),是不變類(lèi),是線程安全的。
// 無(wú)狀態(tài)對(duì)象
class StatelessClass {
public void method(){
//do something
....
}
}
// 有狀態(tài)對(duì)象
class stateClass{
private int count = 0; // 對(duì)象的狀態(tài)變量
public void method(){
count ++; // 對(duì)象的變量變化,在多線程的時(shí)候不安全。
}
}
利用無(wú)狀態(tài)的技術(shù)有單例模式,這樣可以共享實(shí)例,提高性能。有狀態(tài)的Bean,多線程環(huán)境下不安全,那么適合用Prototype原型模式。Prototype: 每次對(duì)bean的請(qǐng)求都會(huì)創(chuàng)建一個(gè)新的bean實(shí)例。 有狀態(tài)的bean都使用prototype作用域,而對(duì)無(wú)狀態(tài)的bean則應(yīng)該使用singleton作用域。
關(guān)于volatile
對(duì)于非volatile類(lèi)型的long和double變量,jvm允許將64位的讀操作或?qū)懖僮鞣纸鉃閮蓚€(gè)32位的操作。當(dāng)讀取一個(gè)非volatile類(lèi)型的long變量是,如果對(duì)改變量的讀操作和寫(xiě)操作在不同的線程中執(zhí)行,那么很可能會(huì)讀取到某個(gè)值的高32位和另一個(gè)值的低32位。因此,即使不考慮失效數(shù)據(jù)問(wèn)題(就是讀取到了“臟數(shù)據(jù)”),在多線程程序中使用共享且可變的long和double等類(lèi)型的變量也是不安全的,除非用關(guān)鍵字volatile來(lái)聲明或者用鎖保護(hù)起來(lái)。
當(dāng)把變量聲明為volatile類(lèi)型后,編譯器與運(yùn)行的虛擬機(jī)都會(huì)注意到這個(gè)變量是共享的,因此不會(huì)講改變量上的操作與其他內(nèi)存操作一起重排序。在訪問(wèn)volatile變量是不會(huì)執(zhí)行加鎖操作,因此也就不會(huì)是執(zhí)行線程阻塞,所以volatile變量是一個(gè)種比sychronized關(guān)鍵字更輕量級(jí)的同步機(jī)制。
volatile變量一般用于某個(gè)狀態(tài)標(biāo)記變量,volatile變量不能確保數(shù)據(jù)的原子性,只能確保可見(jiàn)性(內(nèi)存可見(jiàn)性)