線程安全與併發探究(五)

        併發問題再也不是一個只有高級程序員才能接觸的問題了,在使用多線程編程的時候,我們更多的將目光放在追求系統的高併發和高吞吐,而這一切的前提是確保程序的正確性。在多線程編程中容易產生的問題有很多,比如線程安全問題、死鎖、飢餓等。

@NotThreadSafe
public class UnsafeSequence {
    private int value;
 
    /** Returns a unique value. */
    public int getNext() {
        return value++;
    }
}

這個例子來源於《Java Concurrency In Practice》的第一章,一個最最簡單,卻容易引起線程安全問題的片段。書中給出瞭如下解釋:The problem with UnsafeSequence is that with some unluckytiming, two threads could call getNext and receive the samevalue. Figure 1.1 shows how this can happen. The incrementnotation, nextValue++, may appear to be a single operation, butis in fact three separate operations: read the value, add one to it, and writeout the new value. Since operations in multiple threads may be arbitrarilyinterleaved by the runtime, it is possible for two threads to read the value atthe same time, both see the same value, and then both add one to it. The resultis that the same sequence number is returned from multiple calls in differentthreads.

下圖的確很明瞭的告訴我們線程推進的過程,這也給了我們一個尋找線程不安全的方法,通過這樣圖示法來分析問題。

A: value=9            9+1=10              value=10

B:                  value=9             9+1=10                 value=10


當然,這裏引出了第一個可能會引起線程不安全的因素:程序中有變量的讀取、寫入或判斷操作

/**例如上述變量的自增*/
    public int getNext() {
        return value++;
    }
/**例如單例模式中隊變量的判斷操作*/
   Public Object getInstance(){
     If(obj==null){
  return new Object();
}
return obj;
}
     public class Singleton{
            private static Singleton singleton = null;
              private Singleton (){}
            public static synchronized synchronized getInstance(){
                 if(singleton==null){
                     singleton = new Singleton();
                 }
                return singleton;
            }
       } 


寫一個簡單的程序驗證一下上述問題,其實線程的學習最好的辦法就是舉例證明線程的不安全性,然後再想出解決方案,當然這也可能是這部分學習最難所在:

package com.a2.concurrency.chapter1;
/**
 * 線程安全第一種因素:程序中有變量的讀取、寫入或判斷操作
 * @author ChenHui
 *
 */
public class UnsafeSequence {
 
    private int value;
 
    public int getValue() {
        return value++;
    }
 
    public static void main(String[] args) throws InterruptedException {
         
        final UnsafeSequence us = new UnsafeSequence();
        Thread th1 = new Thread("th1") {
            @Override
            public void run() {
                System.out.println( us.getValue()+" "+super.getName());
            }
        };
 
        Thread th2 = new Thread("th2") {
            @Override
            public void run() {
                System.out.println(us.getValue()+" "+super.getName());
            }
        };
 
        th1.start();
        /**
         * 如果不執行Thread.sleep(1000);
         * 偶爾結果爲:
         * 0 th2
         * 0 th1
         * 如果執行Thread.sleep(1000);
         * 結果爲:
         * 0 th1
         * 1 th2
         */
        //Thread.sleep(1000);
        th2.start();
    }
}


對於這種因素產生的問題,我們先給出一種常用解決方案,就是使用同步機制。這裏我們先給出最簡單,大家也最容易想到的方案,對操作加synchronized關鍵字:


1

2

3

4

private volatile int value;

    public synchronized int getNext() {

        return value++;

    }

在這裏使用了synchronized的情況下,是否使用volatile關鍵字並不是主要的。

在最後例舉一下一般會遇到線程安全問題的地方,引用自併發編程書中第一章:

l  Timer

l  Servlet/JSP

l  RMI

l  Swing

……

注:synchronized 方法控制對類成員變量的訪問: 每個類實例對應一把鎖,每個 synchronized 方法都必須獲得調用該方法的類實例的鎖方能執行,否則所屬 線程阻塞 ,方法一旦執行,就獨佔該鎖,直到從該方法返回時纔將鎖釋放,此後被阻塞的線程方能獲得該鎖,重新進入可 執行狀態。這種機制確保了同一時刻對於每一個類實例,其所有聲明爲 synchronized 的成員函數中至多隻有一個處於可執行狀態(因爲至多隻有一個能夠獲得該類實例對應的鎖),從而有效避免了類成員變量的訪問衝突(只要所有可能訪問類成員變量的方法均被聲明爲 synchronized)。

發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章