線程安全

在多線程程序中,如果多個(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)性)

最后編輯于
?著作權(quán)歸作者所有,轉(zhuǎn)載或內(nèi)容合作請(qǐng)聯(lián)系作者
【社區(qū)內(nèi)容提示】社區(qū)部分內(nèi)容疑似由AI輔助生成,瀏覽時(shí)請(qǐng)結(jié)合常識(shí)與多方信息審慎甄別。
平臺(tái)聲明:文章內(nèi)容(如有圖片或視頻亦包括在內(nèi))由作者上傳并發(fā)布,文章內(nèi)容僅代表作者本人觀點(diǎn),簡(jiǎn)書(shū)系信息發(fā)布平臺(tái),僅提供信息存儲(chǔ)服務(wù)。

相關(guān)閱讀更多精彩內(nèi)容

友情鏈接更多精彩內(nèi)容