AQS及Java中的多種鎖機制

并發編程的優點和缺點

優點:

提升性能

將多核CPU的計算能力發揮到機制,性能得到提升

業務適用

并行計算會比串行計算更適應業務需求,而并發編程更適用于該模型

缺點:

頻繁上線文切換

前提是:線程數量 > CPU核數

線程安全

數據的可見性、原子性、有序性

無鎖并發編程

Runnable和Callable的區別

  • Runnable從JDK1.1就有了,而Callble是JDK1.5之后增加的
  • Callable執行方法是call(),Runnable執行方法是run()
  • Callable任務執行后可返回值,而Runnable任務是不能返回值(void)
  • call方法可以拋出異常,run方法不可以
  • 運行Callable任務可拿到Future對象,即異步計算結果:
    • 它提供了檢查計算是否完成的方法,以等待計算的完成,并檢索計算的結果
    • 通過Future對象可以了解任務執行情況,可取消任務的執行,還可以獲取執行結果
  • 使用線程池方式運行:
  • Runnable用ExecutorService的execute方法
  • Callable使用submit方法

為什么使用鎖

并發操作同一資源

資源競爭、保護

資源胡釵

并發編程的產生

Java中鎖的種類

  • synchronized
    • synchronized關鍵字,語義層面的定義及使用
    • 隱式地獲取鎖,將鎖的獲取和釋放固化,即先獲取在釋放當前鎖
  • Lock(JDK1.5)
  • 對比:
    • 非阻塞方式獲取鎖
    • 獲取鎖喉可被中斷
    • 獲取鎖設置超時機制

synchronize的用法

synchronize的用法.png

Java對象的組成

Java對象的組成.png

synchronize 鎖升級

偏向鎖即有一個線程A訪問帶有鎖的同步代碼塊時,會在對象頭的mark word記錄線程的ID,當有線程訪問同步塊時,會比較mark word中的線程id,和當前線程是否一致,如果還是線程A則直接獲取到鎖,當有一個B訪問時,會嘗試CAS將對象頭中的mark wor替換為指向鎖記錄指針,如果替換成功,則當前線程獲得到鎖,如果替換失敗則說明有其他線程在競爭鎖,當前線程使用自旋來獲取鎖,當自旋次數達到一定次數時,就會升級成為重量級鎖,由操作系統Mutexlock實現線程間的切換

輕量鎖就是偏向鎖+自旋

? 自旋的次數可以用個jvm參數設置,但是在jdk1.7之后,就不建議修改這個參數了,因為程序進行了優化,鎖自動優化,鎖彈性升級,它自動判斷大部分情況需要多少自旋次數能解決問題,不斷升級自旋次數

synchronize 鎖升級.png

Lock

  • Lock概念
    • 是一個接口,它定義和規范了"獲取和釋放鎖",Lock接口的實現基本都是通過聚合了一個同步器的子類來完成線程訪問控制的
  • Lock接口方法
    • lock() 獲取鎖
    • lockInterruptibly() 可中斷地進行獲取鎖
    • tryLock() 非阻塞地嘗試獲取鎖
    • tryLock(long time, TimeUnit unit) 有超時時間獲取鎖
    • unlock() 釋放鎖
    • Condition newCondition獲取等待通知組件,該組件與當前鎖綁定,只有當前線程獲取鎖才可以使用組件await(),await()調用后釋放鎖
import java.util.concurrent.TimeUnit;
import java.util.concurrent.locks.Condition;
import java.util.concurrent.locks.Lock;

public class TestLock implements Lock {
    // 獲取鎖
    public void lock() {

    }

    // 可中斷地進行獲取鎖
    public void lockInterruptibly() throws InterruptedException {

    }

    // 非阻塞地嘗試獲取鎖
    public boolean tryLock() {
        return false;
    }

    // 有超時時間獲取鎖
    public boolean tryLock(long time, TimeUnit unit) throws InterruptedException {
        return false;
    }

    // 釋放鎖
    public void unlock() {

    }

    // 獲取等待通知組件,該組件與當前鎖綁定,只有當前線程獲取鎖才可以使用組件await(),await()調用后釋放鎖
    public Condition newCondition() {
        return null;
    }
}

Condition

  • **await(): **造成當前線程在接收到信號或被中斷之前一直處于等待狀態。

  • **await(long time, TimeUnit unit): **造成當前線程在接收到信號、被中斷或達到指定等待時間之前一直處于等待狀態。

  • **long awaitNanos(long nanosTimeout): **造成當前線程在接收到信號、被中斷或達到指定等待時間之前一直處于等待狀態。返回值表示剩余時間,如果在nanosTimeout之前喚醒,那么返回值= nanosTimeout - 消耗時間,如果返回值 <= 0,則可以認定它已經超時了。

  • **awaitUninterruptibly(): **造成當前線程在接收到信號之前一直處于等待狀態。

    • 注意:該方法對中斷不敏感。
  • **boolean awaitUntil(Date deadline): **造成當前線程在接收到信號、被中斷或到達指定最后期限之前一直處于等待狀態。如果沒有到指定時間就被通知,則返回true,否則表示到了指定時間,返回false。

  • **void signal(): **喚醒一個等待線程。該線程從等待放放返回前必須獲得與Condition相關的鎖。

  • **void signalAll(): **喚醒所有等待線程,能夠從等待放放返回的線程,必須獲取與Condition相關的鎖

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

public class TestCondition {

    public static void main(String[] args) {

        final ReentrantLock reentrantLock = new ReentrantLock();
        final Condition condition = reentrantLock.newCondition();

        new Thread(() -> {

            reentrantLock.lock();

            System.out.println(Thread.currentThread().getName() + "拿到了鎖");
            System.out.println(Thread.currentThread().getName() + "等待信號");

            try {
                condition.await();
            } catch (InterruptedException e) {
                e.printStackTrace();
            }

            System.out.println(Thread.currentThread().getName() + "拿到信號");

            reentrantLock.unlock();
        }, "線程1").start();

        new Thread(() -> {

            reentrantLock.lock();

            System.out.println(Thread.currentThread().getName() + "拿到了鎖");

            try {
                Thread.sleep(1000);
            } catch (InterruptedException e) {
                e.printStackTrace();
            }

            System.out.println(Thread.currentThread().getName() + "發出信號");
            condition.signalAll();

            reentrantLock.unlock();

        }, "線程2").start();

    }

}

AQS 隊列同步器

  • AQS:AbstractQueuedSynchronizer 隊列同步器
  • 用來構建鎖或者其他同步組件的基礎框架
  • 用int成員變量表示同步狀態
  • 通過內置的FIFO(先進先出)隊列來完成資源獲取線程的排隊工作
  • 大部分同步需求的基礎
  • 同步狀態:
    • getState()
    • setSate(int newState)
    • compareAndSetState(int expect, int update)
  • 鎖是面向使用者的,它定義了使用者與鎖交互的接口(比如可以允許兩個線程并行訪問),隱藏了實現細節
  • 同步器(AQS)面向的是鎖的創造者OR開發者,它提供了便捷的工具和方法,將同步狀態,線程排隊,等待及喚醒等底層操作封裝起來,讓開發者使用更便捷
  • 鎖和同步器很多地隔離了使用者和實現者所需關注的范圍及各自關注的邏輯細節

volatile

  • 保證不同線程對統一變量操作時的可見性
  • 禁止指令重排序

MESI協議

  • 將當前處理器緩存行的數據寫會系統內存
  • 這個寫回內存的操作會使其他CPU里緩存了該內存地址的數據無效
  • volatile下的MESI:
    • Lock前綴的指令會引起處理器緩存寫回內存
    • 一個處理器的緩存回寫到內存會導致其他處理器的緩存失效
    • 當處理器發現本地緩存失效后,就會從內存中重讀該變量數據,既可以獲取當前最新值
MESI協議.png

AQS實現原理

  • 同步器依賴內部的隊列完成同步狀態的管理,當線程獲取同步狀態失敗時,同步器會將當前線程與等待狀態等信息構造成為一個節點(Node)將其放入同步隊列,同時會阻塞當前線程。同步狀態釋放時,喚醒首節點中的線程,使其再次嘗試獲取同步狀態,
  • 主要包括:
    • 同步隊列
    • 獨占式同步狀態獲取與釋放
    • 共享式同步狀態獲取與釋放
    • 超時獲取同步狀態等同步器的核心數據結構與模板方法

同步隊列

  • 同步隊列中的節點(Node):

    • 保存獲取同步狀態失敗線程的引用,等待狀態以及前置和后置節點
  • 包含的字段:

    • prev 前置節點
    • next 后置節點
    • thread 獲取同步狀態的線程
    • waitStatus 等待狀態
    • nextWaiter 等待隊列中的后置節點
    AQS同步隊列.png
?著作權歸作者所有,轉載或內容合作請聯系作者
平臺聲明:文章內容(如有圖片或視頻亦包括在內)由作者上傳并發布,文章內容僅代表作者本人觀點,簡書系信息發布平臺,僅提供信息存儲服務。