從volatile說到,i++原子操作,線程安全問題


 

1、可見性(Visibility)

        可見性是指,當一個線程修改了某一個全局共享變量的數值,其他線程是否能夠知道這個修改。

        顯然,在串行程序來說可見性的問題是不存在的。因為你在任何一個地方操作修改了某個變量,那么在后續的程序里面,讀取這個變量的數值,一定是修改后的數值。

        但是,這個問題在並行程序里面就不見得了。在並行程序里面,如果一個線程修改了某一個全局變量,那么其他線程未必可以馬上知道這個變動。下面的圖1展示了可見性問題的一種。如果在CPU1和CPU2上各運行一個線程,他們共享變量t,由於編譯器優化或者應該優化的緣故,在CPU1上的線程將變量t進行了優化,將其緩存在Cache中或者寄存器里面。這種情況下,如果在CPU2上的那個線程修改了線程t 的實際數值,那么CPU1上的線程可能並無法意識到這個改動,依然會讀取cache中或者寄存器里面的數據。因此,就產生了可見性問題。外在表現就是,變量t 的數值被CPU2上的線程修改,但是CPU1上的線程依然會讀到一個舊的數據。

                                                                         

2、原子性(Atomicity)

        原子性,指的是一個操作是不可中斷的。即使是在多個線程一起執行的時候,一個操作一旦開始,就不會被其他線程打斷。

 

3、Java內存模型的抽象結構( JMM )

        在Java中,所有實例域、靜態域和數組元素都存儲在堆內存中,堆內存在線程之間共享(本章用“共享變量”這個術語代指實例域,靜態域和數組元素)。局部變量(Local Variables),方法定義參數(Java語言規范稱之為Formal Method Parameters)和異常處理器參數(ExceptionHandler Parameters)不會在線程之間共享,它們不會有內存可見性問題,也不受內存模型的影響。

        Java線程之間的通信由Java內存模型(本文簡稱為JMM)控制,JMM決定一個線程對共享變量的寫入何時對另一個線程可見。從抽象的角度來看,JMM定義了線程和主內存之間的抽象關系:線程之間的共享變量存儲在主內存(Main Memory)中,每個線程都有一個私有的本地內存(Local Memory),本地內存中存儲了該線程以讀/寫共享變量的副本。本地內存是JMM的一個抽象概念,並不真實存在。它涵蓋了緩存、寫緩沖區、寄存器以及其他的硬件和編譯器優化。Java內存模型的抽象示意如圖所示。

                                     

                                                                                  圖3-1 Java內存模型的抽象結構示意圖

              從圖3-1來看,如果線程A與線程B之間要通信的話,必須要經歷下面2個步驟。

                     1)線程A把本地內存A中更新過的共享變量刷新到主內存中去。

                     2)線程B到主內存中去讀取線程A之前已更新過的共享變量。

             下面通過示意圖(見圖3-2)來說明這兩個步驟。

                                  

                                                                                         圖3-2 線程之間的通信圖

             

            如圖3-2所示,本地內存A和本地內存B由主內存中共享變量x的副本。假設初始時,這3個內存中的x值都為0。線程A在執行時,把更新后的x值(假設值為1)臨時存放在自己的本地內存A中。當線程A和線程B需要通信時,線程A首先會把自己本地內存中修改后的x值刷新到主內存中,此時主內存中的x值變為了1。隨后,線程B到主內存中去讀取線程A更新后的x值,此時線程B的本地內存的x值也變為了1。

           從整體來看,這兩個步驟實質上是線程A在向線程B發送消息,而且這個通信過程必須要經過主內存。JMM通過控制主內存與每個線程的本地內存之間的交互,來為Java程序員提供內存可見性保證。

4、volatile 

 

  4.1使用volatile以后,做了如下事情

  • 每次修改volatile變量都會同步到主存中。
  • 每次讀取volatile變量的值都強制從主存讀取最新的值(強制JVM不可優化volatile變量,如JVM優化后變量讀取會使用cpu緩存而不從主存中讀取)

  4.2 volatile解決的是多線程間共享變量的可見性問題,而保證不了多線程間共享變量原子性問題。對於多線程的i++,++i,依然還是會存在多線程問題,volatile是無法解決的.如下:使用一個線程i++,另一個i--,最終得到的結果不為0。

         4.2.1  多線程下的i++問題

                   一個線程對count進行times次的加操作,一個線程對count進行times次的減操作。count最后的結果,不為0.

 

public class VolatileTest {
    private static volatile int count = 0;
    private static final int times = 10000;
 
    public static void main(String[] args) {
 
        long curTime = System.nanoTime();
 
        Thread decThread = new DecThread();
        decThread.start();
 
        System.out.println("Start thread: " + Thread.currentThread() + " i++");
 
        for (int i = 0; i < times; i++) {
            count++;
        }
 
        System.out.println("End thread: " + Thread.currentThread() + " i--");
 
        // 等待decThread結束
        while (decThread.isAlive())
            ;
 
        long duration = System.nanoTime() - curTime;
        System.out.println("Result: " + count);
        System.out.format("Duration: %.2fs\n", duration / 1.0e9);
    }
 
    private static class DecThread extends Thread {
 
        @Override
        public void run() {
            System.out.println("Start thread: " + Thread.currentThread()
                    + " i--");
            for (int i = 0; i < times; i++) {
                count--;
            }
            System.out
                    .println("End thread: " + Thread.currentThread() + " i--");
        }
    }
}

 

 

         4.2.2  程序的運行結果

Start thread: Thread[Thread-0,5,main] i--
Start thread: Thread[main,5,main] i++
End thread: Thread[main,5,main] i++
End thread: Thread[Thread-0,5,main] i--
Result: -6240
Duration: 0.00s

 

 

         4.2.3  i++和++i並非原子操作

       原因是i++和++i並非原子操作,我們若查看字節碼,會發現

void f1() { i++; } 

       的字節碼如下

    void f1();  
    Code:  
    0: aload_0  
    1: dup  
    2: getfield #2; //Field i:I  
    5: iconst_1  
    6: iadd  
    7: putfield #2; //Field i:I  
    10: return 

可見i++執行了多部操作,從變量i中讀取讀取i的值->值+1 ->將+1后的值寫回i中,這樣在多線程的時候執行情況就類似如下了

    Thread1             Thread2  
    r1 = i;             r3 = i;                 
    r2 = r1 + 1;        r4 = r3 + 1;  
    i = r2;             i = r4;  

       這樣會造成的問題就是 r1, r3讀到的值都是 0,最后兩個線程都將 1 寫入 i, 最后 i等於 1,但是卻進行了兩次自增操作。

       可知加了volatile和沒加volatile都無法解決非原子操作的線程同步問題。

5、使用循環CAS,實現i++原子操作

       5.1   關於Java並發包的介紹

 

        Java提供了java.util.concurrent.atomic包來提供線程安全的基本類型包裝類。這些包裝類都是是用CAS來實現,i++的原子性操作。以AtomicInteger為例子,講一下 public final int getAndIncrement(){} 方法的實現。

public final int getAndIncrement() {
        for (;;) {
            int current = get();
            int next = current + 1;
            if (compareAndSet(current, next))
                return current;
        }
    }

     5.2 使用循環CAS,來實現i++的原子性操作

public class AtomicIntegerTest {
    private static AtomicInteger count = new AtomicInteger(0);
    private static final int times = 10000;
    AtomicInteger atomicInteger;
 
    public static void main(String[] args) {
 
        long curTime = System.nanoTime();
 
        Thread decThread = new DecThread();
        decThread.start();
 
        System.out.println("Start thread: " + Thread.currentThread() + " i++");
 
        for (int i = 0; i < times; i++) {
            // 進行自加的操作
            count.getAndIncrement();
        }
 
        System.out.println("End thread: " + Thread.currentThread() + " i++");
 
        // 等待decThread結束
        while (decThread.isAlive())
            ;
 
        long duration = System.nanoTime() - curTime;
        System.out.println("Result: " + count);
        System.out.format("Duration: %.2fs\n", duration / 1.0e9);
    }
 
    private static class DecThread extends Thread {
 
        @Override
        public void run() {
            System.out.println("Start thread: " + Thread.currentThread()
                    + " i--");
            for (int i = 0; i < times; i++) {
                // 進行自減的操作
                count.getAndDecrement();
            }
            System.out
                    .println("End thread: " + Thread.currentThread() + " i--");
        }
    }
}

 

    5.3  程序的運行結果

 

Start thread: Thread[main,5,main] i++
Start thread: Thread[Thread-0,5,main] i--
End thread: Thread[Thread-0,5,main] i--
End thread: Thread[main,5,main] i++
Result: 0
Duration: 0.00s

 

6、使用鎖機制,實現i++原子操作

      6.1  使用鎖機制,實現i++原子操作

 

package com.baowei.yuanzi;
 
import java.util.concurrent.atomic.AtomicInteger;
import java.util.concurrent.locks.ReentrantLock;
 
public class LockTest {
    private static int count = 0;
    private static final int times = 10000;
 
    // 使用Lock實現,多線程的數據同步
    public static ReentrantLock lock = new ReentrantLock();
 
    public static void main(String[] args) {
 
        long curTime = System.nanoTime();
 
        Thread decThread = new DecThread();
        decThread.start();
 
        System.out.println("Start thread: " + Thread.currentThread() + " i++");
 
        for (int i = 0; i < times; i++) {
            // 進行自加的操作
            try {
                lock.lock();
                count++;
            } catch (Exception e) {
                e.printStackTrace();
            } finally {
                lock.unlock();
            }
        }
 
        System.out.println("End thread: " + Thread.currentThread() + " i++");
 
        // 等待decThread結束
        while (decThread.isAlive())
            ;
 
        long duration = System.nanoTime() - curTime;
        System.out.println("Result: " + count);
        System.out.format("Duration: %.2fs\n", duration / 1.0e9);
    }
 
    private static class DecThread extends Thread {
 
        @Override
        public void run() {
            System.out.println("Start thread: " + Thread.currentThread()
                    + " i--");
            for (int i = 0; i < times; i++) {
                // 進行自減的操作
                try {
                    lock.lock();
                    count--;
                } catch (Exception e) {
                    e.printStackTrace();
                } finally {
                    lock.unlock();
                }
            }
            System.out
                    .println("End thread: " + Thread.currentThread() + " i--");
        }
    }
} 

      6.2  程序的運行結果

 

Start thread: Thread[Thread-0,5,main] i--
Start thread: Thread[main,5,main] i++
End thread: Thread[main,5,main] i++
End thread: Thread[Thread-0,5,main] i--
Result: 0
Duration: 0.04s

 

7、使用synchronized,實現i++原子操作

      7.1  使用synchronized,實現i++原子操作

 

package com.baowei.yuanzi;
 
public class SynchronizedTest {
    private static int count = 0;
    private static final int times = 1000000;
 
    public static void main(String[] args) {
 
        long curTime = System.nanoTime();
 
        Thread decThread = new DecThread();
        decThread.start();
 
        System.out.println("Start thread: " + Thread.currentThread() + " i++");
 
        for (int i = 0; i < times; i++) {
            // 進行自加的操作
            synchronized (SynchronizedTest.class) {
                count++;
            }
 
        }
 
        System.out.println("End thread: " + Thread.currentThread() + " i++");
 
        // 等待decThread結束
        while (decThread.isAlive())
            ;
 
        long duration = System.nanoTime() - curTime;
        System.out.println("Result: " + count);
        System.out.format("Duration: %.2fs\n", duration / 1.0e9);
    }
 
    private static class DecThread extends Thread {
 
        @Override
        public void run() {
            System.out.println("Start thread: " + Thread.currentThread()
                    + " i--");
            for (int i = 0; i < times; i++) {
                // 進行自減的操作
                synchronized (SynchronizedTest.class) {
                    count--;
                }
            }
            System.out
                    .println("End thread: " + Thread.currentThread() + " i--");
        }
    }
}

 

      7.2  程序的運行結果

 

Start thread: Thread[main,5,main] i++
Start thread: Thread[Thread-0,5,main] i--
End thread: Thread[Thread-0,5,main] i--
End thread: Thread[main,5,main] i++
Result: 0
Duration: 0.03s

8、參考的資料

8.1  Java 高並發程序設計(21頁)

8.2  http://www.cnblogs.com/zemliu/p/3298685.html

8.3  Java 並發編程的藝術 (  The Art of Java Concurrency Programming   )  (22-23 頁)
————————————————
版權聲明:本文為CSDN博主「Bwz_Learning」的原創文章,遵循 CC 4.0 BY-SA 版權協議,轉載請附上原文出處鏈接及本聲明。
原文鏈接:https://blog.csdn.net/zbw18297786698/article/details/53420780


免責聲明!

本站轉載的文章為個人學習借鑒使用,本站對版權不負任何法律責任。如果侵犯了您的隱私權益,請聯系本站郵箱yoyou2525@163.com刪除。



 
粵ICP備18138465號   © 2018-2025 CODEPRJ.COM