国产xxxx99真实实拍_久久不雅视频_高清韩国a级特黄毛片_嗯老师别我我受不了了小说

資訊專欄INFORMATION COLUMN

BATJ都愛問的多線程面試題

高勝山 / 1938人閱讀

摘要:今天給大家總結一下,面試中出鏡率很高的幾個多線程面試題,希望對大家學習和面試都能有所幫助。指令重排在單線程環境下不會出先問題,但是在多線程環境下會導致一個線程獲得還沒有初始化的實例。使用可以禁止的指令重排,保證在多線程環境下也能正常運行。

下面最近發的一些并發編程的文章匯總,通過閱讀這些文章大家再看大廠面試中的并發編程問題就沒有那么頭疼了。今天給大家總結一下,面試中出鏡率很高的幾個多線程面試題,希望對大家學習和面試都能有所幫助。備注:文中的代碼自己實現一遍的話效果會更佳哦!

并發編程面試必備:synchronized 關鍵字使用、底層原理、JDK1.6 之后的底層優化以及 和ReenTrantLock 的對比

并發編程面試必備:JUC 中的 Atomic 原子類總結

并發編程面試必備:AQS 原理以及 AQS 同步組件總結

該文已加入開源文檔:JavaGuide(一份涵蓋大部分Java程序員所需要掌握的核心知識)。地址:https://github.com/Snailclimb... 
【強烈推薦!非廣告!】阿里云雙11褥羊毛活動:https://m.aliyun.com/act/team1111/#/share?params=N.FF7yxCciiM.hf47liqn 差不多一折,不過僅限阿里云新人購買,不是新人的朋友自己找方法買哦!
一 面試中關于 synchronized 關鍵字的 5 連擊 1.1 說一說自己對于 synchronized 關鍵字的了解

synchronized關鍵字解決的是多個線程之間訪問資源的同步性,synchronized關鍵字可以保證被它修飾的方法或者代碼塊在任意時刻只能有一個線程執行。

另外,在 Java 早期版本中,synchronized屬于重量級鎖,效率低下,因為監視器鎖(monitor)是依賴于底層的操作系統的 Mutex Lock 來實現的,Java 的線程是映射到操作系統的原生線程之上的。如果要掛起或者喚醒一個線程,都需要操作系統幫忙完成,而操作系統實現線程之間的切換時需要從用戶態轉換到內核態,這個狀態之間的轉換需要相對比較長的時間,時間成本相對較高,這也是為什么早期的 synchronized 效率低的原因。慶幸的是在 Java 6 之后 Java 官方對從 JVM 層面對synchronized 較大優化,所以現在的 synchronized 鎖效率也優化得很不錯了。JDK1.6對鎖的實現引入了大量的優化,如自旋鎖、適應性自旋鎖、鎖消除、鎖粗化、偏向鎖、輕量級鎖等技術來減少鎖操作的開銷。

1.2 說說自己是怎么使用 synchronized 關鍵字,在項目中用到了嗎

synchronized關鍵字最主要的三種使用方式:

修飾實例方法,作用于當前對象實例加鎖,進入同步代碼前要獲得當前對象實例的鎖

修飾靜態方法,作用于當前類對象加鎖,進入同步代碼前要獲得當前類對象的鎖 。也就是給當前類加鎖,會作用于類的所有對象實例,因為靜態成員不屬于任何一個實例對象,是類成員( static 表明這是該類的一個靜態資源,不管new了多少個對象,只有一份,所以對該類的所有對象都加了鎖)。所以如果一個線程A調用一個實例對象的非靜態 synchronized 方法,而線程B需要調用這個實例對象所屬類的靜態 synchronized 方法,是允許的,不會發生互斥現象,因為訪問靜態 synchronized 方法占用的鎖是當前類的鎖,而訪問非靜態 synchronized 方法占用的鎖是當前實例對象鎖

修飾代碼塊,指定加鎖對象,對給定對象加鎖,進入同步代碼庫前要獲得給定對象的鎖。 和 synchronized 方法一樣,synchronized(this)代碼塊也是鎖定當前對象的。synchronized 關鍵字加到 static 靜態方法和 synchronized(class)代碼塊上都是是給 Class 類上鎖。這里再提一下:synchronized關鍵字加到非 static 靜態方法上是給對象實例上鎖。另外需要注意的是:盡量不要使用 synchronized(String a) 因為JVM中,字符串常量池具有緩沖功能!

下面我已一個常見的面試題為例講解一下 synchronized 關鍵字的具體使用。

面試中面試官經常會說:“單例模式了解嗎?來給我手寫一下!給我解釋一下雙重檢驗鎖方式實現單利模式的原理唄!”

雙重校驗鎖實現對象單例(線程安全)

public class Singleton {

    private volatile static Singleton uniqueInstance;

    private Singleton() {
    }

    public static Singleton getUniqueInstance() {
       //先判斷對象是否已經實例過,沒有實例化過才進入加鎖代碼
        if (uniqueInstance == null) {
            //類對象加鎖
            synchronized (Singleton.class) {
                if (uniqueInstance == null) {
                    uniqueInstance = new Singleton();
                }
            }
        }
        return uniqueInstance;
    }
}

另外,需要注意 uniqueInstance 采用 volatile 關鍵字修飾也是很有必要。

uniqueInstance 采用 volatile 關鍵字修飾也是很有必要的, uniqueInstance = new Singleton(); 這段代碼其實是分為三步執行:

為 uniqueInstance 分配內存空間

初始化 uniqueInstance

將 uniqueInstance 指向分配的內存地址

但是由于 JVM 具有指令重排的特性,執行順序有可能變成 1->3->2。指令重排在單線程環境下不會出先問題,但是在多線程環境下會導致一個線程獲得還沒有初始化的實例。例如,線程 T1 執行了 1 和 3,此時 T2 調用 getUniqueInstance() 后發現 uniqueInstance 不為空,因此返回 uniqueInstance,但此時 uniqueInstance 還未被初始化。

使用 volatile 可以禁止 JVM 的指令重排,保證在多線程環境下也能正常運行。

1.3 講一下 synchronized 關鍵字的底層原理

synchronized 關鍵字底層原理屬于 JVM 層面。

① synchronized 同步語句塊的情況

public class SynchronizedDemo {
    public void method() {
        synchronized (this) {
            System.out.println("synchronized 代碼塊");
        }
    }
}

通過 JDK 自帶的 javap 命令查看 SynchronizedDemo 類的相關字節碼信息:首先切換到類的對應目錄執行 javac SynchronizedDemo.java 命令生成編譯后的 .class 文件,然后執行javap -c -s -v -l SynchronizedDemo.class

從上面我們可以看出:

synchronized 同步語句塊的實現使用的是 monitorenter 和 monitorexit 指令,其中 monitorenter 指令指向同步代碼塊的開始位置,monitorexit 指令則指明同步代碼塊的結束位置。 當執行 monitorenter 指令時,線程試圖獲取鎖也就是獲取 monitor(monitor對象存在于每個Java對象的對象頭中,synchronized 鎖便是通過這種方式獲取鎖的,也是為什么Java中任意對象可以作為鎖的原因) 的持有權.當計數器為0則可以成功獲取,獲取后將鎖計數器設為1也就是加1。相應的在執行 monitorexit 指令后,將鎖計數器設為0,表明鎖被釋放。如果獲取對象鎖失敗,那當前線程就要阻塞等待,直到鎖被另外一個線程釋放為止。

② synchronized 修飾方法的的情況

public class SynchronizedDemo2 {
    public synchronized void method() {
        System.out.println("synchronized 方法");
    }
}

synchronized 修飾的方法并沒有 monitorenter 指令和 monitorexit 指令,取得代之的確實是 ACC_SYNCHRONIZED 標識,該標識指明了該方法是一個同步方法,JVM 通過該 ACC_SYNCHRONIZED 訪問標志來辨別一個方法是否聲明為同步方法,從而執行相應的同步調用。

1.4 說說 JDK1.6 之后的synchronized 關鍵字底層做了哪些優化,可以詳細介紹一下這些優化嗎

JDK1.6 對鎖的實現引入了大量的優化,如偏向鎖、輕量級鎖、自旋鎖、適應性自旋鎖、鎖消除、鎖粗化等技術來減少鎖操作的開銷。

鎖主要存在四中狀態,依次是:無鎖狀態、偏向鎖狀態、輕量級鎖狀態、重量級鎖狀態,他們會隨著競爭的激烈而逐漸升級。注意鎖可以升級不可降級,這種策略是為了提高獲得鎖和釋放鎖的效率。

關于這幾種優化的詳細信息可以查看:synchronized 關鍵字使用、底層原理、JDK1.6 之后的底層優化以及 和ReenTrantLock 的對比

1.5 談談 synchronized和ReenTrantLock 的區別

① 兩者都是可重入鎖

兩者都是可重入鎖。“可重入鎖”概念是:自己可以再次獲取自己的內部鎖。比如一個線程獲得了某個對象的鎖,此時這個對象鎖還沒有釋放,當其再次想要獲取這個對象的鎖的時候還是可以獲取的,如果不可鎖重入的話,就會造成死鎖。同一個線程每次獲取鎖,鎖的計數器都自增1,所以要等到鎖的計數器下降為0時才能釋放鎖。

② synchronized 依賴于 JVM 而 ReenTrantLock 依賴于 API

synchronized 是依賴于 JVM 實現的,前面我們也講到了 虛擬機團隊在 JDK1.6 為 synchronized 關鍵字進行了很多優化,但是這些優化都是在虛擬機層面實現的,并沒有直接暴露給我們。ReenTrantLock 是 JDK 層面實現的(也就是 API 層面,需要 lock() 和 unlock 方法配合 try/finally 語句塊來完成),所以我們可以通過查看它的源代碼,來看它是如何實現的。

③ ReenTrantLock 比 synchronized 增加了一些高級功能

相比synchronized,ReenTrantLock增加了一些高級功能。主要來說主要有三點:①等待可中斷;②可實現公平鎖;③可實現選擇性通知(鎖可以綁定多個條件)

ReenTrantLock提供了一種能夠中斷等待鎖的線程的機制,通過lock.lockInterruptibly()來實現這個機制。也就是說正在等待的線程可以選擇放棄等待,改為處理其他事情。

ReenTrantLock可以指定是公平鎖還是非公平鎖。而synchronized只能是非公平鎖。所謂的公平鎖就是先等待的線程先獲得鎖。 ReenTrantLock默認情況是非公平的,可以通過 ReenTrantLock類的ReentrantLock(boolean fair)構造方法來制定是否是公平的。

synchronized關鍵字與wait()和notify/notifyAll()方法相結合可以實現等待/通知機制,ReentrantLock類當然也可以實現,但是需要借助于Condition接口與newCondition() 方法。Condition是JDK1.5之后才有的,它具有很好的靈活性,比如可以實現多路通知功能也就是在一個Lock對象中可以創建多個Condition實例(即對象監視器),線程對象可以注冊在指定的Condition中,從而可以有選擇性的進行線程通知,在調度線程上更加靈活。 在使用notify/notifyAll()方法進行通知時,被通知的線程是由 JVM 選擇的,用ReentrantLock類結合Condition實例可以實現“選擇性通知” ,這個功能非常重要,而且是Condition接口默認提供的。而synchronized關鍵字就相當于整個Lock對象中只有一個Condition實例,所有的線程都注冊在它一個身上。如果執行notifyAll()方法的話就會通知所有處于等待狀態的線程這樣會造成很大的效率問題,而Condition實例的signalAll()方法 只會喚醒注冊在該Condition實例中的所有等待線程。

如果你想使用上述功能,那么選擇ReenTrantLock是一個不錯的選擇。

④ 性能已不是選擇標準

二 面試中關于線程池的 4 連擊 2.1 講一下Java內存模型

在 JDK1.2 之前,Java的內存模型實現總是從主存(即共享內存)讀取變量,是不需要進行特別的注意的。而在當前的 Java 內存模型下,線程可以把變量保存本地內存(比如機器的寄存器)中,而不是直接在主存中進行讀寫。這就可能造成一個線程在主存中修改了一個變量的值,而另外一個線程還繼續使用它在寄存器中的變量值的拷貝,造成數據的不一致

要解決這個問題,就需要把變量聲明為 volatile,這就指示 JVM,這個變量是不穩定的,每次使用它都到主存中進行讀取。

說白了, volatile 關鍵字的主要作用就是保證變量的可見性然后還有一個作用是防止指令重排序。

2.2 說說 synchronized 關鍵字和 volatile 關鍵字的區別

synchronized關鍵字和volatile關鍵字比較

volatile關鍵字是線程同步的輕量級實現,所以volatile性能肯定比synchronized關鍵字要好。但是volatile關鍵字只能用于變量而synchronized關鍵字可以修飾方法以及代碼塊。synchronized關鍵字在JavaSE1.6之后進行了主要包括為了減少獲得鎖和釋放鎖帶來的性能消耗而引入的偏向鎖和輕量級鎖以及其它各種優化之后執行效率有了顯著提升,實際開發中使用 synchronized 關鍵字的場景還是更多一些

多線程訪問volatile關鍵字不會發生阻塞,而synchronized關鍵字可能會發生阻塞

volatile關鍵字能保證數據的可見性,但不能保證數據的原子性。synchronized關鍵字兩者都能保證。

volatile關鍵字主要用于解決變量在多個線程之間的可見性,而 synchronized關鍵字解決的是多個線程之間訪問資源的同步性。

三 面試中關于 線程池的 2 連擊 3.1 為什么要用線程池?

線程池提供了一種限制和管理資源(包括執行一個任務)。 每個線程池還維護一些基本統計信息,例如已完成任務的數量。

這里借用《Java并發編程的藝術》提到的來說一下使用線程池的好處:

降低資源消耗。 通過重復利用已創建的線程降低線程創建和銷毀造成的消耗。

提高響應速度。 當任務到達時,任務可以不需要的等到線程創建就能立即執行。

提高線程的可管理性。 線程是稀缺資源,如果無限制的創建,不僅會消耗系統資源,還會降低系統的穩定性,使用線程池可以進行統一的分配,調優和監控。

3.2 實現Runnable接口和Callable接口的區別

如果想讓線程池執行任務的話需要實現的Runnable接口或Callable接口。 Runnable接口或Callable接口實現類都可以被ThreadPoolExecutor或ScheduledThreadPoolExecutor執行。兩者的區別在于 Runnable 接口不會返回結果但是 Callable 接口可以返回結果。

備注: 工具類Executors可以實現Runnable對象和Callable對象之間的相互轉換。(Executors.callable(Runnable task)Executors.callable(Runnable task,Object resule))。

3.3 執行execute()方法和submit()方法的區別是什么呢?

1)execute() 方法用于提交不需要返回值的任務,所以無法判斷任務是否被線程池執行成功與否;

2)submit()方法用于提交需要返回值的任務。線程池會返回一個future類型的對象,通過這個future對象可以判斷任務是否執行成功,并且可以通過future的get()方法來獲取返回值,get()方法會阻塞當前線程直到任務完成,而使用 get(long timeout,TimeUnit unit)方法則會阻塞當前線程一段時間后立即返回,這時候有可能任務沒有執行完。

3.4 如何創建線程池

《阿里巴巴Java開發手冊》中強制線程池不允許使用 Executors 去創建,而是通過 ThreadPoolExecutor 的方式,這樣的處理方式讓寫的同學更加明確線程池的運行規則,規避資源耗盡的風險**

Executors 返回線程池對象的弊端如下:

FixedThreadPool 和 SingleThreadExecutor : 允許請求的隊列長度為 Integer.MAX_VALUE,可能堆積大量的請求,從而導致OOM。

CachedThreadPool 和 ScheduledThreadPool : 允許創建的線程數量為 Integer.MAX_VALUE ,可能會創建大量線程,從而導致OOM。

方式一:通過構造方法實現

方式二:通過Executor 框架的工具類Executors來實現
我們可以創建三種類型的ThreadPoolExecutor:

FixedThreadPool : 該方法返回一個固定線程數量的線程池。該線程池中的線程數量始終不變。當有一個新的任務提交時,線程池中若有空閑線程,則立即執行。若沒有,則新的任務會被暫存在一個任務隊列中,待有線程空閑時,便處理在任務隊列中的任務。

SingleThreadExecutor: 方法返回一個只有一個線程的線程池。若多余一個任務被提交到該線程池,任務會被保存在一個任務隊列中,待線程空閑,按先入先出的順序執行隊列中的任務。

CachedThreadPool: 該方法返回一個可根據實際情況調整線程數量的線程池。線程池的線程數量不確定,但若有空閑線程可以復用,則會優先使用可復用的線程。若所有線程均在工作,又有新的任務提交,則會創建新的線程處理任務。所有線程在當前任務執行完畢后,將返回線程池進行復用。

對應Executors工具類中的方法如圖所示:

四 面試中關于 Atomic 原子類的 4 連擊 4.1 介紹一下Atomic 原子類

Atomic 翻譯成中文是原子的意思。在化學上,我們知道原子是構成一般物質的最小單位,在化學反應中是不可分割的。在我們這里 Atomic 是指一個操作是不可中斷的。即使是在多個線程一起執行的時候,一個操作一旦開始,就不會被其他線程干擾。

所以,所謂原子類說簡單點就是具有原子/原子操作特征的類。

并發包 java.util.concurrent 的原子類都存放在java.util.concurrent.atomic下,如下圖所示。

4.2 JUC 包中的原子類是哪4類?

基本類型

使用原子的方式更新基本類型

AtomicInteger:整形原子類

AtomicLong:長整型原子類

AtomicBoolean :布爾型原子類

數組類型

使用原子的方式更新數組里的某個元素

AtomicIntegerArray:整形數組原子類

AtomicLongArray:長整形數組原子類

AtomicReferenceArray :引用類型數組原子類

引用類型

AtomicReference:引用類型原子類

AtomicStampedRerence:原子更新引用類型里的字段原子類

AtomicMarkableReference :原子更新帶有標記位的引用類型

對象的屬性修改類型

AtomicIntegerFieldUpdater:原子更新整形字段的更新器

AtomicLongFieldUpdater:原子更新長整形字段的更新器

AtomicStampedReference :原子更新帶有版本號的引用類型。該類將整數值與引用關聯起來,可用于解決原子的更新數據和數據的版本號,可以解決使用 CAS 進行原子更新時可能出現的 ABA 問題。

4.3 講講 AtomicInteger 的使用

AtomicInteger 類常用方法

public final int get() //獲取當前的值
public final int getAndSet(int newValue)//獲取當前的值,并設置新的值
public final int getAndIncrement()//獲取當前的值,并自增
public final int getAndDecrement() //獲取當前的值,并自減
public final int getAndAdd(int delta) //獲取當前的值,并加上預期的值
boolean compareAndSet(int expect, int update) //如果輸入的數值等于預期值,則以原子方式將該值設置為輸入值(update)
public final void lazySet(int newValue)//最終設置為newValue,使用 lazySet 設置之后可能導致其他線程在之后的一小段時間內還是可以讀到舊的值。

AtomicInteger 類的使用示例

使用 AtomicInteger 之后,不用對 increment() 方法加鎖也可以保證線程安全。

class AtomicIntegerTest {
        private AtomicInteger count = new AtomicInteger();
      //使用AtomicInteger之后,不需要對該方法加鎖,也可以實現線程安全。
        public void increment() {
                  count.incrementAndGet();
        }
     
       public int getCount() {
                return count.get();
        }
}
4.4 能不能給我簡單介紹一下 AtomicInteger 類的原理

AtomicInteger 線程安全原理簡單分析

AtomicInteger 類的部分源碼:

    // setup to use Unsafe.compareAndSwapInt for updates(更新操作時提供“比較并替換”的作用)
    private static final Unsafe unsafe = Unsafe.getUnsafe();
    private static final long valueOffset;

    static {
        try {
            valueOffset = unsafe.objectFieldOffset
                (AtomicInteger.class.getDeclaredField("value"));
        } catch (Exception ex) { throw new Error(ex); }
    }

    private volatile int value;

AtomicInteger 類主要利用 CAS (compare and swap) + volatile 和 native 方法來保證原子操作,從而避免 synchronized 的高開銷,執行效率大為提升。

CAS的原理是拿期望的值和原本的一個值作比較,如果相同則更新成新的值。UnSafe 類的 objectFieldOffset() 方法是一個本地方法,這個方法是用來拿到“原來的值”的內存地址,返回值是 valueOffset。另外 value 是一個volatile變量,在內存中可見,因此 JVM 可以保證任何時刻任何線程總能拿到該變量的最新值。

關于 Atomic 原子類這部分更多內容可以查看我的這篇文章:并發編程面試必備:JUC 中的 Atomic 原子類總結

五 AQS 5.1 AQS 介紹

AQS的全稱為(AbstractQueuedSynchronizer),這個類在java.util.concurrent.locks包下面。

AQS是一個用來構建鎖和同步器的框架,使用AQS能簡單且高效地構造出應用廣泛的大量的同步器,比如我們提到的ReentrantLock,Semaphore,其他的諸如ReentrantReadWriteLock,SynchronousQueue,FutureTask等等皆是基于AQS的。當然,我們自己也能利用AQS非常輕松容易地構造出符合我們自己需求的同步器。

5.2 AQS 原理分析

AQS 原理這部分參考了部分博客,在5.2節末尾放了鏈接。

在面試中被問到并發知識的時候,大多都會被問到“請你說一下自己對于AQS原理的理解”。下面給大家一個示例供大家參加,面試不是背題,大家一定要假如自己的思想,即使加入不了自己的思想也要保證自己能夠通俗的講出來而不是背出來。

下面大部分內容其實在AQS類注釋上已經給出了,不過是英語看著比較吃力一點,感興趣的話可以看看源碼。

5.2.1 AQS 原理概覽

AQS核心思想是,如果被請求的共享資源空閑,則將當前請求資源的線程設置為有效的工作線程,并且將共享資源設置為鎖定狀態。如果被請求的共享資源被占用,那么就需要一套線程阻塞等待以及被喚醒時鎖分配的機制,這個機制AQS是用CLH隊列鎖實現的,即將暫時獲取不到鎖的線程加入到隊列中。

CLH(Craig,Landin,and Hagersten)隊列是一個虛擬的雙向隊列(虛擬的雙向隊列即不存在隊列實例,僅存在結點之間的關聯關系)。AQS是將每條請求共享資源的線程封裝成一個CLH鎖隊列的一個結點(Node)來實現鎖的分配。

看個AQS(AbstractQueuedSynchronizer)原理圖:

AQS使用一個int成員變量來表示同步狀態,通過內置的FIFO隊列來完成獲取資源線程的排隊工作。AQS使用CAS對該同步狀態進行原子操作實現對其值的修改。

private volatile int state;//共享變量,使用volatile修飾保證線程可見性

狀態信息通過procted類型的getState,setState,compareAndSetState進行操作

//返回同步狀態的當前值
protected final int getState() {  
        return state;
}
 // 設置同步狀態的值
protected final void setState(int newState) { 
        state = newState;
}
//原子地(CAS操作)將同步狀態值設置為給定值update如果當前同步狀態的值等于expect(期望值)
protected final boolean compareAndSetState(int expect, int update) {
        return unsafe.compareAndSwapInt(this, stateOffset, expect, update);
}
5.2.2 AQS 對資源的共享方式

AQS定義兩種資源共享方式

Exclusive(獨占):只有一個線程能執行,如ReentrantLock。又可分為公平鎖和非公平鎖:

公平鎖:按照線程在隊列中的排隊順序,先到者先拿到鎖

非公平鎖:當線程要獲取鎖時,無視隊列順序直接去搶鎖,誰搶到就是誰的

Share(共享):多個線程可同時執行,如Semaphore/CountDownLatch。Semaphore、CountDownLatCh、 CyclicBarrier、ReadWriteLock 我們都會在后面講到。

ReentrantReadWriteLock 可以看成是組合式,因為ReentrantReadWriteLock也就是讀寫鎖允許多個線程同時對某一資源進行讀。

不同的自定義同步器爭用共享資源的方式也不同。自定義同步器在實現時只需要實現共享資源 state 的獲取與釋放方式即可,至于具體線程等待隊列的維護(如獲取資源失敗入隊/喚醒出隊等),AQS已經在頂層實現好了。

5.2.3 AQS底層使用了模板方法模式

同步器的設計是基于模板方法模式的,如果需要自定義同步器一般的方式是這樣(模板方法模式很經典的一個應用):

使用者繼承AbstractQueuedSynchronizer并重寫指定的方法。(這些重寫方法很簡單,無非是對于共享資源state的獲取和釋放)

將AQS組合在自定義同步組件的實現中,并調用其模板方法,而這些模板方法會調用使用者重寫的方法。

這和我們以往通過實現接口的方式有很大區別,這是模板方法模式很經典的一個運用。

AQS使用了模板方法模式,自定義同步器時需要重寫下面幾個AQS提供的模板方法:

isHeldExclusively()//該線程是否正在獨占資源。只有用到condition才需要去實現它。
tryAcquire(int)//獨占方式。嘗試獲取資源,成功則返回true,失敗則返回false。
tryRelease(int)//獨占方式。嘗試釋放資源,成功則返回true,失敗則返回false。
tryAcquireShared(int)//共享方式。嘗試獲取資源。負數表示失敗;0表示成功,但沒有剩余可用資源;正數表示成功,且有剩余資源。
tryReleaseShared(int)//共享方式。嘗試釋放資源,成功則返回true,失敗則返回false。

默認情況下,每個方法都拋出 UnsupportedOperationException。 這些方法的實現必須是內部線程安全的,并且通常應該簡短而不是阻塞。AQS類中的其他方法都是final ,所以無法被其他類使用,只有這幾個方法可以被其他類使用。

以ReentrantLock為例,state初始化為0,表示未鎖定狀態。A線程lock()時,會調用tryAcquire()獨占該鎖并將state+1。此后,其他線程再tryAcquire()時就會失敗,直到A線程unlock()到state=0(即釋放鎖)為止,其它線程才有機會獲取該鎖。當然,釋放鎖之前,A線程自己是可以重復獲取此鎖的(state會累加),這就是可重入的概念。但要注意,獲取多少次就要釋放多么次,這樣才能保證state是能回到零態的。

再以CountDownLatch以例,任務分為N個子線程去執行,state也初始化為N(注意N要與線程個數一致)。這N個子線程是并行執行的,每個子線程執行完后countDown()一次,state會CAS(Compare and Swap)減1。等到所有子線程都執行完后(即state=0),會unpark()主調用線程,然后主調用線程就會從await()函數返回,繼續后余動作。

一般來說,自定義同步器要么是獨占方法,要么是共享方式,他們也只需實現tryAcquire-tryReleasetryAcquireShared-tryReleaseShared中的一種即可。但AQS也支持自定義同步器同時實現獨占和共享兩種方式,如ReentrantReadWriteLock

推薦兩篇 AQS 原理和相關源碼分析的文章:

http://www.cnblogs.com/watery...

https://www.cnblogs.com/cheng...

5.3 AQS 組件總結

Semaphore(信號量)-允許多個線程同時訪問: synchronized 和 ReentrantLock 都是一次只允許一個線程訪問某個資源,Semaphore(信號量)可以指定多個線程同時訪問某個資源。

CountDownLatch (倒計時器): CountDownLatch是一個同步工具類,用來協調多個線程之間的同步。這個工具通常用來控制線程等待,它可以讓某一個線程等待直到倒計時結束,再開始執行。

CyclicBarrier(循環柵欄): CyclicBarrier 和 CountDownLatch 非常類似,它也可以實現線程間的技術等待,但是它的功能比 CountDownLatch 更加復雜和強大。主要應用場景和 CountDownLatch 類似。CyclicBarrier 的字面意思是可循環使用(Cyclic)的屏障(Barrier)。它要做的事情是,讓一組線程到達一個屏障(也可以叫同步點)時被阻塞,直到最后一個線程到達屏障時,屏障才會開門,所有被屏障攔截的線程才會繼續干活。CyclicBarrier默認的構造方法是 CyclicBarrier(int parties),其參數表示屏障攔截的線程數量,每個線程調用await方法告訴 CyclicBarrier 我已經到達了屏障,然后當前線程被阻塞。

關于AQS這部分的更多內容可以查看我的這篇文章:并發編程面試必備:AQS 原理以及 AQS 同步組件總結

Reference

《深入理解 Java 虛擬機》

《實戰 Java 高并發程序設計》

《Java并發編程的藝術》

http://www.cnblogs.com/watery...

https://www.cnblogs.com/cheng...

【強烈推薦!非廣告!】阿里云雙11褥羊毛活動(10.29-11.12):https://m.aliyun.com/act/team1111/#/share?params=N.FF7yxCciiM.hf47liqn 。一句話解析該次活動:新用戶低至一折購買(1核2g服務器僅8.3/月,比學生機還便宜,真的強烈推薦屯3年)。老用戶可以加入我的戰隊,然后分享自己的鏈接,可以獲得紅包和25%的返現,我們的戰隊目前300位新人,所以可以排進前100,后面可以瓜分百萬現金(按拉新人數瓜分現金,拉的越多分的越多!不要自己重新開戰隊,后面不能參與瓜分現金)。

你若盛開,清風自來。 歡迎關注我的微信公眾號:“Java面試通關手冊”,一個有溫度的微信公眾號。公眾號后臺回復關鍵字“1”,可以免費獲取一份我精心準備的小禮物哦!

文章版權歸作者所有,未經允許請勿轉載,若此文章存在違規行為,您可以聯系管理員刪除。

轉載請注明本文地址:http://specialneedsforspecialkids.com/yun/71990.html

相關文章

  • Spring Aware 到底是什么?

    摘要:代碼示例自定義實現注冊運行和預想一樣,輸出結果為,如果移除掉注解的屬性,輸出結果為總結在大多數情況下,我們應該避免使用任何接口,除非我們需要它們。 showImg(https://segmentfault.com/img/remote/1460000019807821?w=1920&h=1080); 通過如下前序兩篇文章: Spring Bean 生命周期之我從哪里來? Spring...

    mingzhong 評論0 收藏0
  • 結束了我短暫的秋招,說點自己的感受

    摘要:總體來說,玄武科技的真的很熱情,為他們點個贊,雖然自己最后沒能去玄武科技,然后就是技術面非常簡單,面和高管面也都還好,不會有壓抑的感覺,總體聊得很愉快。 該文已加入開源文檔:JavaGuide(一份涵蓋大部分Java程序員所需要掌握的核心知識)。地址:https://github.com/Snailclimb... 秋招歷程流水賬總結 筆主大四準畢業生,在秋招末流比較幸運地進入了一家...

    KoreyLee 評論0 收藏0
  • 面試官都會問的Mybatis面試,你會這樣回答嗎?

    摘要:最終能和面試官聊的開心愉快投緣的叫面霸。能夠與很好的集成提供映射標簽,支持對象與數據庫的字段關系映射提供對象關系映射標簽,支持對象關系組件維護。使用可以有效的防止注入,提高系統安全性。 showImg(https://segmentfault.com/img/bVbsSlt?w=358&h=269); 一、概述 面試,難還是不難?取決于面試者的底蘊(氣場+技能)、心態和認知及溝通技巧。...

    seanHai 評論0 收藏0
  • 假如我是面試官,我會這樣虐你

    摘要:又是金三銀四的時候,我希望這份面試題能夠祝你一臂之力自我和項目相關自我介紹你覺得自己的優點是你覺得自己有啥缺點你有哪些你為什么要離開上家公司你上家公司在,我們公司在,離這么遠為什么要選擇我們這里上家公司的同事和領導是怎么評價你的介紹下你的上 又是金三銀四的時候,我希望這份面試題能夠祝你一臂之力! 自我和項目相關 1、自我介紹 2、你覺得自己的優點是?你覺得自己有啥缺點? 3、你有哪些 ...

    Benedict Evans 評論0 收藏0

發表評論

0條評論

最新活動
閱讀需要支付1元查看
<