java 幾種同步方式


java的同步方式

在java主要有以下幾種方式

1.給變量/代碼塊/方法/類添加悲觀鎖避免一個變量更改值的時候對這個變量進行更改。

用 sychronized 來進行同步
由於java的每個對象都有一個內置鎖,當用此關鍵字修飾方法時, 內置鎖會保護整個方法。在調用該方法前,需要獲得內置鎖,否則就處於阻塞狀態。
代碼如:
public synchronized void save(){}
注: synchronized關鍵字也可以修飾靜態方法,此時如果調用該靜態方法,將會鎖住整個類
優缺點:
優點:加上synchronized鎖是為了保證服務器選擇的順序性,使得並發操作的同一時刻有且僅有一個線程能夠讀取/寫入數據表記錄,防止多線程並發造成的臟數據。
缺點:悲觀鎖的引入,將會導致並發吞吐量發生明顯下降,付出相當大的性能代價。且不能獲得鎖是否是已經加上去了。

2.使用特殊域變量(volatile)實現線程同步

它的原理是每次要線程要訪問volatile修飾的變量時都是從內存中讀取,而不是存緩存當中讀取,因此每個線程訪問到的變量值都是一樣的。這樣就保證了同步
a.volatile關鍵字為域變量的訪問提供了一種免鎖機制,
b.使用volatile修飾域相當於告訴虛擬機該域可能會被其他線程更新,
c.因此每次使用該域就要重新計算,而不是使用寄存器中的值
d.volatile不會提供任何原子操作,它也不能用來修飾final類型的變量
因為volatile使用了復合操作的原子性,所以下面的代碼結果一定<=正確值


/**
 * 使用特殊域變量(volatile)實現線程同步
 *   a.volatile關鍵字為域變量的訪問提供了一種免鎖機制,
 *   b.使用volatile修飾域相當於告訴虛擬機該域可能會被其他線程更新,
 *   c.因此每次使用該域就要重新計算,而不是使用寄存器中的值
 *   d.volatile不會提供任何原子操作,它也不能用來修飾final類型的變量
 */

public class VolatileKeywordSynchronization {

    class Bank {
        private volatile int accout = 10;


        public int getAccout() {
            return accout;
        }

        public void saveAccount(int money) {
            accout += money;
            System.out.println("account:" + accout);

        }

    }

    class VolatileThread implements Runnable {

        private Bank bank;

        public VolatileThread(Bank bank) {
            this.bank = bank;
        }

        @Override
        public void run() {

            for (int i = 0; i < 10; i++) {
                bank.saveAccount(10);
                System.out.println(Thread.currentThread().getName() + "-->第" + i + "次當前賬戶余額:" + bank.getAccout() + "元。");
            }
        }
    }


    public void userVolatileThread() {
        Bank bank = new Bank();

        VolatileThread volatileThread = new VolatileThread(bank);

        Thread thread1 = new Thread(volatileThread);

        Thread thread2 = new Thread(volatileThread);

        System.out.println("線程1:");
        thread1.start();

        System.out.println("線程2:");
        thread2.start();


    }

    public static void main(String[] args) {

        VolatileKeywordSynchronization volatileKeywordSynchronization = new VolatileKeywordSynchronization();

        volatileKeywordSynchronization.userVolatileThread();
    }


}

volatile的優缺點
優點:Volatile修飾的成員變量在每次被線程訪問是,都強迫從共享內存中重讀成員變量的值;當成員變量發生變化是,強迫線程將變化值回寫到共享內存;這樣可以讓多個線程總是看到某個成員變量的同一個值,Volatile關鍵字就是提示VM:對於這個成員變量不能保存它的私有拷貝,而應該直接語共享成員變量交互;
缺點: 關鍵字能保證數據的可見性,但不能保證數據的原子性,且volatile只適用變量。
3.AtomicInteger(類)

public class VolatileKeywordSynchronization {

    class Bank {
        private AtomicInteger accout = new AtomicInteger(10);


        public int getAccout() {
            return accout.get();
        }

        public void saveAccount(int money) {
            accout.getAndAdd(money);
            System.out.println("account:" + accout);

        }

    }

    class VolatileThread implements Runnable {

        private Bank bank;

        public VolatileThread(Bank bank) {
            this.bank = bank;
        }

        @Override
        public void run() {

            for (int i = 0; i < 10; i++) {
                bank.saveAccount(10);
                System.out.println(Thread.currentThread().getName() + "-->第" + i + "次當前賬戶余額:" + bank.getAccout() + "元。");
            }
        }
    }


    public void userVolatileThread() {
        Bank bank = new Bank();

        VolatileThread volatileThread = new VolatileThread(bank);

        Thread thread1 = new Thread(volatileThread);

        Thread thread2 = new Thread(volatileThread);

        System.out.println("線程1:");
        thread1.start();

        System.out.println("線程2:");
        thread2.start();


    }

    public static void main(String[] args) {

        VolatileKeywordSynchronization volatileKeywordSynchronization = new VolatileKeywordSynchronization();

        volatileKeywordSynchronization.userVolatileThread();
    }
}

AtomicInteger 類主要利用 CAS (compare and swap) + volatile 和 native 方法來保證原子操作,從而避免 synchronized 的高開銷,執行效率大為提升。 CAS 的原理是拿期望的值和原本的一個值作比較,如果相同則更新成新的值。UnSafe 類的 objectFieldOffset() 方法是一個本地方法,這個方法是用來拿到“原來的值”的內存地址,返回值是 valueOffset。另外 value 是一個 volatile 變量,在內存中可見,因此 JVM 可以保證任何時刻任何線程總能拿到該變量的最新值。
優缺點:
優點:不需要加鎖,就可以保證原子性,減少大量的開銷。
缺點:不能保證代碼塊的原子性

ReentrantLock

ReentrantLock 意思為可重入鎖,指的是一個線程能夠對一個臨界資源重復加鎖。
其原理是使用AQS(AbstractQueuedSynchornized)機制,是采用雙向隊列來進行加鎖。

AQS機制

AQS 是一個設計模板,具體的實現由子類完成,AQS中維護了一個state(共享資源,代表是否可以被加鎖)和一個CLH(先入先出)雙向隊列。把需要等待的線程放進CLH隊列。

ReentrantLock 實現原理

ReentrantLock支持公平鎖和非公平鎖,其中的一些原理是稍微由不同的。

公平鎖

ReentrantLock(true)
reentrantLock.lock(); reentrantLock.unlock();
線程加鎖,先把線程加入隊列中,如果該線程是頭節點,就加鎖執行,如果不是調用本地方法park等待。線程解鎖,更新狀態,並且從尾節點向上追溯到head的下一個節點,並且更新head節點。

非公平鎖

ReentrantLock() ReentrantLock(false)
reentrantLock.lock(); reentrantLock.unlock();
AQS中維護了一個volatile int state(共享資源)和一個CLH隊列。當state=1時代表當前對象鎖已經被占用,其他線程來加鎖時則會失敗,失敗的線程被放入一個FIFO的等待隊列中,然后會被UNSAFE.park()操作掛起,等待已經獲得鎖的線程釋放鎖才能被喚醒。

public class VolatileKeywordSynchronization {
    ReentrantLock reentrantLock = new ReentrantLock();
    class Bank {
        private int accout = 10;

        public int getAccout() {
            return accout;
        }

        public void saveAccount(int money) {
            reentrantLock.lock();
            try {
                accout+=money;
            }finally {
                reentrantLock.unlock();
            }

            System.out.println("account:" + accout);

        }

    }

    class VolatileThread implements Runnable {

        private Bank bank;

        public VolatileThread(Bank bank) {
            this.bank = bank;
        }

        @Override
        public void run() {

            for (int i = 0; i < 10; i++) {
                try {
                    Thread.sleep(200);
                } catch (InterruptedException e) {
                    e.printStackTrace();
                }
                bank.saveAccount(10);

                System.out.println(Thread.currentThread().getName() + "-->第" + i + "次當前賬戶余額:" + bank.getAccout() + "元。");
            }
        }
    }


    public void userVolatileThread() {
        Bank bank = new Bank();

        VolatileThread volatileThread = new VolatileThread(bank);

        Thread thread1 = new Thread(volatileThread);

        Thread thread2 = new Thread(volatileThread);

        System.out.println("線程1:");
        thread1.start();

        System.out.println("線程2:");
        thread2.start();


    }

    public static void main(String[] args) {
        VolatileKeywordSynchronization volatileKeywordSynchronization = new VolatileKeywordSynchronization();

        volatileKeywordSynchronization.userVolatileThread();
    }
}


免責聲明!

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



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