多線程的相關關鍵詞理解 volatile,atomic,synchronized,lock

多線程的相關關鍵詞理解 volatile,atomic,synchronized,lock

**

  1. 造成線程不安全的原因: 多線程操作一個變量的時候,每個線程會保存有自己的副本,線程的內部當進行操作的時候實際用的是副本,而不是主線程本身。 創建副本的時候(其他線程創建的時候),會去到當前主線程本身的值,不會去拿到其他線程副本里面的值,拿到的值可能是 其他線程操作過後的值,所以這個值不一定是最新的值。 當副本賦值完成之後會把自己當前變量的值賦值到主線程本身去,不管別線程有沒有賦值過,所以抹殺了別人的勞動成果造成線程不安全。

========================================================================

**

1. volatile

  1. 先看代碼
public class BankCard4 {
     Account account;
    public BankCard4() {
        account = new Account();
        //默認有100塊
        account.setMoney(100);
    }
    /**
     * 存款
     *
     * @param money
     */
    public  void deposit(long money) {
        long old = account.getMoney();
        account.setMoney(old + money);
        System.out.println(Thread.currentThread().getName() + "充值成功" + account.getMoney());
    }

    /**
     * 取款
     */
    public  void withdraw(long money) {
        long old = account.getMoney();
        if (old < money ) {
            System.out.println(Thread.currentThread().getName() + " 餘額不足! 餘額:" + old);
        }
        if (old - money >= 0) {
            account.setMoney(old - money);
            System.out.println(Thread.currentThread().getName() + " 取款成功 : " + money + " 餘額 :  " + account.getMoney());
        }
    }

}
public class Test {
  //volatile 修飾
    static volatile BankCard4 bankCard = new BankCard4();
    public static void main(String[] args) {
        for (int i = 0; i < 10; i++) {
            new Thread(() -> {
                try {
                    Thread.sleep(200);
                } catch (InterruptedException e) {
                    e.printStackTrace();
                }
                bankCard.deposit(10);
                // bankCard.withdraw(10);
            }).start();
        }
    }
//開了十個線程 去對同一個變量操作,經過 volatile 修飾。理論上我們充值完成後
//應該有200 , 原來有100 , 10個線程 每個線程 充值10塊。
}

結果:

Thread-2充值成功130
Thread-8充值成功180
Thread-7充值成功170
Thread-6充值成功160
Thread-4充值成功160
Thread-5充值成功140
Thread-3充值成功140
Thread-0充值成功130
Thread-1充值成功130
Thread-9充值成功190

Process finished with exit code 0
  1. 結果我們發現 充值過後 最大是 190,沒有得到想要的 結果,而且多次充值結果一樣。如果是真的銀行卡,那太可怕了。
  2. 事實證明了volatile並不是線程安全的 。
  3. 經過volatile修飾,只是保證在創建副本的時候去取其他副本里面的最新的值。
  4. 後面副本賦值到主線程的時候,會拿自己的值去覆蓋其他線程賦值的值,所以造成了線程不安全的原因。

結論: *volatile修飾不能保證線程安全,只能保證線程建立的時候副本的值是最新的。*保證此變量對所有線程的可見性(當一條線程修改這個變量值時,新值其他線程立即得知,不能保證在併發條件下是線程安全)

2.atomic類

  1. 原子包:java.util.concurrent.atomic
    原理: 提供了一組原子類。其基本的特性就是在多線程環境下,當有多個線程同時執行這些類的實例包含的方法時,具有排他性,即當某個線程進入方法,執行其中的指令時,不會被其他線程打斷,而別的線程就像自旋鎖一樣,一直等到該方法執行完成,才由JVM從等待隊列中選擇一個另一個線程進入,這只是一種邏輯上的理解。
    i++ 這就是一個原子操作,就是必須等這個線程的這個操作完成後才能進行其他操作。
    這就是原子操作。
package bank;

import java.util.concurrent.atomic.AtomicLong;
import java.util.concurrent.atomic.AtomicReference;
import java.util.concurrent.locks.Lock;
import java.util.concurrent.locks.ReentrantLock;

/**
 * Created by linving on 2016/11/15.
 */
public class BankCard3 {
   // private AtomicReference<Long> moneny;
    private AtomicLong moneny = new AtomicLong(); //用原子包下面的 AtomicLong 進行操作

    public BankCard3() {
       // moneny = new AtomicReference<>();
        moneny.set(100L);
    }


    /**
     * 存款
     *
     * @param
     */
    public void deposit(long m) {
        Long tmp = moneny.get();
       if(moneny.compareAndSet(tmp, tmp + m)) ;
        System.out.println(Thread.currentThread().getName() + "充值成功" + moneny.get());
    }

    /**
     * 取款
     */
    public void withdraw(long m) {
        long old = moneny.get();

        if (old < m) {
            System.out.println(Thread.currentThread().getName() + " 餘額不足! 餘額:" + old);
        }

        if (moneny.compareAndSet(old, old - m)) {
            System.out.println(Thread.currentThread().getName() + " 取款成功 : " + m + " 餘額 :  " + moneny.get());
        }
    }

}

Thread-1充值成功110
Thread-0充值成功110
Thread-2充值成功110
Thread-3充值成功120
Thread-4充值成功130
Thread-5充值成功140
Thread-6充值成功150
Thread-7充值成功160
Thread-8充值成功170
Thread-9充值成功180

Process finished with exit code 0

從結果看:是線程不安全的。
也只能保證原子操作,也就是取錢,存錢的操作是不會被打斷。
也就是防止加到一半了變量變成其他的其他線程進來,不能保證線程安全。

3. synchronized

  1. 同步鎖的使用
    可以鎖定對象或者方法
  2. synchronized(obj)
    鎖定對象的時候 該對象只能被當前獲得鎖的線程使用,其他線程需要等待。
    鎖定範圍是整個對象。
  3. public synchronized void method()
    鎖定函數,鎖定的改函數只能被獲取到鎖定線程使用,其他線程需要等待。
    鎖定範圍只是加鎖的函數。 其他沒有加鎖的函數可以被其他線程使用。
package bank;

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

/**
 * Created by linving on 2016/11/15.
 */
public class BankCard2 {
    // private AtomicReference<Account> atomicReference;
    Account account;

    public BankCard2() {
        account = new Account();
        account.setMoney(100);
    }


    /**
     * 存款
     *
     * @param money
     */
    public synchronized void deposit(long money) {
        long old = account.getMoney();
        account.setMoney(old + money);
        System.out.println(Thread.currentThread().getName() + "充值成功" + account.getMoney());
    }

    /**
     * 取款
     */
    public synchronized void withdraw(long money) {
        long old = account.getMoney();

        if (old < money ) {
            System.out.println(Thread.currentThread().getName() + " 餘額不足! 餘額:" + old);
        }

        if (old - money >= 0) {
            account.setMoney(old - money);
            System.out.println(Thread.currentThread().getName() + " 取款成功 : " + money + " 餘額 :  " + account.getMoney());
        }
    }

}

結果:

Thread-9充值成功110
Thread-8充值成功120
Thread-6充值成功130
Thread-5充值成功140
Thread-4充值成功150
Thread-7充值成功160
Thread-3充值成功170
Thread-0充值成功180
Thread-1充值成功190
Thread-2充值成功200

Process finished with exit code 0

結果和我們預期的一樣。

4.Lock

  1. Lock 和 synchronized 同樣可以保證線程安全性。
  2. 使用比 synchronized 要靈活,但是複雜了一點:
  3. 可以把鎖鎖在所需要加鎖的代碼塊上面。
  4. 靈活,但是要只注意 死鎖問題。鎖需要自己釋放,synchronized 的鎖執行完代碼後會自己釋放,不存在死鎖問題。lock 則要自己釋放,忘記釋放很容易造成死鎖。
package bank;

import bank.Account;

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

/**
 * Created by linving on 2016/11/15.
 */
public class BankCard {
    // private AtomicReference<Account> atomicReference;
    Account account;
    Lock lock;

    public BankCard() {
        account = new Account();
        account.setMoney(100);
        lock = new ReentrantLock();
    }


    /**
     * 存款
     *
     * @param money
     */
    public void deposit(long money) {
        lock.lock();
        long old = account.getMoney();
        account.setMoney(old + money);
        System.out.println(Thread.currentThread().getName() + "充值成功" + account.getMoney());
        lock.unlock();
    }

    /**
     * 取款
     */
    public void withdraw(long money) {
        lock.lock();
        long old = account.getMoney();

        if (old < money ) {
            System.out.println(Thread.currentThread().getName() + " 餘額不足! 餘額:" + old);
        }

        if (old - money >= 0) {
            account.setMoney(old - money);
            System.out.println(Thread.currentThread().getName() + " 取款成功 : " + money + " 餘額 :  " + account.getMoney());
        }
        lock.unlock();
    }

}

結果:

Thread-0充值成功110
Thread-2充值成功120
Thread-3充值成功130
Thread-1充值成功140
Thread-4充值成功150
Thread-5充值成功160
Thread-6充值成功170
Thread-7充值成功180
Thread-9充值成功190
Thread-8充值成功200

結果和我們想要的一樣。

發佈了59 篇原創文章 · 獲贊 14 · 訪問量 9萬+
發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章