AQS及Java中的多種鎖機制

并發(fā)編程的優(yōu)點和缺點

優(yōu)點:

提升性能

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

業(yè)務(wù)適用

并行計算會比串行計算更適應(yīng)業(yè)務(wù)需求,而并發(fā)編程更適用于該模型

缺點:

頻繁上線文切換

前提是:線程數(shù)量 > CPU核數(shù)

線程安全

數(shù)據(jù)的可見性、原子性、有序性

無鎖并發(fā)編程

Runnable和Callable的區(qū)別

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

為什么使用鎖

并發(fā)操作同一資源

資源競爭、保護

資源胡釵

并發(fā)編程的產(chǎn)生

Java中鎖的種類

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

synchronize的用法

synchronize的用法.png

Java對象的組成

Java對象的組成.png

synchronize 鎖升級

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

輕量鎖就是偏向鎖+自旋

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

synchronize 鎖升級.png

Lock

  • Lock概念
    • 是一個接口,它定義和規(guī)范了"獲取和釋放鎖",Lock接口的實現(xiàn)基本都是通過聚合了一個同步器的子類來完成線程訪問控制的
  • Lock接口方法
    • lock() 獲取鎖
    • lockInterruptibly() 可中斷地進行獲取鎖
    • tryLock() 非阻塞地嘗試獲取鎖
    • tryLock(long time, TimeUnit unit) 有超時時間獲取鎖
    • unlock() 釋放鎖
    • Condition newCondition獲取等待通知組件,該組件與當前鎖綁定,只有當前線程獲取鎖才可以使用組件await(),await()調(diào)用后釋放鎖
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()調(diào)用后釋放鎖
    public Condition newCondition() {
        return null;
    }
}

Condition

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

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

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

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

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

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

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

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() + "發(fā)出信號");
            condition.signalAll();

            reentrantLock.unlock();

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

    }

}

AQS 隊列同步器

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

volatile

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

MESI協(xié)議

  • 將當前處理器緩存行的數(shù)據(jù)寫會系統(tǒng)內(nèi)存
  • 這個寫回內(nèi)存的操作會使其他CPU里緩存了該內(nèi)存地址的數(shù)據(jù)無效
  • volatile下的MESI:
    • Lock前綴的指令會引起處理器緩存寫回內(nèi)存
    • 一個處理器的緩存回寫到內(nèi)存會導(dǎo)致其他處理器的緩存失效
    • 當處理器發(fā)現(xiàn)本地緩存失效后,就會從內(nèi)存中重讀該變量數(shù)據(jù),既可以獲取當前最新值
MESI協(xié)議.png

AQS實現(xiàn)原理

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

同步隊列

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

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

    • prev 前置節(jié)點
    • next 后置節(jié)點
    • thread 獲取同步狀態(tài)的線程
    • waitStatus 等待狀態(tài)
    • nextWaiter 等待隊列中的后置節(jié)點
    AQS同步隊列.png
?著作權(quán)歸作者所有,轉(zhuǎn)載或內(nèi)容合作請聯(lián)系作者
【社區(qū)內(nèi)容提示】社區(qū)部分內(nèi)容疑似由AI輔助生成,瀏覽時請結(jié)合常識與多方信息審慎甄別。
平臺聲明:文章內(nèi)容(如有圖片或視頻亦包括在內(nèi))由作者上傳并發(fā)布,文章內(nèi)容僅代表作者本人觀點,簡書系信息發(fā)布平臺,僅提供信息存儲服務(wù)。

友情鏈接更多精彩內(nèi)容