Java執行緒同步的四種方式詳解(建議收藏)

2022-09-28 09:01:10

 Java執行緒同步的四種方式詳解(建議收藏)-mikechen的網際網路架構

Java執行緒同步屬於Java多執行緒與並行程式設計的核心點,需要重點掌握,下面我就來詳解Java執行緒同步的4種主要的實現方式@mikechen

目錄

什麼是執行緒同步

當使用多個執行緒來存取同一個資料時,將會導致資料不準確,相互之間產生衝突,非常容易出現執行緒安全問題,如下圖所示:

Java執行緒同步的四種方式詳解(建議收藏)-mikechen的網際網路架構

比如多個執行緒都在操作同一資料,都打算修改商品庫存,這樣就會導致資料不一致的問題。

執行緒同步的真實意思,其實是「排隊」:幾個執行緒之間要排隊,一個一個對共用資源進行操作,而不是同時進行操作。

所以我們用同步機制來解決這些問題,加入同步鎖以避免在該執行緒沒有完成操作之前,被其他執行緒的呼叫,從而保證了該變數的唯一性和準確性。

 

執行緒同步的幾種方式

Java執行緒同步的四種方式詳解(建議收藏)-mikechen的網際網路架構

1、使用synchronized關鍵字

這種方式比較靈活,修飾一個程式碼塊,被修飾的程式碼塊稱為同步語句塊。

其作用的範圍是大括號{}括起來的程式碼,作用的物件是呼叫這個程式碼塊的物件,如下格式:

synchronized(物件) {                    
   //得到物件的鎖,才能操作同步程式碼
    需要被同步程式碼;
}

通常沒有必要同步整個方法,使用synchronized程式碼塊同步關鍵程式碼即可。

具體的範例如下:

public class SynchronizedThread {
 
    class Bank {
 
        private int account = 200;
 
        public int getAccount() {
            return account;
        }
 
        /**
         * 用同步方法實現
         *
         * @param money
         */
        public synchronized void save(int money) {
            account += money;
        }
 
        /**
         * 用同步程式碼塊實現
         *
         * @param money
         */
        public void save1(int money) {
            synchronized (this) {
                account += money;
            }
        }
    }
 
    class NewThread implements Runnable {
        private Bank bank;
 
        public NewThread(Bank bank) {
            this.bank = bank;
        }
 
        @Override
        public void run() {
            for (int i = 0; i < 10; i++) {
                // bank.save1(10);
                bank.save(10);
                System.out.println(i + "賬戶餘額為:" + bank.getAccount());
            }
        }
 
    }
 
    /**
     * 建立執行緒,呼叫內部類
     */
    public void useThread() {
        Bank bank = new Bank();
        NewThread new_thread = new NewThread(bank);
        System.out.println("執行緒1");
        Thread thread1 = new Thread(new_thread);
        thread1.start();
        System.out.println("執行緒2");
        Thread thread2 = new Thread(new_thread);
        thread2.start();
    }
 
    public static void main(String[] args) {
        SynchronizedThread st = new SynchronizedThread();
        st.useThread();
    }
 
}

 

2.使用ReentrantLock

ReentrantLock類是可重入、互斥、實現了Lock介面的鎖,它與使用synchronized方法具有相同的基本行為和語意,並且擴充套件了其能力。

private int account = 100;
            //需要宣告這個鎖
            private Lock lock = new ReentrantLock();
            public int getAccount() {
                return account;
            }
            //這裡不再需要synchronized 
            public void save(int money) {
                lock.lock();
                try{
                    account += money;
                }finally{
                    lock.unlock();
                }
 
            }
        }

 

synchronized 與 Lock 的對比

ReentrantLock是顯示鎖,手動開啟和關閉鎖,別忘記關閉鎖;

synchronized 是隱式鎖,出了作用域自動釋放;

ReentrantLock只有程式碼塊鎖,synchronized 有程式碼塊鎖和方法鎖;

使用 ReentrantLock鎖,JVM 將花費較少的時間來排程執行緒,執行緒更好,並且具有更好的擴充套件性(提供更多的子類);

優先使用順序:

ReentrantLocksynchronized 同步程式碼塊> synchronized 同步方法

 

3.使用原子變數實現執行緒同步

為了完成執行緒同步,我們將使用原子變數(Atomic***開頭的)來實現。

比如典型代表:AtomicInteger類存在於java.util.concurrent.atomic中,該類表示支援原子操作的整數,採用getAndIncrement方法以原子方法將當前的值遞加。

具體範例如下:

private AtomicInteger account = new AtomicInteger(100);
 
        public AtomicInteger getAccount() {
            return account;
        }
 
        public void save(int money) {
            account.addAndGet(money);
        }

4.ThreadLocal實現執行緒同步

如果使用ThreadLocal管理變數,則每一個使用該變數的執行緒都獲得該變數的副本,副本之間相互獨立,這樣每一個執行緒都可以隨意修改自己的變數副本,而不會對其他執行緒產生影響,從而實現執行緒同步。

具體程式碼範例如下:

//只改Bank類,其餘程式碼與上同
        public class Bank{
            // 建立一個執行緒本地變數 ThreadLocal
            private static ThreadLocal<Integer> account = new ThreadLocal<Integer>(){
                @Override
                //返回當前執行緒的"初始值" 
                protected Integer initialValue(){
                    return 100;
                }
            };
            public void save(int money){
                //設定執行緒副本中的值
                account.set(account.get()+money);
            }
            public int getAccount(){
                //返回執行緒副本中的值 
                return account.get();
            }
        }
 

以上

作者簡介

陳睿|mikechen,10年+大廠架構經驗,《BAT架構技術500期》系列文章作者,專注於網際網路架構技術。

閱讀mikechen的網際網路架構更多技術文章合集

Java並行|JVM|MySQL|Spring|Redis|分散式|高並行