java 面試知識點筆記(十二)多線程與併發-原理 中下篇

問:synchronized和ReentrantLock的區別?java

ReentrantLock(可重入鎖)數組

  • 位於java.util.concurrent.locks包(著名的juc包是由Doug lea大神寫的AQS抽象類框架衍生出來的應用)
  • 和CountDownLatch、FutureTask、Semaphore同樣基於AQS實現
  • 可以實現比synchronized更細粒度的控制,如控制fairness
  • 調用lock()後,必須調用unlock()釋放鎖
  • 性能未必比synchronized高,而且也是可重入的

ReentrantLock公平性設置緩存

ReentrantLock fairLock = new ReentrantLock(true);

參數爲ture時,傾向於將鎖賦予等待時間最久的線程安全

公平鎖:獲取鎖的順序按前後調用lock方法的順序(慎用,一般公平性沒有想象的那麼重要,java默認的調用策略不多會有飢餓狀況的發生,與此同時若要保證公平性,會增長額外的開銷,致使必定的吞吐量降低)多線程

非公平鎖:獲取鎖的順序是無序的,synchronized是非公平鎖併發

例子:app

package interview.thread;

import java.util.concurrent.locks.ReentrantLock;

/**
 * @Author: cctv
 * @Date: 2019/5/21 11:46
 */
public class ReentrantLockDemo implements Runnable {

    private static ReentrantLock lock = new ReentrantLock(false);

    @Override
    public void run() {
        while (true) {
            lock.lock();
            System.out.println(Thread.currentThread().getName() + " get lock");
            try {
                Thread.sleep(1000);
            } catch (InterruptedException e) {
                e.printStackTrace();
            } finally {
                lock.unlock();
            }
        }

    }

    public static void main(String[] args) {
        ReentrantLockDemo rtld = new ReentrantLockDemo();
        Thread t1 = new Thread(rtld);
        Thread t2 = new Thread(rtld);
        t1.start();
        t2.start();
    }
}

公平鎖 new ReentrantLock(true);框架

非公平鎖 new ReentrantLock(false);jvm

ReentrantLock將鎖對象化ide

  • 判斷是否有線程,或者某個特定線程再排隊等待獲取鎖
  • 帶超時的獲取鎖嘗試
  • 感知有沒有成功獲取鎖

是否能將wait\notify\notifyAll對象化

  • java.util.concurrent.locks.Condition

總結synchronized和ReentrantLock的區別:

  1. synchronized是關鍵字,ReentrantLock是類
  2. ReentrantLock能夠對獲取鎖的等待時間進行設置,避免死鎖
  3. ReentrantLock能夠獲取各類鎖信息
  4. ReentrantLock能夠靈活的實現多路通知
  5. 機制:synchronized操做MarkWord,ReentrantLock調用Unsafe類的park()方法

 

問:什麼是Java內存模型中的happens-before?

java內存模型(即Java Memory Model 簡稱JMM)是一種抽象概念,並不真實存在,它描述的是一組規則或規範,經過這組規範定義了程序中各個變量(包括實例字段,靜態字段和構成數組對象的元素)的訪問方式

JMM中的主內存

  1. 存儲java實例對象
  2. 包括成員變量、類信息、常量、靜態變量
  3. 屬於數據共享區域,多線程併發操做時會引起線程安全問題

JMM中的工做內存

  1. 存儲當前方法的全部本地變量信息,本地變量對其餘線程不可見(工做內存是存儲的主內存的變量的拷貝)
  2. 字節碼行號指示器、native方法信息
  3. 屬於線程私有的數據區域,不存在線程安全問題

JMM和java內存區域劃分是不一樣的概念層次:

  • JMM描述的是一組規則,圍繞原子性、有序性、可見性展開
  • 類似點:都存在共享區域和私有區域

主內存和工做內存的數據存儲類型以及操做方式概括:

  • 方法裏的基本數據類型本地變量將直接存儲在工做內存的棧幀結構中
  • 引用類型的本地變量:引用存儲在工做內存中,實例存儲在主內存中
  • 成員變量、static變量、類信息均會被存儲在主內存中
  • 主內存共享的方式線程各拷貝一份數據到工做內存,操做完成後刷新回主內存

JMM如何解決可見性問題?

首先要講下重排序:在執行程序時,爲了提升性能,編譯器和處理器經常會對指令作重排序。

指令重排序須要知足的條件:

  1. 單線程環境下不能改變程序運行的結果
  2. 存在數據依賴關係的不容許重排序(沒法經過happens-before原則推導出來的,才能進行指令重排序)

happens-before的八大原則:

  1. 程序次序規則:一個線程內,按照代碼順序,書寫在前面的操做先行發生於書寫在後面的操做;
  2. 鎖定規則:一個unLock操做先行發生於後面對同一個鎖額lock操做;
  3. volatile變量規則:對一個變量的寫操做先行發生於後面對這個變量的讀操做;
  4. 傳遞規則:若是操做A先行發生於操做B,而操做B又先行發生於操做C,則能夠得出操做A先行發生於操做C;
  5. 線程啓動規則:Thread對象的start()方法先行發生於此線程的每一個一個動做;
  6. 線程中斷規則:對線程interrupt()方法的調用先行發生於被中斷線程的代碼檢測到中斷事件的發生;
  7. 線程終結規則:線程中全部的操做都先行發生於線程的終止檢測,咱們能夠經過Thread.join()方法結束、Thread.isAlive()的返回值手段檢測到線程已經終止執行;
  8. 對象終結規則:一個對象的初始化完成先行發生於他的finalize()方法的開始;

若是兩個操做不知足上述任意一個happens-before規則,那麼這兩個操做就沒有順序的保障,JVM能夠對這兩個操做進行重排序

若是操做A happens-before B 那麼操做A在內存上所作的操做對操做B都是可見的

 

volatile:jvm提供的輕量級同步機制,解決了內存可見性問題,但並非線程安全的(能夠配合synchronized達到線程安全目的)

  • 保證被volatile修飾的共享變量對全部線程老是可見的
  • 禁止指令重排序優化

問:volatile變量如何當即可見?

當寫一個volatile變量時,JMM會把該線程對應的工做內存中的共享變量刷新到主內存中

當讀取一個volatile變量時,JMM會把該線程對應的工做內存置爲無效,該線程只能從主內存中讀取變量

問:volatile變量如何禁止重排優化?

內存屏障(Memory Barrier)

  1. 保證特定操做的執行順序
  2. 保證某些變量的內存可見性

經過插入內存屏障指令禁止在內存屏障先後的指令執行重排序優化,強制刷出各類CPU的緩存數據,所以任何CPU上的線程都能讀取到這些數據的最新版本

package interview.thread;

/**
 * 單例模式的雙重檢測實現
 *
 * @Author: cctv
 * @Date: 2019/5/21 17:19
 */
public class Singleton {
    // 禁止指令重排序優化
    private volatile static Singleton instance;

    private Singleton() {
    }

    public static Singleton getInstance() {
        //第一次檢測
        if (instance == null) {
            //同步
            synchronized (Singleton.class) {
                // 第二次檢測
                if (instance == null) {
                    // 多線程環境下可能會出現問題的地方(會出現指令重排序,致使instance先賦值後初始化Singleton)
                    instance = new Singleton();
                }
            }
        }
        return instance;
    }
}

 

volatile和synchronized的區別

  • volatile本質是在告訴jvm當前變量在寄存器(工做內存)中的值是不肯定的,須要從主存中讀取; synchronized則是鎖定當前變量,只有當前線程能夠訪問該變量,其餘線程被阻塞住。
  • volatile僅能使用在變量級別;synchronized則可使用在變量、方法、和類級別的
  • volatile僅能實現變量的修改可見性,不能保證原子性;而synchronized則能夠保證變量的修改可見性和原子性
  • volatile不會形成線程的阻塞;synchronized可能會形成線程的阻塞。
  • volatile標記的變量不會被編譯器優化;synchronized標記的變量能夠被編譯器優化

問:談談 CAS(Compare and Swap)?

一種高效實現線程線程安全的方法

  • 支持原子更新操做,適用於計數器,序列發生器等場景
  • 屬於樂觀鎖機制,號稱lock-free(其實底層仍是有加鎖)
  • CAS操做失敗時由開發者決定是否繼續嘗試,仍是執行別的操做,因此失敗線程不會阻塞掛起

cas思想:

包含三個操做數--內存位置V 預期原值A 和 新增B

  • 直接使用JUC的atomic包提供了經常使用的原子性數據類型以及引用、數組等相關原子類型和更新操做工具,是不少線程安全程序的首選
  • Unsafe類雖提供了CAS服務,但由於可以操縱任意內存地址的讀寫而有隱患
  • java9之後可使用Variable Handle API來替代Unsafe

缺點:

  1. 若循環時間長,則開銷很大
  2. 只能保證一個共享變量的原子操做
  3. ABA問題(解決方法:AtomicStampedReference,它能夠控制變量的版本保證正確性)
相關文章
相關標籤/搜索