1. 程式人生 > >Java併發程式設計規則:原子變數實現執行緒安全

Java併發程式設計規則:原子變數實現執行緒安全

判定規則:

如果一個類中存在變數,並且此變數的操作不是原子操作,那麼這個類就是非執行緒安全的類。線上程產生競爭條件的情況下,多執行緒訪問導致原子性不可保證。

競爭條件產生的原因:

當計算的正確性依賴於執行時中相關的時序或多執行緒的交替時,會產生競爭條件。多執行緒情況下,執行緒的時序不能確定,所以一旦程式依賴於特定的執行時序就會產生競爭條件。所以,在多執行緒環境下必須保證執行緒的可見性可控或避免產生競爭條件。記憶體可見性,可以理解為變數是否對外可訪問,是否受保護。執行緒之間出現競爭,是因為業務依賴於時序操作或變數的可見性未加以約束。一般如果要實現記憶體可見性控制有兩種方法,即使用synchronized和volatile關鍵字。

class SafetyWithSynchronized{

    private int something = 0;

    public synchronized  int getSomething ( ) {
        return something;
    }

    public synchronized  void setSomething (int something) {
        this.something = something;
    }
}

class SafetyWithVolatile{
   private volatile int something = 0;

   public int getSomething ( ) {
        return something;
   }
   public void setSomething (int something) {
        this.something = something;
   }
}

使用volatile 關鍵字的方案,在效能上要好很多,因為volatile是一個輕量級的同步,只能保證多執行緒的記憶體可見性,不能保證多執行緒的執行有序性。因此開銷遠比synchronized要小。Volatile 變數具有 synchronized 的可見性特性,但是不具備原子特性。這就是說執行緒能夠自動發現 volatile 變數的最新值。Volatile 變數可用於提供執行緒安全,但是隻能應用於非常有限的一組用例:多個變數之間或者某個變數的當前值與修改後值之間沒有約束。出於簡易性或可伸縮性的考慮,大多數情況下可能傾向於使用 volatile 變數而不是鎖。當使用 volatile 變數而非鎖時,某些習慣用法(idiom)更加易於編碼和閱讀。此外,volatile 變數不會像鎖那樣造成執行緒阻塞,因此也很少造成可伸縮性問題。在某些情況下,如果讀操作遠遠大於寫操作,volatile 變數還可以提供優於鎖的效能優勢。

CAS原理實現原子操作:

原子操作:

在複雜的業務處理中,假設有操作A和B。若果從執行A的執行緒看,當其它執行緒執行B時,要麼B全部執行完成,要麼一點都沒有執行,這樣A和B互為原子操作。一個原子操作是指:該操作對於所有的操作,包括它自己,都滿足前面描述的狀態。


CAS 指的是現代 CPU 廣泛支援的一種對記憶體中的共享資料進行操作的一種特殊指令。這個指令會對記憶體中的共享資料做原子的讀寫操作。簡單介紹一下這個指令的操作過程:首先,CPU 會將記憶體中將要被更改的資料與期望的值做比較。然後,當這兩個值相等時,CPU 才會將記憶體中的數值替換為新的值。否則便不做操作。最後,CPU 會將舊的數值返回。這一系列的操作是原子的。它們雖然看似複雜,但卻是 Java 5 併發機制優於原有鎖機制的根本。簡單來說,CAS 的含義是“我認為原有的值應該是什麼,如果是,則將原有的值更新為新值,否則不做修改,並告訴我原來的值是多少”。(這段描述引自《Java併發程式設計實踐》)

java.util.concurrent併發包實現原子操作:

由於java的CAS同時具有 volatile 讀和volatile寫的記憶體語義,因此Java執行緒之間的通訊現在有了下面四種方式:

  1. A執行緒寫volatile變數,隨後B執行緒讀這個volatile變數。
  2. A執行緒寫volatile變數,隨後B執行緒用CAS更新這個volatile變數。
  3. A執行緒用CAS更新一個volatile變數,隨後B執行緒用CAS更新這個volatile變數。
  4. A執行緒用CAS更新一個volatile變數,隨後B執行緒讀這個volatile變數。

Java的CAS會使用現代處理器上提供的高效機器級別原子指令,這些原子指令以原子方式對記憶體執行讀-改-寫操作,這是在多處理器中實現同步的關鍵(從本質上來說,能夠支援原子性讀-改-寫指令的計算機器,是順序計算圖靈機的非同步等價機器,因此任何現代的多處理器都會去支援某種能對記憶體執行原子性讀-改-寫操作的原子指令)。同時,volatile變數的讀/寫和CAS可以實現執行緒之間的通訊。把這些特性整合在一起,就形成了整個concurrent包得以實現的基石。如果我們仔細分析concurrent包的原始碼實現,會發現一個通用化的實現模式:

  1. 首先,宣告共享變數為volatile;
  2. 然後,使用CAS的原子條件更新來實現執行緒之間的同步;
  3. 同時,配合以volatile的讀/寫和CAS所具有的volatile讀和寫的記憶體語義來實現執行緒之間的通訊。

常見原子變數:

在java.util.concurrent.atomic包下還有很多類,使用這些類可以保證對這些類的諸如“獲取-更新”操作是原子性的,從而避發生競爭條件。jdk1.7 java.util.concurrent.atomic的原子變數:

AtomicBoolean 	//A boolean value that may be updated atomically.
AtomicInteger 	//An int value that may be updated atomically.
AtomicIntegerArray 	//An int array in which elements may be updated atomically.
AtomicIntegerFieldUpdater<T> 	//A reflection-based utility that enables atomic updates to designated volatile int fields of designated classes.
AtomicLong 	//A long value that may be updated atomically.
AtomicLongArray 	//A long array in which elements may be updated atomically.
AtomicLongFieldUpdater<T> 	//A reflection-based utility that enables atomic updates to designated volatile long fields of designated classes.
AtomicMarkableReference<V> 	//An AtomicMarkableReference maintains an object reference along with a mark bit, that can be updated atomically.
AtomicReference<V> 	//An object reference that may be updated atomically.
AtomicReferenceArray<E> 	//An array of object references in which elements may be updated atomically.
AtomicReferenceFieldUpdater<T,V> 	//A reflection-based utility that enables atomic updates to designated volatile reference fields of designated classes.
AtomicStampedReference<V> 	//An AtomicStampedReference maintains an object reference along with an integer "stamp", that can be updated atomically.

注:學習併發程式設計的時候儘可能地通過二手資料去發掘一手資料,這樣可以學到更多,最好是從Java API中去尋找。

非執行緒安全示例:

package net.jcip.examples;

import java.math.BigInteger;
import javax.servlet.*;

import net.jcip.annotations.*;

/**
 * UnsafeCountingFactorizer
 *
 * Servlet that counts requests without the necessary synchronization
 *
 * @author Brian Goetz and Tim Peierls
 */
@NotThreadSafe
public class UnsafeCountingFactorizer extends GenericServlet implements Servlet {
    private long count = 0;

    public long getCount() {
        return count;
    }

    public void service(ServletRequest req, ServletResponse resp) {
        BigInteger i = extractFromRequest(req);
        BigInteger[] factors = factor(i);
        ++count;
        encodeIntoResponse(resp, factors);
    }

    void encodeIntoResponse(ServletResponse res, BigInteger[] factors) {
    }

    BigInteger extractFromRequest(ServletRequest req) {
        return new BigInteger("7");
    }

    BigInteger[] factor(BigInteger i) {
        // Doesn't really factor
        return new BigInteger[] { i };
    }
}

注:此類在單執行緒環境中可以很好的執行,但是在多執行緒環境中就是不是執行緒安全的了,因為++count不是原子操作。

執行緒安全示例:

package net.jcip.examples;

import java.math.BigInteger;
import java.util.concurrent.atomic.*;
import javax.servlet.*;

import net.jcip.annotations.*;

/**
 * CountingFactorizer
 *
 * Servlet that counts requests using AtomicLong
 *
 * @author Brian Goetz and Tim Peierls
 */
@ThreadSafe
public class CountingFactorizer extends GenericServlet implements Servlet {
    private final AtomicLong count = new AtomicLong(0);

    public long getCount() { return count.get(); }

    public void service(ServletRequest req, ServletResponse resp) {
        BigInteger i = extractFromRequest(req);
        BigInteger[] factors = factor(i);
        count.incrementAndGet();
        encodeIntoResponse(resp, factors);
    }

    void encodeIntoResponse(ServletResponse res, BigInteger[] factors) {}
    BigInteger extractFromRequest(ServletRequest req) {return null; }
    BigInteger[] factor(BigInteger i) { return null; }
}

利用原子變數實現簡單的自增序列例項:

class Sequencer {
   private final AtomicLong sequenceNumber= new AtomicLong(0);

  public long next() {
     return sequenceNumber.getAndIncrement();
   }
 }

對原子變數操作的總結:

1. 知道++操作不是原子操作。

2. 非原子操作有執行緒安全問題。

3. 併發下的記憶體可見性可以通過synchronized和volatile加以控制。

4. Atomic類通過CAS + volatile可以比synchronized做的更高效,推薦使用。

5.利用AtomicLong這樣已有的執行緒安全管理類的狀態是非常實用的,相比於非執行緒安全物件,判斷一個執行緒安全物件的可能狀態和狀態的裝換要容易得多。這簡化了維護和驗證執行緒安全性的工作。

相關推薦

Java併發程式設計規則原子變數實現執行安全

判定規則: 如果一個類中存在變數,並且此變數的操作不是原子操作,那麼這個類就是非執行緒安全的類。線上程產生競爭條件的情況下,多執行緒訪問導致原子性不可保證。 競爭條件產生的原因: 當計算的正確性依賴於執行時中相關的時序或多執行緒的交替時,會產生競爭條件。多執行緒情況下,執行

JAVA併發程式設計實戰》原子變數和非阻塞同步機制

引言 即使原子變數沒有用於非阻塞演算法的開發,他們也可以用作一種“更好的”volatile型別變數。原子變數提供了與volatile型別變數相同的記憶體語義,此外還支援原子的更新操作,從而使他們更加適用於實現計數器、序列發生器和統計資料收集等,同時還能比基於鎖

Java併發程式設計規則不可變物件永遠是執行安全

建立後狀態不能被修改的物件叫作不可變物件。不可變物件天生就是執行緒安全的。它們的常量(變數)是在建構函式中建立的,既然它們的狀態無法被修改,那麼這些常量永遠不會被改變——不可變物件永遠是執行緒安全的。 不可變性的理解: 無論是Java語言規範還是Java儲存模型都沒有對不可

Java併發程式設計規則構建執行安全的共享物件

構建執行緒安全的共享物件,使其在多執行緒環境下能夠提供安全的訪問。編寫正確的併發程式關鍵在於控制共享、可變的狀態進行訪問管理。synchornized關鍵字既可以阻塞程式,也可以維護操作的原子性,它是一個執行緒安全與非執行緒安全的臨界區標識,通過它我們可以控制物件的記憶體可

Java併發程式設計規則無狀態物件永遠是執行安全

規則說明: 無狀態物件即無狀態類,是指其本身沒有內部變數和外部變數的操作的,在每個使用者訪問的執行緒棧中都是一個各自的例項。 執行緒安全的表現: 一個執行緒對該類的訪問不會影響其他執行緒的訪問結果。

java併發程式設計(二)多個執行多個鎖

多個執行緒多個鎖 多個執行緒多個鎖:多個執行緒,每個執行緒都可以拿到自己制定的鎖,分別獲得鎖之後,執行synchronized方法體的內容。就是在上次那個部落格上說道的鎖競爭的問題,是因為所有的執行緒過來以後都爭搶同一個鎖。如果說每個執行緒都可以或得到自己的鎖,這樣的話我們的鎖競爭問題就沒有了

java執行2區域性變數執行安全,實列變數的非執行安全

java多執行緒2:區域性變數的執行緒安全,實列變數的非執行緒安全 “非執行緒安全“就是在多個執行緒訪問同一個物件的例項變數進行併發訪問時候發生,產生的後果就是”髒讀“,也就是取到的資料其實是被修改過的。 a.多執行緒訪問區域性變數是執行緒安全的。 package multiThread

Java併發程式設計原理與實戰一(執行狀態及建立執行的多種方式)

一、為什麼要學習併發程式設計 1.發揮多處理的強大能力 2.建模的簡單性 3.非同步事件的簡化處理 4.響應更加靈敏的使用者介面 二、併發的缺點 1.安全性問題 多執行緒環境下 多個執行緒共享一個資源 對資源進行非原子性操作 2.活躍

java 併發程式設計學習筆記(八)執行

                                          &nb

關於執行同步的問題(原子變數實現執行同步)

package com.bootdo.wang; import java.util.concurrent.atomic.AtomicInteger; /** * 7.使用原子變數實現執行緒同步 * 在java的util.concurrent.atomic包中提供了建立了原子型別變數的工

java併發程式設計(十五)之執行

待續...package com.dason.juc2; import java.util.ArrayList; import java.util.List; import java.util.concurrent.Callable; import java.util.co

Java併發程式設計的藝術》讀書筆記—— 執行

                              執行緒池 1.為什麼要引入執行緒池         昨天和一老鐵聊天時就說到了執行緒,他說執行緒是輕量級的程序,其實也可以這麼說吧。在《Ja

java併發程式設計的藝術(五)-----執行狀態

執行緒與程序? 執行緒是作業系統排程的最小單元,而程序是系統進行資源分配和排程的一個基本單位。 簡單理解:程序是程式的一次執行,執行緒可以理解為程序中的執行的一段程式片段 360解釋:程序是一個具有獨立功能的程式關於某個資料集合的一次執行活動。它可以申請和

c/c++ 多執行 利用條件變數實現執行安全的佇列

多執行緒 利用條件變數實現執行緒安全的佇列 背景:標準STL庫的佇列queue是執行緒不安全的。 利用條件變數(Condition variable)簡單實現一個執行緒安全的佇列。 程式碼: #include <queue> #include <memory> #include

java通過雙重檢測或列舉類實現執行安全單例(懶漢模式)

雙重檢測實現 /** * 懶漢模式->雙重同步鎖單例模式 */ public class SingletonExample5 { private SingletonExample5() { } //volatile + 雙重檢測機制 -> 禁止指令重排序

Java 併發程式設計(二)如何保證共享變數原子性?

執行緒安全性是我們在進行 Java 併發程式設計的時候必須要先考慮清楚的一個問題。這個類在單執行緒環境下是沒有問題的,那麼我們就能確保它在多執行緒併發的情況下表現出正確的行為嗎? 我這個人,在沒有副業之前,一心撲在工作上面,所以處理的蠻得心應手,心態也一直保持的不錯;但有了副業之後,心態就變得像坐過山車一樣

Java併發程式設計的藝術——原子操作的實現原理

原子操作的定義 atomic,表面上,指的是不能進一步分割的最小粒子。借鑑我們在資料庫中學的事務的概念,atomic operation,意為——不可被中斷的一個或者一系列操作。 處理器如何實現原子操作 使用匯流排鎖保證原子性 如果多個處理器同時對共享變數進行讀

JAVA併發程式設計(五)建立執行的第三種方式實現Callable介面

眾所周知建立執行緒的方式有兩種:1.繼承Thread類。2.實現Runnable介面。從jdk1.5開始,提供了另一種建立執行緒的方式。今天我們就來看看這第三種方式:實現Callable介面 一、Callable與Runnable 我們直接來看一個使用C

Java 併發程式設計(三)如何保證共享變數的可見性?

上一篇,我們談了談如何通過同步來保證共享變數的原子性(一個操作或者多個操作要麼全部執行並且執行的過程不會被任何因素打斷,要麼就都不執行),本篇我們來談一談如何保證共享變數的可見性(多個執行緒訪問同一個變數時,一個執行緒修改了這個變數的值,其他執行緒能夠立即看得到修改的值)。 我們使用同步的目的不僅是,不希望

Java併發程式設計學習volatile關鍵字解析

轉載:https://www.cnblogs.com/dolphin0520/p/3920373.html 寫的非常棒,好東西要分享一下 Java併發程式設計:volatile關鍵字解析    volatile這個關鍵字可能很多朋友都聽說過,或許也都用過。在Java 5之前,它是一個備受