多執行緒的安全問題及解決方案

陳菜頭發表於2020-07-18

概念

當多個執行緒同時共享同一個全域性變數做寫的操作的時候,可能會受到其他執行緒的干擾,就會產生執行緒安全問題,出現資料髒讀

解決方案

核心思想:同一個時刻保證只允許單執行緒執行

方案

  1. 使用java鎖的機制Synchronized、Lock鎖和CAS無鎖機制
  2. 對於程式碼中如果在多執行緒同時執行操作的情況下,可能會受到其他執行緒的干擾的程式碼採用鎖的機制,在同一個時刻只能保證只有一個執行緒去執行,只有獲取到鎖之後,才能夠進入該程式碼塊執行,程式碼執行完之後釋放鎖之後其他執行緒去獲取鎖才可以執行。
  3. 沒有獲取到鎖的執行緒,則一直會排隊阻塞,整個過程是一個悲觀狀態

Synchronized的基本用法

可以定義任意物件作為鎖,但是需要注意:如果該方法為普通方法的情況下,可以使用普通物件或者屬性作為鎖
如果該方法為靜態方法的情況,則必須使用當前類的Class或者是靜態屬性作為鎖
例項方法
如果在普通方法上加上鎖,則預設表示使用this鎖
靜態程式碼塊
如果是在靜態方法上加上鎖,則預設表示使用當前類的.class鎖。

鎖的重入鎖

在這裡插入圖片描述

package com.mayikt;

public class Thread006 implements Runnable {
    @Override
    public void run() {
        System.out.println(Thread.currentThread().getName() + ",我是子執行緒");
        a();
    }

    public synchronized void a() {
        System.out.println(Thread.currentThread().getName() + ",我是方法A");
        //呼叫b方法獲取鎖的時候可以之間獲取this物件了
        b();
    }

    public synchronized void b() {
        System.out.println(Thread.currentThread().getName() + ",我是方法B");
    }
}

多執行緒死鎖產生的原因

在同步中巢狀同步,你中有我,我中有你,會出現死鎖的現象

Lock鎖的基本用法

在使用阻塞等待獲取鎖的方式中,必須在try程式碼塊之外,並且加鎖方法和try程式碼塊之間沒有任何可能丟擲異常的方法呼叫,避免加鎖成功後,在finally無法解鎖
說明一:如果在lock方法與try程式碼塊之間的方法呼叫丟擲異常,那麼無法解鎖,造成其他執行緒無法成功獲取鎖
說明二:如果lock方法在try程式碼塊之內,可能由於丟擲異常,導致在finally程式碼塊中,unlock對未加鎖的物件解鎖,它會呼叫tryRelease,丟擲IlegalMonitorStateException異常
說明三:在Lock物件的lock方法實現中可能丟擲uncheck異常
lock加鎖的程式碼

    private  void ticket() {
        if (count > 0) {
            try {
                Thread.sleep(30);
            } catch (InterruptedException e) {
                e.printStackTrace();
            }
            lock.lock();
            try {
                System.out.println(Thread.currentThread().getName() + "正在出第" + (101 - count) + "張票");
                count--;
            } catch (Exception e) {
                e.printStackTrace();
            } finally {
                lock.unlock();
            }
        }

    }

讀寫鎖ReadAndWriteLock

程式碼例子

package com.mayikt;

import java.util.concurrent.locks.ReentrantReadWriteLock;

public class MyTask {
    ReentrantReadWriteLock lock = new ReentrantReadWriteLock();

    /**
     * 讀讀共享
     */
    public void read() {
        lock.readLock().lock();
        try {
            System.out.println(Thread.currentThread().getName() + ",正在開始讀取");
            Thread.sleep(1000);
            System.out.println(Thread.currentThread().getName() + ",正在結束讀取");
        } catch (Exception e) {

        }

        lock.readLock().unlock();
    }

    /**
     * 寫寫互斥
     */
    public void write() {
        lock.writeLock().lock();
        try {
            System.out.println(Thread.currentThread().getName() + ",正在開始寫入");
            Thread.sleep(1000);
            System.out.println(Thread.currentThread().getName() + ",正在結束寫入");
        } catch (Exception e) {

        }
        lock.writeLock().unlock();

    }

    public static void main(String[] args) {
        MyTask myTask = new MyTask();
/*        for (int i = 0; i < 10; i++) {
            new Thread(() -> {
                myTask.write();
            }).start();
        }*/
      for (int i =0; i <10;i++){
          new Thread(() -> {
              myTask.read();
          }).start();
          new Thread(() -> {
              myTask.write();
          }).start();
      }
    }
}

Lock與Synchronized鎖的區別

  1. Synchronized屬於java內建的關鍵字,而lock鎖是基於aqs封裝的一個鎖的框架
  2. Synchronized當程式碼執行結束自動釋放鎖,而lock需要人工釋放鎖,相對於lock鎖更加靈活
    參考:螞蟻課堂

相關文章