AQS學習小總結

AQS

概念

AbstractQueueSyncronizer 同步發生器 用於構建LOCK JUC下面的ReentrantLock就是基於AQS實現的,實際上JUC下面的很多類都是通過AQS實現的

基本思想

通過內置的FIFO同步隊列來完成線程爭奪資源的管理工作

CLH同步隊列

每一個線程就是一個Node,有兩個指針,一個指向前驅,一個指向後繼.

最前端是一個同步器,是一個傀儡節點,兩個指針分別指向head頭和tail尾

競爭資源用一個同步的狀態去描述 (volatile int state)

如果state等於0表示沒有線程去佔用它,大於等於1時有線程佔用它
在這裏插入圖片描述

當一個線程來了之後首先會去判斷state的值,如果爲0再去拿,否則進入等待隊列中

每個線程要做的事:

  1. 通過自旋獲取鎖(tryAcquire)
  2. 釋放鎖

四個waitState

  1. CANCELED = 1 取消(因中斷或者完成退出隊列)
  2. SIGNAL = -1 信號(節點的繼任節點被阻塞)
  3. CONDITION = -2 條件(條件阻塞)
  4. PROPAGATE = -3 等待狀態傳播(共享模式下頭結點的狀態)

自定義鎖

一. 鎖

AQS寫一個鎖

子類定義爲非公共內部幫助類(私有內部類繼承AQS),寫鎖的時候的一個幫助器,提供獲取鎖和釋放鎖的功能.

–>模板

acquire() 以獨佔模式獲取對象,忽略中斷

acquireShared() 以共享模式獲取對象,忽略中斷

tryAcquire() 試圖在獨佔模式下獲取對象狀態

tryAcquireShared() 試圖在共享模式下獲取對象狀態

release() 以獨佔方式釋放對象

releaseShared() 以共享方式釋放對象

可重入性:同一個鎖多統一資源進行佔有的時候,直接分配給這個線程.

案例:

package AQS;

import java.util.concurrent.TimeUnit;
import java.util.concurrent.locks.AbstractQueuedSynchronizer;
import java.util.concurrent.locks.Condition;
import java.util.concurrent.locks.Lock;

public class MyLock implements Lock {
    private Helper helper = new Helper();

    private class Helper extends AbstractQueuedSynchronizer {
        //獲取鎖
        @Override
        protected boolean tryAcquire(int arg) {
            if (getState() == 0) {
                //利用CAS原理修改state
                if (compareAndSetState(0, arg)) {
                    //設置當前線程佔有資源
                    setExclusiveOwnerThread(Thread.currentThread());
                    return true;
                }
            }else if (getExclusiveOwnerThread()==Thread.currentThread()){
                //可重入性判斷,如果申請鎖的線程是當前線程,將state加一返回true
                setState(getState()+arg);
                return true;
            }
            return false;
        }

        //釋放鎖
        @Override
        protected boolean tryRelease(int arg) {
            int state = getState() - arg;
            //判斷釋放後是否爲0
            if (state == 0) {
                setExclusiveOwnerThread(null);
                setState(state); //此時爲0
                return true;
            }
            //此時是線程安全的,因爲要"釋放"鎖的前提是已經"拿到"了鎖
            setState(state); //可重入鎖的體現,此時爲state-arg
            return false;
        }

        public Condition newConditionObject() {
            return new ConditionObject();
        }
    }

    @Override
    public void lock() {
        helper.acquire(1);
    }

    @Override
    public void lockInterruptibly() throws InterruptedException {
        helper.acquireInterruptibly(1);
    }

    @Override
    public boolean tryLock() {
        return helper.tryAcquire(1);
    }

    @Override
    public boolean tryLock(long time, TimeUnit unit) throws InterruptedException {
        return helper.tryAcquireNanos(1, unit.toNanos(time));
    }

    @Override
    public void unlock() {
        helper.release(1);
    }

    @Override
    public Condition newCondition() {
        return helper.newConditionObject();
    }
}

package AQS;
import	java.util.concurrent.TimeUnit;

public class Demo {

    private MyLock lock = new MyLock();

    private int m = 0;

    public int next(){
        lock.lock();
        try {
            return m++;
        } finally {
            lock.unlock();
        }
    }

    public static void main(String[] args) {
        Demo demo = new Demo();
        for (int i = 0; i < 20; i++) {
            new Thread(()->{
                System.out.println(demo.next());
            }).start();
        }
    }
}

ReentrantLock

基於AQS實現

公平鎖

非公平鎖

ReentrantReadWriteLock

讀取者優先或寫入者優先強加給鎖訪問的排序.但是,它確實支持可選的公平策略.

讀操作加readLock,寫操作加writeLock.

在write.unlock()之前先加read.lock(),鎖降級,寫鎖降級到讀鎖

二. 併發工具

CountdownLatch

CountDownl atch類位於java util.concurrent包下,利用它可以實現類似計數器的功能。

比如有一個任務A,它要等待其他4個任務執行完畢之後才能執行,可以利用CountDownLatch來實現.

CountDownLatch是通過一個計數器來實現的, 計數器的初始值爲線程的數量。

每當一個線程完成了自己的任務後,計數器的值就會減1。當計數器值到達0時,它表示所有的線程已經完成了任務,然後在閉鎖上等待的線程就可以恢復執行任務。

CountdownLatch如何工作?

構造一個用給定計數初始化的CountdownLatch(int count)

構造器中的計數值(count)實際上就是團鎖需要等待的線程數量。這個值只能被設置一次,而且CountDownlatch沒有提供任何機制去重新設置這個計數值。
與CountDownLatch的第一次交互是主線程等待其他線程。主線程必須在啓動其他線程後立即調用CountDownlatch.await()方法。這樣主線程的操作就會在這個方法上阻塞,直到其他線程完成各自的任務。
其他N個線程必須引用閉鎖對象,因爲他們需要通知CountDownLatch對象他們已經完成了各自的任務。這種通知機制是通過CountDownLatch.countDown()方法來完成的;
每調用一次這個方法,在構造函數中初始化的count值就減1。所以當N個線程都調用了這個方法,count的值等於0,然後主線程就能通過await()方法,恢復執行自己的任務。

案例:

在這裏插入圖片描述

package AQS;

import java.util.ArrayList;
import java.util.Arrays;
import java.util.List;
import java.util.Random;
import java.util.concurrent.CountDownLatch;
import java.util.concurrent.TimeUnit;

public class CountDownDemo {

    private static List<String> company = Arrays.asList("東方航空","南方航空","海南航空");
    private static List<String> flightLIst = new ArrayList<>();

    public static void main(String[] args) throws InterruptedException {
        String origin = "Beijing";
        String destination = "Shanghai";
        CountDownLatch latch = new CountDownLatch(company.size());
        for (int i = 0; i < company.size(); i++) {
            String name = company.get(i);
            new Thread(()->{
                System.out.println(name+"查詢從"+origin+"到"+destination+"的機票");
                int val = new Random().nextInt(10);//隨機產生票數
                try {
                    TimeUnit.SECONDS.sleep(val);
                    flightLIst.add(name+"--"+val);
                    System.out.println(name+"查詢成功");
                    latch.countDown();
                } catch (InterruptedException e) {
                    e.printStackTrace();
                }
            }).start();
        }
        latch.await();
        System.out.println("======查詢結果======");
        flightLIst.forEach(str->System.out.println(str));
    }
}

結果如下:(3個線程查詢機票,主線程返回查詢的結果list)
在這裏插入圖片描述

CyclicBarrier

需要所有的子任務都完成時,才執行主任務,這個時候就可以選擇使用CyclicBarrier.

基本原理:

每個線程執行時,都會碰到一個屏障,直到所有線程執行結束,然後屏障便會打開,使所有線程繼續往下執行。

案例:

package AQS;

import java.util.Random;
import java.util.concurrent.CyclicBarrier;
import java.util.concurrent.TimeUnit;

public class CyclicBarrierDemo {
    public static void main(String[] args) {
        CyclicBarrier barrier = new CyclicBarrier(8);
        for (int i = 0; i < 8; i++) {
            new Thread(()->{
                try {
                    TimeUnit.SECONDS.sleep(new Random().nextInt(10));
                    System.out.println(Thread.currentThread().getName()+"準備好了");
                    barrier.await();
                } catch (Exception e) {
                    e.printStackTrace();
                }
                System.out.println(Thread.currentThread().getName()+"起跑");
            },"運動員"+i).start();
        }
    }
}

運行結果:

在這裏插入圖片描述

在這裏插入圖片描述

在CyclicBarrier的內部定義了一個Lock對象,每當-個線程調用await方法時,將攔截的線程數加1,然後判斷剩餘攔截數是否爲初始值parties,如果不是,進入Lock對象的條件隊列等待。如果是,執行barrierAction對象的Runnable方法,然後將鎖的條件隊列中的所有線程放入鎖等待隊列中,這些線程會依次的獲取鎖、釋放鎖.

CyclicBarrier的兩個構造函數: CyclicBarrier(int parties)和CyclicBarrier(int parties,Runnable barrierAction) :前者只需要聲明需要攔截的線程數即可,而後者還需要定義一個等待所有線程到達屏障優先執行的Runnable對象。

一般情況下對於兩個非常相似的類,我們一-般都會想當然地去把他們進行類比。對於CountDownLatch 和CyclicBarrier 兩個類,我們可以看到CountDownLatch 類都是一個類似於集結點的概念,很多個線程做完事情之後等待其他線程完成,全部線程完成之後再恢復運行。不同的是CountDownLatch 類需要你自己調用countDown() 方法減少一個計數,然後調用await() 方法即可。而CyclicBarrier 則直接調用await() 方法即可。所以從上面來看,CountDownLatch 更傾向於多個線程合作的情況,等你所有東西都準備好了,我這邊就自動執行了。CyclicBarrier 則是我們都在一 個地方等你,大家到齊了,大家再一起執行。

Semaphore

熟悉的操作系統信號量PV機制

發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章