併發 (3) 同步阻塞 監視器 Volatile域 線程局部變量 讀/寫鎖

同步阻塞

每一個 Java 對象有一個鎖。線程可以通過調用同步方法獲得鎖。還有另一種機制可以獲得鎖,通過進入一個同步阻塞。當線程進入如下形式的阻塞

synchronized (obj) // this is the syntax for a synchronized block
{
critical section
}

有時會發現“ 特殊的” 鎖,例如:

public class Bank{
private doublet] accounts;private Object lock = new Object。;
public void transfer(int from, int to, int amount){
synchronized (lock) // an ad-hoc lock
{
accounts[from] -= amount;accounts[to] += amount;}
System.out.print

在此,lock 對象被創建僅僅是用來使用每個 Java 對象持有的鎖。例子:

		Object a = new Object();

		//同步阻塞
		new Thread(() -> {
			synchronized (a) {
				for (int b = 0; b <= 1000; b++) {
					System.out.println(b + "-----");
				}
			}
		}).start();

		new Thread(() -> {
			synchronized (a) {
				for (int b = 0; b <= 1000; b++) {
					System.out.println(b + "-----");
				}
			}
		}).start();

	}

監視器

       剛看完這一段,又去百度了一下監視器,沒想到打成了監聽器,感覺和線程沒什麼關係,才發現把監視器和監聽器當成了一種東西,很傻。。

鎖和條件是線程同步的強大工具,但是,嚴格地講,它們不是面向對象的。多年來,研究人員努力尋找一種方法,可以在不需要程序員考慮如何加鎖的情況下,就可以保證多線程的安全性。最成功的解決方案之一是監視器(monitor), 這一概念最早是由 PerBrinchHansen和 Tony Hoare 在 20 世紀 70 年代提出的。用 Java 的術語來講,監視器具有如下特性:

 •監視器是隻包含私有域的類。
 •每個監視器類的對象有一個相關。
         •使用該鎖對所有的方法進行加鎖。換句話說,如果客戶端調用 obj.meth0d(), 那 麼 obj對象的鎖是在方法調用開始時自動獲得, 並且當方法返回時自動釋放該鎖。因爲所有的域是私有的,這樣的安排可以確保一個線程在對對象操作時, 沒有其他線程能訪問該域。

         •該鎖可以有任意多個相關條件。

Java 設計者以不是很精確的方式採用了監視器概念, Java 中的每一個對象有一個內部的鎖和內部的條件。如果一個方法用 synchronized 關鍵字聲明,那麼,它表現的就像是一個監視器方法。通過調過調用 wait/notifyAU/notify 來訪問條件變量。

在下述的 3 個方面 Java 對象不同於監視器, 從而使得線程的安全性下降:

•域不要求必須是 private。

•方法不要求必須是 synchronized。

•內部鎖對客戶是可用的。

 Volatile域

多併發編程的三個特別重要的概念:可見性,原子性,有序性

volatile 關鍵字爲實例域的同步訪問提供了一種免鎖機制。如果聲明一個域爲 volatile ,那麼編譯器和虛擬機就知道該域是可能被另一個線程併發更新的。

一旦一個共享變量(類的成員變量、類的靜態成員變量)被volatile修飾之後,那麼就具備了兩層語義:

1)保證了不同線程對這個變量進行操作時的可見性,即一個線程修改了某個變量的值,這新值對其他線程來說是立即可見的。

2)禁止進行指令重排序。

但是volatile關鍵字只實現了可見性和有序性,並沒有實現原子性。

synchronized關鍵字是防止多個線程同時執行一段代碼,那麼就會很影響程序執行效率,而volatile關鍵字在某些情況下性能要優於synchronized,但是要注意volatile關鍵字是無法替代synchronized關鍵字的,因爲volatile關鍵字無法保證操作的原子性。通常來說,使用volatile必須具備以下2個條件:

1)對變量的寫操作不依賴於當前值

2)該變量沒有包含在具有其他變量的不變式中

以下是volatile關鍵字的使用場景:

1.狀態標記量

volatile boolean flag = false;
 
while(!flag){
    doSomething();
}
 
public void setFlag() {
    flag = true;
}
2.雙重檢查
class Singleton{
    private volatile static Singleton instance = null;
 
    private Singleton() {
 
    }
 
    public static Singleton getInstance() {
        if(instance==null) {
            synchronized (Singleton.class) {
                if(instance==null)
                    instance = new Singleton();
            }
        }
        return instance;
    }
}

關於volatile關鍵字的詳細介紹與內部原理,查看 http://www.importnew.com/18126.html,非常的不錯。

線程局部變量

要爲每個線程構造一個實例,可以使用以下代碼:

要訪問具體的格式化方法,可以調用

public static final ThreadLocal<SimpleDateFormat> dateFormat =ThreadLocal.withInitial(() -> new SimpleDateF
String dateStamp = dateFormat.get().format(new DateO);

 ThreadLocal<T>  在一個給定線程中首次調用 get 時, 會調用 initialValue 方法。在此之後, get 方法會返回
屬於當前線程的那個。

讀/寫鎖

   // 下面是使用讀 / 寫鎖的必要步驟:
    // 1 ) 構 造 一 個 ReentrantReadWriteLock 對象:
    private ReentrantReadWriteLock rwl = new ReentrantReadWriteLock():
    // 2 ) 抽取讀鎖和寫鎖:
    private Lock readLock = rwl.readLock();
    private Lock writeLock = rwl.writeLock();

    //  3 ) 對所有的獲取方法加讀鎖:
    public double getTotalBalance()

    {
        readLock.lock();
        try {
            //...
        } finally {
            readLock.unlock();
        }
    }

    //  4 ) 對所有的修改方法加寫鎖:
    public void transfer() {
        writeLock.lock();
        try {
            // . . .
        } finally {
            writeLock.unlock();
        }
    }

      上面的代碼很清楚的展示了讀/寫鎖的用法,在一個寫線程與多個讀線程的操作時,讀/寫鎖和volatile關鍵字修飾是一樣的,因爲volatile關鍵字能保證可見性,但是在多個寫線程與多個讀線程時,volatile就不行了,因爲不能保證原子性,而讀/寫鎖可以互斥同時保證了儘可能的並行。 synchronized的修飾一般限制這個區域是不可重入的。每次只有一個線程可以訪問。這種強烈的互斥性使得每次不管是讀數據還是寫數據都只能有一個線程可以操作。在希望有多個讀線程可以並行執行的情況下,它並不是一個理想的選擇。 

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