(原創)確保JAVA線程安全的4種常用方法


在Java中可以有很多方法來保證線程安全,比如使用同步方法、同步塊,使用原子類(atomic concurrent classes),實現並發鎖,使用volatile關鍵字,使用不變類和線程安全類。

這里是最基礎的線程安全教程

實際上在volatile的使用上很容易有誤解,以為volatile就可以做原子操作,實際不然。Volatile 變量具有 synchronized 的可見性特性,但是不具備原子特性。這就是說線程能夠自動發現 volatile 變量的最新值。Volatile 變量可用於提供線程安全,但是只能應用於非常有限的一組用例:多個變量之間或者某個變量的當前值與修改后值之間沒有約束。因此,單獨使用 volatile 還不足以實現計數器、互斥鎖或任何具有與多個變量相關的不變式(Invariants)的類(例如 “start <=end”)。

對於volatile修飾的變量,jvm虛擬機只是保證從主內存加載到線程工作內存的值是最新的。

直接上代碼:

import java.util.concurrent.atomic.AtomicInteger;
import java.util.concurrent.locks.Lock;
import java.util.concurrent.locks.ReentrantLock;


public class Counter {
    
    public static int count = 0; //對於值引用來說,多線程操作的是變量的副本,操作完后刷新到主存中.所以不具有原子性。
    //錯誤的volatile使用方法,volatil只是直接進行內存地址操作,但並不能保證線程安全.volatile很容易被誤用,用來進行原子性操作,
    public volatile static int volatileCount = 0; 
    static Object obj =new Object();
    public static AtomicInteger atomicCount;// 正確的方法1,使用原子操作
    
    static class MyObject{// 正確的方法4,使用地址引用,多線程是通過地址操作。值的改變是同一個變量(地址)
        static int mycount=0;
        }

    public static void inc1() {
        MyObject.mycount++;
    }
   
    public static void inc() {
        //這里延遲1毫秒,使得結果明顯
        try {
            Thread.sleep(1);
        } catch (InterruptedException e) {
        }
        
        //典型錯誤1:在資源對象加鎖顯然是不對的,實際上毫無意義
        //Lock lock =new ReentrantLock();     
        //lock.lock();
        //synchronized (obj)  // 正確的方法2,可重人的同步塊操作。這也是最常用的辦法
        {
            count++;
            volatileCount++;
            atomicCount.incrementAndGet();
        }
        //lock.unlock();
    }
 
    public static void main(String[] args) {
 
        //同時啟動100個線程,去進行i++計算,看看實際結果
        atomicCount =new AtomicInteger(0);
        Lock lock =new ReentrantLock(); // 正確的方法3,可重人鎖   ReentrantLock
        Thread threads[]=new Thread[100];
 
        for (int i = 0; i < 100; i++) {
            threads[i]=new Thread(new Runnable() {
                @Override
                public void run() {
                    //lock.lock();// 正確的方法3,可重人鎖   ReentrantLock
                    Counter.inc();
                    //lock.unlock(); 
                    inc1();
                }
            });
            threads[i].start();
        }
        
        //保障線程全部結束
        for(int i=0;i<100;i++){
            try {
                threads[i].join();
            } catch (InterruptedException e) {
                // TODO Auto-generated catch block
                e.printStackTrace();
            }
        }
 
        //如果沒有同步鎖.值有可能不同。
        System.out.println("運行結果1:Counter.count=" + Counter.count);
        //atomicCount值都應該是一致的
        System.out.println("運行結果2:Counter.atomicCount=" + Counter.atomicCount);
        //atomicCount值。如果沒有同步鎖.值有可能不同。
        System.out.println("運行結果3:Counter.volatileCount=" + Counter.volatileCount);
        //使用地址引用,多線程是通過地址操作。值的改變是同一個變量(地址)。值都應該是一致的
        System.out.println("運行結果4:Counter.mycount=" +MyObject.mycount);
    }
    
    
}

 

運行之后,結果可能會這樣

運行結果1:Counter.count=96
運行結果2:Counter.atomicCount=100
運行結果3:Counter.volatileCount=97
運行結果4:Counter.mycount=100

 

如果在52行和54行取消注釋(或者取消32行的注釋),結果必然如下:

運行結果1:Counter.count=100
運行結果2:Counter.atomicCount=100
運行結果3:Counter.volatileCount=100
運行結果4:Counter.mycount=100

 


免責聲明!

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



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