Java 多線程同步與線程間通信

前言

java多線程同步和通信的方法有如下幾種:

  1. synchronized關鍵字修飾方法或代碼段,實現數據的互斥訪問
  2. volatile修飾變量,實現多線程環境下數據的同步
  3. ReentrantLock可重入鎖,實現數據的互斥訪問
  4. synchronized結合Object的wait和notify方法,實現線程間的等待通知機制
  5. ReentrantLock結合Condition接口的await()和signal()方法,實現線程間的等待通知機制

1、synchronized 關鍵字修飾方法或代碼段,只保證臨界數據是互斥訪問的

java的每個對象都有一個內置鎖,當用synchronized關鍵字修飾時,線程會獲取該對象的內置鎖,其他線程沒有獲取該對象的內置鎖就會進入阻塞狀態。

  • 對象鎖: synchronized關鍵字修飾代碼段時,需要傳入一個對象,通過該對象的內置鎖來實現代碼塊的同步。
    synchronized關鍵字修飾方法時,會將實例化的java對象的內置鎖傳進去,通過該鎖來實現代碼塊的同步。
  • 類鎖: synchronized關鍵字修飾靜態方法,會鎖住該類的Class對象,此時如果調用該靜態方法,將會鎖住整個類。
  • 注意: 同步是一種高開銷的操作,因此應該儘量減少同步的內容。通常沒有必要同步整個方法,使用synchronized代碼塊同步關鍵代碼即可。
package com.kgc;

/**
 * @author:Tokgo J
 * @date:2020/2/23
 * @aim:
 */

public class Demo1 {

    static class MyThread extends Thread{
        Bank bank;
        public MyThread(String 線程, Bank bank){
            this.bank=bank;
        }
        @Override
        public void run(){
            for(int i=0;i<10;i++){
                bank.save(100); // 多線程調用該同步方法
            }
        }
    }

    static class Bank{
        private int account = 100 ; // 臨界數據
        public int getAccount(){
            return account;
        }
        // 同步方法
        public synchronized void save(int money){
            account+=money;
            System.out.println("當前線程:"+Thread.currentThread().getId()+"當前餘額:"+getAccount());
        }
        public void save1(int money){
            //同步代碼塊
            synchronized (this){ // 獲取當前對象鎖
                account+=money;
                System.out.println("當前線程:"+Thread.currentThread().getId()+"當前餘額:"+getAccount());
            }
        }
    }

    public static void main(String[] args) {
        Bank bank = new Bank();
        MyThread th1 = new MyThread("線程1",bank);
        th1.start();
        MyThread th2 = new MyThread("線程2",bank);
        th2.start();
    }
}

2、volatile修飾變量

使用 volatile 修飾域相當於告訴虛擬機該域可能會被其他線程更新,因此可以保證在多線程環境下保證數據的一致性

    class Bank {
        //需要同步的變量加上volatile
        private volatile int account = 100;
        public int getAccount() {
            return account;
        }
        //這裏不再需要synchronized
        public void save(int money) {
            account += money;
        }

3、ReentrantLock可重入鎖,實現數據的互斥訪問

    class Bank{
        private ReentrantLock lock = new ReentrantLock();
        private int account = 100;//臨界區數據
        public int getAccount(){
            return account;
        }
        
        public void put(int money)
        {
            lock.lock();
            try {
                
                account+=money;
                System.out.println(Thread.currentThread().getId()+"存入"+money+" 當前餘額:"+getAccount());
                
            } catch (InterruptedException e) {
                e.printStackTrace();
            } finally {
                lock.unlock();
            }
        }
        
        public void get(int money)
        {
            lock.lock();
            try {
                
                account-=money;
                System.out.println(Thread.currentThread().getId()+"取出"+money+" 當前餘額:"+getAccount());
                
            } catch (InterruptedException e) {
                e.printStackTrace();
            } finally{
                lock.unlock();
            }
        }
    }

4、wait和notify,實現線程間的等待通知機制

通常在synchronized修飾的代碼塊中使用wait、notify/notifyAll函數。

  • 當wait()被執行的時,會釋放當前所持有的鎖,然後讓出CPU,進入等待阻塞狀態;
  • 當notify/notifyAll()被執行時候,會喚醒一個或多個正處於等待狀態的線程,然後繼續往下執行,因此最好在同步代碼塊最後執行notify / notifyAll。
    //消費者線程類,每隔100ms消費一個產品
    class CustomerThread extends Thread{
        Bank bank;
        public CustomerThread(Bank bank) {
            this.bank = bank;
        }
        @Override
        public void run() {
            while(true){
                bank.get(1);
                try {
                    sleep(100);
                } catch (InterruptedException e) {
                    e.printStackTrace();
                }
            }
        }
    }
    //生產者線程類,每隔300ms生產一個產品
    class ProductorThread extends Thread{
        Bank bank;
        public ProductorThread(Bank bank) {
            this.bank = bank;
        }
        @Override
        public void run() {
            while(true){
                bank.put(1);
                try {
                    sleep(300);
                } catch (InterruptedException e) {
                    e.printStackTrace();
                }
            }
        }
    }
    //數據緩衝區
    class Bank{
        private int account = 100;//臨界區數據
        public int getAccount(){
            return account;
        }
        public void put(int money){
            synchronized(this) //獲取當前對象鎖
            {
                if(getAccount() >= 120){ //若數量大於120則阻塞當前線程,釋放對象鎖
                    try {
                        this.wait();
                    } catch (InterruptedException e) {
                        e.printStackTrace();
                    }
                }
                account+=money;//生產一個產品
                System.out.println(Thread.currentThread().getId()+"存入"+money+" 當前餘額:"+getAccount());
                
                this.notifyAll();//喚醒其他線程
            }
        }
        
        public void get(int money){
            synchronized(this)
            {
                if(getAccount() <= 0){ // 若數量小於等於0則阻塞當前線程,釋放對象鎖
                    try {
                        this.wait();
                    } catch (InterruptedException e) {
                        e.printStackTrace();
                    }
                }
                account-=money; // 消費一個產品
                System.out.println(Thread.currentThread().getId()+"取出"+money+" 當前餘額:"+getAccount());
                
                this.notifyAll();//喚醒其他線程
            }
        }
    }
    //主函數調用例子
    public static void main(String[] args)
    {
        Bank bank = new Bank();//創建一個緩衝區對象
        ProductorThread th1 = new ProductorThread(bank);//創建生產者線程1
        th1.start();
        ProductorThread th2 = new ProductorThread(bank);//創建生產者線程2
        th2.start();
        CustomerThread th3 = new CustomerThread(bank);//創建消費者線程1
        th3.start();
    }

5、ReentrantLock結合Condition接口,實現線程間的等待通知機制

    class CustomerThread extends Thread{
        Bank bank;
        public CustomerThread(Bank bank) {
            this.bank = bank;
        }
        @Override
        public void run() {
            while(true){
                bank.get(1);
                try {
                    sleep(100);
                } catch (InterruptedException e) {
                    e.printStackTrace();
                }
            }
        }
    }
    class ProductorThread extends Thread{
        Bank bank;
        public ProductorThread(Bank bank) {
            this.bank = bank;
        }
 
        @Override
        public void run() {
            while(true){
                bank.put(1);
                try {
                    sleep(300);
                } catch (InterruptedException e) {
                    e.printStackTrace();
                }
            }
        }
    }
    class Bank{
        private ReentrantLock lock = new ReentrantLock(); // 創建可重入鎖
        private Condition notEmpty = lock.newCondition(); // 創建非空條件變量
        private Condition notFullCondition = lock.newCondition(); // 創建非滿條件變量
        private int account = 100;//臨界區數據
        public int getAccount(){
            return account;
        }
        public void put(int money){
            lock.lock();
            try {
                if(getAccount() >= 120){ //當數量已滿時,等待非滿條件
                    notFullCondition.await();
                }
                
                //進行生產
                account+=money;
                System.out.println(Thread.currentThread().getId()+"存入"+money+" 當前餘額:"+getAccount());
                
                notEmpty.signal();// 非空條件釋放信號
            } catch (InterruptedException e) {
                e.printStackTrace();
            } finally{
                lock.unlock();
            }
        }
 
        public void get(int money){
            
            lock.lock();
            try {
                if(getAccount() <= 0){ //當數量爲空時,等待非空條件
                    notEmpty.await();
                }
                
                // 進行消費
                account-=money;
                System.out.println(Thread.currentThread().getId()+"取出"+money+" 當前餘額:"+getAccount());
                
                notFullCondition.signal();// 非滿條件釋放信號
            } catch (InterruptedException e) {
                e.printStackTrace();
            } finally{
                lock.unlock();
            }
        }
    }
    public static void main(String[] args)
    {
        Bank bank = new Bank();
        ProductorThread th1 = new ProductorThread(bank);
        th1.start();
        CustomerThread th3 = new CustomerThread(bank);
        th3.start();
    }
發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章