天天看點

并發模型—共享記憶體模型(線程與鎖)示例篇

共享記憶體模型,顧名思義就是通過共享記憶體來實作并發的模型,當多個線程在并發執行中使用共享資源時如不對所共享的資源進行約定或特殊處理時就會出現讀到髒資料、無效資料等問題;而為了決解共享資源所引起的這些問題,Java中引入了同步、鎖、原子類型等這些用于處理共享資源的操作;

  在本篇文章中,将通過幾個Demo來介紹Java的synchronized、lock、atomic相關類等,Java的共享記憶體并發模型也就展現在同步(synchronized)、鎖(lock)等這些實作上;

同步:

  Demo中開啟兩個線程調用一個計數器,這時計數器成了兩個線程的共享資源,兩個線程都在競争共享資源發生了竟态條件,當不對竟态條件進行處理時得到的資料就可能是有異常的不正确的;

1 /**
 2  * Created by linx on 2015-05-12.
 3  */
 4 public class Counter { 
 6     private int count = 0; 
 8     public  void increment() {
 9         ++count;
10     }
12     public int getCount() {
13         return count;
14     }
15 }
16 /**
17  * Created by linx on 2015-05-12.
18  */
19 public class CountThread extends Thread {
20 
21     private Counter counter;
22     public CountThread(Counter counter) {
23         this.counter = counter;
24     }
25     @Override
26     public void run() {
27         for (int i = 0; i < 5000; i++) {
28             counter.increment();
29         }
30     }
31 }
32 /**
33  * Created by linx on 2015-05-12.
34  */
35 public class CountMain {
36 
37     public static void main(String[] args) throws InterruptedException {
38 
39         Counter counter = new Counter();
40         AtomicCounter atomicCounter=new AtomicCounter();
41         CountThread t1 = new CountThread(counter);
42         CountThread t2 = new CountThread(counter);
43         t1.start();
44         t2.start(); 
45         t1.join();
46         t2.join();
47         System.out.println(counter.getCount());
48     } 
49 }      

我在執行這代碼的時候幾乎每次得到的結果都是不一樣的,結果如下:

并發模型—共享記憶體模型(線程與鎖)示例篇

因為這裡有竟态條件是以結果是不可預測的;

解決竟态條件的方法是對鎖競争的資源進行加鎖同步,在java中可以用synchronized或lock等鎖;

現在我們再修改計數器的代碼:

public synchronized void increment() {
  ++count;
}      

這裡我們隻是在increment方法聲明處加了synchronized關鍵字,這時候我們在執行程式,現在每次我們得到結果都會是10000,

因為我們解決了竟态條件,同一時間就會有一個線程會進入到increment方法執行,是以這時候得到的就是正确的結果;

并發模型—共享記憶體模型(線程與鎖)示例篇

在這裡我們隻是把上面Demo中的synchronized換成Lock對象,得到的結果還是相同的;

/**
 * Created by linx on 2015-05-12.
 */
public class Counter {
    private int count = 0;
    Lock lock=new ReentrantLock();
    public  void increment() {
        lock.lock();
        try {
            ++count;
        }finally {
            lock.unlock();
        }
    }
    public int getCount() {
        return count;
    }
}        

這裡我們顯示的使用了顯試的ReentrantLock鎖對象給increment方法中的代碼塊進行了加鎖,其他synchronized也是對方法進行了加鎖,不過它使用的是對象的内置鎖;

原子類型

    我們上面的Demo隻是以沒有同步或加鎖時會出現問題是因為++count不是原子的,它其實是read-modify-write三個操作,隻要能保證increment為原子方法那麼這裡也就不是出現問題了,現在我們吧count改為原子類型;

/**
 * Created by linx on 2015-05-12.
 */
public class AtomicCounter {
    private AtomicInteger count=new AtomicInteger();
    public  void increment() {
        count.incrementAndGet();
    } 
    public AtomicInteger getCount() {
        return count;
    }
}      

這個計數器類我們不進行任何同步或加鎖都不會出現問題,因為increment方法是原子的。

模型優缺點

  優點:記憶體共享模型或稱線程與鎖模型使用面很廣,而且現在幾乎每個作業系統中也存在這種模型,是以也算非常見的一種模型。

  缺點:線程與鎖模型存在的一些問題有,沒有直接支援并發、無法或難于實作分布式共享記憶體的系統,線程與鎖模型有非常不好的地方就是難于測試,在多線程程式設計中很多時候不經意間就出現問題了這時都還不知道,而且當突然出現了Bug這時往往我們也難于重制這個Bug,共享記憶體模型又是不可建立數學模型的,其中有很大的不确定性,而不确定性就說明可能掩藏着問題,人的思維也隻是單線程的;

還有由于建立線程也是非常消耗資源的,而多線程間的竟态條件、鎖等競争如果處理不好也是會非常影響性能的;

 文章首發位址:Solinx

http://www.solinx.co/archives/190