併發編程之關鍵字(synchronized、volatile)

  併發編程主要設計兩個關鍵字:一個是synchronized,另外一個是volatile。下面主要講解這兩個關鍵字,並對這兩個關機進行比較。編程

synchronized緩存

   synchronized是經過JMV種的monitorentermonitorexit指令實現同步。monitorenter指令是在編譯後插入到同步代碼的開始位置,而monitorexit插入到同步代碼的結束位置和異常位置。每個對象都與一個monitor相關聯,當monitor被只有後,它將處於鎖定狀態。多線程

  當一個線程試圖訪問同步代碼時,它必須先得到鎖;退出或者拋出異常時,必須釋放鎖。Java中,每個對象均可以做爲鎖。具體的表現形式有3種:併發

  • 對於普通的同步方法,鎖是當前的實例對象(this對象)
權限修飾符 synchronized 返回值類型 函數名(形參列表..){
       //函數體
}
  • 對於靜態同步方法,鎖是當前類的Class對象
權限修飾符 static synchronized 返回值類型 函數名(形參列表..){
       //函數體
}
  • 對於同步方法塊,鎖是Synchronized括號中配置的對象
    • 鎖對象必須是多線程共享的對象,不然鎖不住
Synchronized(鎖){
   //須要同步的代碼塊
}

  注意:在同步代碼塊/同步方法中調用sleep()不會釋放鎖對象,調用wait()會釋放鎖對象ide

 

  Synchronized提供了一種排他式的數據同步機制,某個線程在獲取monitor lock的時候可能會被阻塞,而這種阻塞有兩個明顯的缺陷:1. 沒法控制阻塞時長; 2. 阻塞不能被中斷函數

public class SyncDefect {

    /**
     *線程休眠一個小時
     */
    public synchronized void syncMethod(){
        try {
            TimeUnit.HOURS.sleep(1);
        } catch (InterruptedException e) {
            e.printStackTrace();
        }
    }


    public static void main(String[] args) throws InterruptedException {
        SyncDefect defect = new SyncDefect();
        new Thread(defect::syncMethod,"t1").start();

        //休眠3毫秒後啓動線程t2,確保t1先進入同步方法
        TimeUnit.MILLISECONDS.sleep(3);
        Thread t2 = new Thread(defect::syncMethod, "t2");
        t2.start();

        //休眠3毫秒後中斷線程t2,確保t2已經啓動
        TimeUnit.MILLISECONDS.sleep(3);
        t2.interrupt();

        System.out.println(t2.isInterrupted()); //true
        System.out.println(t2.getState());  //BLOCKED
    }
}

   針對synchronized的兩個缺點,可使用BooleanLock來解決測試

public interface Lock {

    void lock() throws InterruptedException;

    /**
     * 指定獲取鎖的超時時間
     * @param mills 等待獲取鎖的最大時間
     * @throws InterruptedException
     * @throws TimeoutException
     */
    void lock(long mills) throws InterruptedException, TimeoutException;

    void unlock();

    List<Thread> getBlockedThreads();
}
public class BooleanLock implements Lock {

    /**
     * 記錄取得鎖的線程
     */
    private Thread currentThread;
    /**
     * Bollean開關,標誌鎖是否已經被獲取
     */
    private boolean locked = false;

    private List<Thread> blockedList = new ArrayList<>();

    @Override
    public void lock()  {
        //使用同步代碼塊的方式獲取鎖
        synchronized (this) {
            Thread currentThread = Thread.currentThread();
            //當鎖已經被某個線程獲取,將當前線程加入阻塞隊列,並使用this.wait()釋放thisMonitor
            while (locked){
                try {
                    if(!blockedList.contains(currentThread)){
                        blockedList.add(currentThread);
                    }
                    this.wait();
                } catch (InterruptedException e) {
                    blockedList.remove(currentThread);
                    e.printStackTrace();
                }
            }

            blockedList.remove(currentThread);
            this.locked = true;
            this.currentThread = currentThread;
        }
    }

    @Override
    public void lock(long mills) throws InterruptedException, TimeoutException {
        synchronized (this){
            if(mills <= 0) {//時間不合法,調用默認的lock()
                this.lock();
            } else {
                long remainingMills = mills;
                long endMills = System.currentTimeMillis() + remainingMills;
                while (locked) {
                    if (remainingMills <= 0) {//在指定的時間內未獲取鎖或者當前線程被其它線程喚醒,拋出異常
                        throw new TimeoutException(Thread.currentThread().getName()+" can't get lock during "+mills);
                    }
                    if(!blockedList.contains(Thread.currentThread())){
                        blockedList.add(Thread.currentThread());
                    }
                    //等待remainingMills後從新嘗試獲取鎖
                    this.wait(remainingMills);
                    remainingMills = endMills - System.currentTimeMillis();
                }
                blockedList.remove(Thread.currentThread());
                this.locked = true;
                this.currentThread = Thread.currentThread();
            }
        }
    }

    @Override
    public void unlock() {
        synchronized (this) {
            if(Thread.currentThread() == currentThread) {
                this.locked = false;
                this.notifyAll();
            }
        }
    }

    @Override
    public List<Thread> getBlockedThreads() {
        return Collections.unmodifiableList(blockedList);
    }
}
/**
* 測試阻塞中斷
*/
public class BooleanLockInterruptTest {

    private final Lock lock = new BooleanLock();

    public void syncMethod() {
        try {
            lock.lock();
            System.out.println(Thread.currentThread().getName()+" get lock.");
            TimeUnit.HOURS.sleep(1);
        } catch (InterruptedException e) {
            e.printStackTrace();
        } finally {
            System.out.println("BLOCKED THREAD :"+lock.getBlockedThreads());
            lock.unlock();
        }
    }

    public static void main(String[] args) throws InterruptedException {
        BooleanLockInterruptTest test = new BooleanLockInterruptTest();

        new Thread(test::syncMethod,"t1").start();
        TimeUnit.MILLISECONDS.sleep(3);
        Thread t2 = new Thread(test::syncMethod, "t2");
        t2.start();
        TimeUnit.MILLISECONDS.sleep(3);
        t2.interrupt();
        System.out.println(t2.isInterrupted()); //true
        System.out.println(t2.getState());  //RUNNABLE
    }
}
/**
* 測試超時
*/
public class BooleanLockTimeOutTest {

    private final Lock lock = new BooleanLock();

    public void syncTimeOutMethod() {
        try {
            lock.lock(1000);
            System.out.println(Thread.currentThread().getName()+" get lock.");
            TimeUnit.HOURS.sleep(1);
        } catch (InterruptedException | TimeoutException e) {
            e.printStackTrace();
        } finally {
            lock.unlock();
        }
    }
    
    public static void main(String[] args) throws InterruptedException {
        BooleanLockTimeOutTest test = new BooleanLockTimeOutTest();

        new Thread(test::syncTimeOutMethod,"t1").start();
        TimeUnit.MILLISECONDS.sleep(3);
        new Thread(test::syncTimeOutMethod, "t2").start();
    }
}

   針對是synhronized還有一些概念及相關知識點須要補充優化

  • Monitor
    • 每個對象都與一個Monitor相關聯,一個monitor的lock在某一刻只能被一個線程獲取。
    • monitor有一個計數器,當爲0時,該monitor的lock未被獲取;當有線程持獲取monitor時,則monitor計數器加一,釋放時減一。
    • Monitor分爲This Monitor和Class Monitor。This Monitor對應類的實例方法,Class Monitor對應類的靜態方法。
    • synchronized關鍵字實例方法時,爭取的是同一個monitor的鎖,與之關聯的引用是ThisMonitor的實例引用。即: 同一個類中的不一樣多線程方法,使用的是同一個鎖
    • 將靜態方法聲明爲synchronized。該靜態方法被調用後,對應的class對象將會被鎖住(使用的是ClassMonitor)。其餘線程沒法調用該class對象的全部靜態方法, 直到資源被釋放。
  • Synchronized使用wait()進入條件對象的等待集,使用notifyAll()/notify()喚醒等待集中的線程。
public synchronized void transfer(int from , int to,
   double amount) throws InterruptedException{
        while(accounts[from] < amount){
            //wait on intrinsic object lock’s single condition
             wait();
        }
        accounts[from] -= amount;
        accounts[to] += amount;
        //notify all threads waiting on the condition
        notifyAll();
}

 

volatilethis

  volatile是輕量級的synchronized,它爲實例域的同步訪問提供了一種免鎖機制,不會引發線程上下文的切換和調度。它在多處理器開發中保證了共享變量的「可見性「,若是一個屬性被聲明成volatile,Java模型會確保全部的線程看到這個變量的值時一致的。【volatile變量不能提供原子性】spa

  volatile主要用來鎖住一個屬性,在對該屬性的值進行寫操做時,會將數據寫回主存,並將CPU裏緩存了該內存地址的數據無效。【線程在對volatile修飾的變量進行讀寫操做時,會首先檢查線程緩存的值是否失效,若是失效,就會從主存中把數據讀到線程緩存裏】。 volatile本質上就是告訴JVM當前變量的值須要從主存中讀取,當前變量的值被修改後直接刷新到主存中,且不須要被編譯器優化(即:禁止命令重排)。

  使用示範:

private volatile boolean done;
public boolean isDone(){
    return done;
}
public void setDone(boolean done){
    this.done = done;
}

// Same as

private boolean done;
public synchronized boolean isDone(){
    return done;
}
public synchronized void setDone(boolean done){
    this.done = done;
}

 

比較synchronized和volatile

 
volatile
synchronized
做用對象
實例變量、類變量
方法、代碼塊
原子性
不具有
具有
可見性
具有
具有
可見性原理
使用機器指令的方式迫使其它工做內存中的變量失效
利用monitor鎖的排它性實現
是否會指令重排
是否形成線程阻塞
相關文章
相關標籤/搜索