自旋鎖在高并發(fā)的用處,實現(xiàn)各種鎖

虛假喚醒:
由于莫名其妙的原因,線程有可能在沒有調(diào)用過notify()和notifyAll()的情況下醒來。這就是所謂的假喚醒(spurious wakeups)

.wikipedia的描述.png

我的demo測試,確實存在虛假喚醒,導(dǎo)致結(jié)果不一致,如圖將while自旋換成if判斷后 輸出的count值可能到不了10000,需要多測試幾遍。
我的理解,假如一個線程進入if條件后進行wait()釋放鎖,此時有別的線程在執(zhí)行++count,此時剛好發(fā)生虛喚醒(別問我怎么會發(fā)生,高并發(fā)就是這么巧,自己測試)那么就會執(zhí)行下面的語句,也進行++count,其實相當于兩個線程看到的比如都是77 ++后只變到了78,所以就導(dǎo)致了錯誤的結(jié)果發(fā)生,自旋鎖while怎么避免呢,由于是while循環(huán),即使被虛喚醒,那么該線程的代碼還是得執(zhí)行條件判斷,就又進入了wait狀態(tài)(因為即使發(fā)生虛喚醒事件,條件變量isLocked不可能變成false)所以解決了這個問題。但是自旋鎖是while循環(huán),需要耗費cpu資源的。


修改測試的地方

完整測試代碼

package com.alibaba.otter.canal.common;
import java.util.concurrent.CountDownLatch;

import org.junit.After;
import org.junit.Before;
import org.junit.Test;
@SuppressWarnings("restriction")
public class LockTest extends AbstractZkTest {
    private Object obj = new Object();
    private int count = 0;
    @Before
    public void setUp() {
        
    }

    @After
    public void tearDown() {
    }

    @Test
    public void testUnsafe() {
        CountDownLatch latch=new CountDownLatch(10000);
        for (int i = 0; i < 10000; i++) {
            Worker worker  = new Worker(latch);
            worker.start();
        }
        try {
            latch.await();
        } catch (InterruptedException e) {
            // TODO Auto-generated catch block
            e.printStackTrace();
        }
        
    }
    public int inc(){
        synchronized(this){
            System.out.println(count);
            return ++count;
            
        }
    }

    private Lock lock = new Lock();
    public int lockInc() {
        try {
            lock.lock();
        } catch (InterruptedException e) {
            // TODO Auto-generated catch block
            e.printStackTrace();
        }
        int newCount = ++count;
        System.out.println(count);
        lock.unlock();
        return newCount;
    }
    public class Lock{
        private boolean isLocked = false;

        public synchronized void lock()
            throws InterruptedException{
//          while(isLocked){
            if(isLocked){
                wait();
            }
            isLocked = true;
        }

        public synchronized void unlock(){
            isLocked = false;
            notify();
        }
    }
    class Worker extends Thread {
        private CountDownLatch latch;
        public Worker(CountDownLatch latch) {
            this.latch = latch;
        }
        public void run() {
//          inc();
            lockInc();
            latch.countDown();
        }
    }
}

實現(xiàn)可重入鎖
概念:可重入其實是同步代碼段中 發(fā)現(xiàn)是本線程 則不需要再wait,直接可以執(zhí)行 另一個同步代碼塊。java的synchronized同步是可重入的 例如

public synchronized outer(){
    inner();
}

public synchronized inner(){
    //do something
}

當線程獲得鎖 進入outer同步塊后 需要執(zhí)行inner 另一個同步塊,按理說此時是所有線程都去搶占inner代碼塊的鎖,但是可重入的話 獲得鎖的線程直接可以執(zhí)行inner語句
如果用lock代替synchronized的話一定要注意處理可重入性,避免死鎖。主要就是通過記錄是不是自己獲得了鎖,并且鎖了幾次,釋放鎖的時候?qū)?yīng)的將次數(shù)減少即可。這里附上完整的測試代碼

package com.alibaba.otter.canal.common;
import java.util.concurrent.CountDownLatch;
import java.util.concurrent.TimeUnit;

import org.junit.After;
import org.junit.Before;
import org.junit.Test;
@SuppressWarnings("restriction")
public class LockTest extends AbstractZkTest {
    private Object obj = new Object();
    private int count = 0;
    @Before
    public void setUp() {
        
    }

    @After
    public void tearDown() {
    }

    @Test
    public void testUnsafe() {
        CountDownLatch latch=new CountDownLatch(10000);
        for (int i = 0; i < 10000; i++) {
            Worker worker  = new Worker(latch);
            worker.start();
        }
        try {
            latch.await();
        } catch (InterruptedException e) {
            // TODO Auto-generated catch block
            e.printStackTrace();
        }
        
    }
    public int inc(){
        synchronized(this){
            System.out.println(count);
            return ++count;
            
        }
    }

    private Lock lock = new Lock();
    public int lockInc() {
        int newCount=count;
        try {
            lock.lock();
            newCount = ++count;
            System.out.println(count);
        } catch (InterruptedException e) {
            // TODO Auto-generated catch block
            e.printStackTrace();
        } finally {
            lock.unlock();
        }
        
        return newCount;
    }
    public class Lock{
        private boolean isLocked = false;

        public synchronized void lock()
            throws InterruptedException{
            while(isLocked){
            
//          if(isLocked){
                
                wait();
            }
            isLocked = true;
        }

        public synchronized void unlock(){
            isLocked = false;
            notify();
        }
    }
    class Worker extends Thread {
        private CountDownLatch latch;
        public Worker(CountDownLatch latch) {
            this.latch = latch;
        }
        public void run() {
//          inc();
//          lockInc();
            try {
//              reentrantOuter();//可重入 synchronized方式
//              unReentrantOuter(); //lock未處理是否自己鎖 產(chǎn)生的是不可重入鎖,導(dǎo)致死鎖
                reentrantLockOuter();//lock方式實現(xiàn)可重入鎖
            } catch (Exception e) {
                // TODO Auto-generated catch block
                e.printStackTrace();
            }
            latch.countDown();
        }
    }
    

    /**
     * 
     * 可重入鎖的測試
     */
    @Test
    public void testReentrant() {
        CountDownLatch latch=new CountDownLatch(2);
        for (int i = 0; i < 2; i++) {
            Worker worker  = new Worker(latch);
            worker.start();
        }
        try {
            latch.await();
        } catch (InterruptedException e) {
            // TODO Auto-generated catch block
            e.printStackTrace();
        }
        
    }
    public synchronized void reentrantOuter() throws InterruptedException{
        System.out.println("reentrantOuter1");
        reentrantInner();
    }
    
    public synchronized void reentrantInner() throws InterruptedException{
        Thread.currentThread().sleep(10);
        System.out.println("reentrantInner2");
    }
    
    
    public void unReentrantOuter() throws InterruptedException{
        try {
            lock.lock();
            System.out.println("unReentrantouter1");
            unReentrantInner();
        } catch (InterruptedException e) {
            e.printStackTrace();
        } finally {
            lock.unlock();
        }
    }

    public synchronized void unReentrantInner() {
        try {
            lock.lock();
            System.out.println("unReentrantInner2");
        } catch (InterruptedException e) {
            e.printStackTrace();
        } finally {
            lock.unlock();
        }
    }
    
    public void reentrantLockOuter() {
        try {
            reentrantLock.lock();
            System.out.println("unReentrantouter1");
            reentrantLockInner();
        } catch (InterruptedException e) {
            e.printStackTrace();
        } finally {
            reentrantLock.unlock();
        }
    }

    public synchronized void reentrantLockInner() throws InterruptedException{
        try {   
            reentrantLock.lock();
            System.out.println("unReentrantInner2");
            Thread.currentThread().sleep(10);
        } catch (InterruptedException e) {
            e.printStackTrace();
        } finally {
            reentrantLock.unlock();
        }
    }
    
    ReentrantLock reentrantLock = new ReentrantLock();
    class ReentrantLock{
        boolean isLocked = false;
        Thread  lockedBy = null;
        int lockedCount = 0;

        public synchronized void lock()
            throws InterruptedException{
            Thread callingThread = Thread.currentThread();
            while(isLocked && lockedBy != callingThread){
                wait();
            }
            isLocked = true;
            lockedCount++;
            lockedBy = callingThread;
      }

        public synchronized void unlock(){
            if(Thread.currentThread() ==this.lockedBy){
                lockedCount--;
                if(lockedCount == 0){
                    isLocked = false;
                    notify();
                }
            }
        }
    }
}

上述鎖都是非公平的鎖,即先來的請求不一定是先處理,這樣的話就會導(dǎo)致有的線程可能很久得不到鎖(不要問為什么,并發(fā)大的話就是可能發(fā)生),這樣的話有些問題。我們基于此實現(xiàn)各公平的鎖。主要思路是 來的請求線程放到列表中,然后 notify的時候調(diào)用列表第一個的notify,即通知喚醒先來的請求線程即可。附上完整測試代碼。

package com.alibaba.otter.canal.common;
import java.util.ArrayList;
import java.util.List;
import java.util.concurrent.CountDownLatch;
import java.util.concurrent.TimeUnit;

import org.junit.After;
import org.junit.Before;
import org.junit.Test;
@SuppressWarnings("restriction")
public class FairLockTest extends AbstractZkTest {
    private Object obj = new Object();
    private int count = 0;
    @Before
    public void setUp() {

    }

    @After
    public void tearDown() {
    }

    @Test
    public void testUnsafe() {
        CountDownLatch latch = new CountDownLatch(10000);
        for (int i = 0; i < 10000; i++) {
            Worker worker = new Worker(latch, i);
            worker.start();
        }
        try {
            latch.await();
        } catch (InterruptedException e) {
            e.printStackTrace();
        }

    }
    FairLock fairLock = new FairLock();
    public int fairLockInc() {
        int newCount = count;
        try {
            fairLock.lock();
            newCount = ++count;

        } catch (InterruptedException e) {
            // TODO Auto-generated catch block
            e.printStackTrace();
        } finally {
            fairLock.unlock();
        }

        return newCount;
    }

    class Worker extends Thread {
        private CountDownLatch latch;
        public Worker(CountDownLatch latch, int i) {
            this.latch = latch;
            this.setName(i + " thread");
        }
        public void run() {
            try {
                fairLockInc();
            } catch (Exception e) {
                e.printStackTrace();
            }
            latch.countDown();
        }
    }

    class FairLock {
        private boolean isLocked = false;
        private Thread lockingThread = null;
        private List<QueueObject> waitingThreads = new ArrayList<QueueObject>();

        public void lock() throws InterruptedException {
            QueueObject queueObject = new QueueObject();
            boolean isLockedForThisThread = true;
            synchronized (this) {
                System.out.println(Thread.currentThread().getName() + " in");
                waitingThreads.add(queueObject);
            }

            while (isLockedForThisThread) {
                synchronized (this) {
                    isLockedForThisThread = isLocked || waitingThreads.get(0) != queueObject;
                    if (!isLockedForThisThread) {
                        isLocked = true;
                        System.out.println(Thread.currentThread().getName()
                                + " out");
                        waitingThreads.remove(queueObject);
                        lockingThread = Thread.currentThread();
                        return;
                    }
                }
                try {
                    queueObject.doWait();
                } catch (InterruptedException e) {
                    synchronized (this) {
                        System.out.println(Thread.currentThread().getName()
                                + " out");
                        waitingThreads.remove(queueObject);
                    }
                    throw e;
                }
            }
        }

        public synchronized void unlock() {
            if (this.lockingThread != Thread.currentThread()) {
                throw new IllegalMonitorStateException(
                        "Calling thread has not locked this lock");
            }
            isLocked = false;
            lockingThread = null;
            if (waitingThreads.size() > 0) {
                waitingThreads.get(0).doNotify();
            }
        }
    }
    class QueueObject {

        private boolean isNotified = false;

        public synchronized void doWait() throws InterruptedException {

            while (!isNotified) {
                this.wait();
            }

            this.isNotified = false;
        }

        public synchronized void doNotify() {
            this.isNotified = true;
            this.notify();
        }

        public boolean equals(Object o) {
            return this == o;
        }

    }

}

其實我們基于此還可以實現(xiàn)更多的鎖,可以實現(xiàn)基于優(yōu)先級的鎖,主要實現(xiàn)思路就是創(chuàng)建線程的時候傳入優(yōu)先級參數(shù),然后我們可以在入等待列表的時候?qū)Ρ葌魅氲膬?yōu)先級參數(shù)進行比較大小,找到插入的位置即可,當然方法不止這一個,也可以是通知的時候選取優(yōu)先級最大的通知。我覺得基于此我們可以把juc的所有類都可以實現(xiàn)。
上面都是基于wait/notify/notifyAll來同步的。wait/notify機制有個很蛋疼的地方是,比如線程B要用notify通知線程A,那么線程B要確保線程A已經(jīng)在wait調(diào)用上等待了,否則線程A可能永遠都在等待。編程的時候就會很蛋疼。另外,是調(diào)用notify,還是notifyAll?notify只會喚醒一個線程,如果錯誤地有兩個線程在同一個對象上wait等待,那么又悲劇了。為了安全起見,貌似只能調(diào)用notifyAll了
看一看 java.util.concurrent.locks對wait/notify/notifyAll的代替 怎么實現(xiàn)的各種鎖

Paste_Image.png

這里涉及到一個基礎(chǔ)類 也是基于Unsafe 類實現(xiàn)的。
給出官方api的翻譯版

用來創(chuàng)建鎖和其他同步類的基本線程阻塞原語。
此類以及每個使用它的線程與一個許可關(guān)聯(lián)(從 Semaphore 類的意義上說)。如果該許可可用,并且可在進程中使用,則調(diào)用 park 將立即返回;否則可能 阻塞。如果許可尚不可用,則可以調(diào)用 unpark 使其可用。(但與 Semaphore 不同的是,許可不能累積,并且最多只能有一個許可。)
park 和 unpark 方法提供了阻塞和解除阻塞線程的有效方法,并且不會遇到導(dǎo)致過時方法 Thread.suspend 和 Thread.resume 因為以下目的變得不可用的問題:由于許可的存在,調(diào)用 park 的線程和另一個試圖將其 unpark 的線程之間的競爭將保持活性。此外,如果調(diào)用者線程被中斷,并且支持超時,則 park 將返回。park 方法還可以在其他任何時間“毫無理由”地返回,因此通常必須在重新檢查返回條件的循環(huán)里調(diào)用此方法。從這個意義上說,park 是“忙碌等待”的一種優(yōu)化,它不會浪費這么多的時間進行自旋,但是必須將它與 unpark 配對使用才更高效。
三種形式的 park 還各自支持一個 blocker 對象參數(shù)。此對象在線程受阻塞時被記錄,以允許監(jiān)視工具和診斷工具確定線程受阻塞的原因。(這樣的工具可以使用方法 getBlocker(java.lang.Thread) 訪問 blocker。)建議最好使用這些形式,而不是不帶此參數(shù)的原始形式。在鎖實現(xiàn)中提供的作為 blocker 的普通參數(shù)是 this。
這些方法被設(shè)計用來作為創(chuàng)建高級同步實用工具的工具,對于大多數(shù)并發(fā)控制應(yīng)用程序而言,它們本身并不是很有用。park 方法僅設(shè)計用于以下形式的構(gòu)造:
while (!canProceed()) { ... LockSupport.park(this); }在這里,在調(diào)用 park 之前,canProceed 和其他任何動作都不會鎖定或阻塞。因為每個線程只與一個許可關(guān)聯(lián),park 的任何中間使用都可能干擾其預(yù)期效果。
示例用法。 以下是一個先進先出 (first-in-first-out) 非重入鎖類的框架。

class FIFOMutex {
   private final AtomicBoolean locked = new AtomicBoolean(false);
   private final Queue<Thread> waiters
     = new ConcurrentLinkedQueue<Thread>();
   public void lock() {
     boolean wasInterrupted = false;
     Thread current = Thread.currentThread();
     waiters.add(current);
    // Block while not first in queue or cannot acquire lock
     while (waiters.peek() != current ||
            !locked.compareAndSet(false, true)) {
        LockSupport.park(this);
        if (Thread.interrupted()) // ignore interrupts while waiting
          wasInterrupted = true;
     }
     waiters.remove();
     if (wasInterrupted)          // reassert interrupt status on exit
        current.interrupt();
   }
   public void unlock() {
     locked.set(false);
     LockSupport.unpark(waiters.peek());
   }
 }

這里寫了個測試類,附上源碼(有大概注釋)

package com.alibaba.otter.canal.common;

import java.util.concurrent.locks.LockSupport;

import org.junit.After;
import org.junit.Before;
import org.junit.Test;
public class LockSupportTest extends AbstractZkTest {
    @Before
    public void setUp() {
        
    }

    @After
    public void tearDown() {
    }

    @Test
    public void testLockSupport() {
         LockSupport.park();
         System.out.println("block.");//阻塞在這里證明 默認許可時不可用的
    }
    @Test
    public void testUnpark() {
         Thread thread = Thread.currentThread();
         LockSupport.unpark(thread);//釋放許可
         LockSupport.park();// 獲取許可
         System.out.println("b");//正常執(zhí)行 一對一使用
    }
    
    @Test
    public void testReentrantUnpark() {
        Thread thread = Thread.currentThread();
        
        LockSupport.unpark(thread);
        
        System.out.println("a");
        LockSupport.park();
        System.out.println("b");
        LockSupport.park();
        System.out.println("c");//阻塞在這里 ,說明非可重入的
    }
    @Test
    public void testInterrupt() throws Exception {
        Thread t = new Thread(new Runnable()
        {
            private int count = 0;

            @Override
            public void run()
            {
                long start = System.currentTimeMillis();
                long end = 0;

                while ((end - start) <= 1000)
                {
                    count++;
                    end = System.currentTimeMillis();
                }

                System.out.println("after 1 second.count=" + count);

            //等待或許許可
                LockSupport.park();
                System.out.println("thread over." + Thread.currentThread().isInterrupted());

            }
        });

        t.start();

        Thread.sleep(2000);

        // 中斷線程
        t.interrupt(); //不會拋出InterruptException 不影響主線程

        
        System.out.println("main over");
    }
    
    
    
}

最后編輯于
?著作權(quán)歸作者所有,轉(zhuǎn)載或內(nèi)容合作請聯(lián)系作者
平臺聲明:文章內(nèi)容(如有圖片或視頻亦包括在內(nèi))由作者上傳并發(fā)布,文章內(nèi)容僅代表作者本人觀點,簡書系信息發(fā)布平臺,僅提供信息存儲服務(wù)。
  • 序言:七十年代末,一起剝皮案震驚了整個濱河市,隨后出現(xiàn)的幾起案子,更是在濱河造成了極大的恐慌,老刑警劉巖,帶你破解...
    沈念sama閱讀 228,606評論 6 533
  • 序言:濱河連續(xù)發(fā)生了三起死亡事件,死亡現(xiàn)場離奇詭異,居然都是意外死亡,警方通過查閱死者的電腦和手機,發(fā)現(xiàn)死者居然都...
    沈念sama閱讀 98,582評論 3 418
  • 文/潘曉璐 我一進店門,熙熙樓的掌柜王于貴愁眉苦臉地迎上來,“玉大人,你說我怎么就攤上這事。” “怎么了?”我有些...
    開封第一講書人閱讀 176,540評論 0 376
  • 文/不壞的土叔 我叫張陵,是天一觀的道長。 經(jīng)常有香客問我,道長,這世上最難降的妖魔是什么? 我笑而不...
    開封第一講書人閱讀 63,028評論 1 314
  • 正文 為了忘掉前任,我火速辦了婚禮,結(jié)果婚禮上,老公的妹妹穿的比我還像新娘。我一直安慰自己,他們只是感情好,可當我...
    茶點故事閱讀 71,801評論 6 410
  • 文/花漫 我一把揭開白布。 她就那樣靜靜地躺著,像睡著了一般。 火紅的嫁衣襯著肌膚如雪。 梳的紋絲不亂的頭發(fā)上,一...
    開封第一講書人閱讀 55,223評論 1 324
  • 那天,我揣著相機與錄音,去河邊找鬼。 笑死,一個胖子當著我的面吹牛,可吹牛的內(nèi)容都是我干的。 我是一名探鬼主播,決...
    沈念sama閱讀 43,294評論 3 442
  • 文/蒼蘭香墨 我猛地睜開眼,長吁一口氣:“原來是場噩夢啊……” “哼!你這毒婦竟也來了?” 一聲冷哼從身側(cè)響起,我...
    開封第一講書人閱讀 42,442評論 0 289
  • 序言:老撾萬榮一對情侶失蹤,失蹤者是張志新(化名)和其女友劉穎,沒想到半個月后,有當?shù)厝嗽跇淞掷锇l(fā)現(xiàn)了一具尸體,經(jīng)...
    沈念sama閱讀 48,976評論 1 335
  • 正文 獨居荒郊野嶺守林人離奇死亡,尸身上長有42處帶血的膿包…… 初始之章·張勛 以下內(nèi)容為張勛視角 年9月15日...
    茶點故事閱讀 40,800評論 3 354
  • 正文 我和宋清朗相戀三年,在試婚紗的時候發(fā)現(xiàn)自己被綠了。 大學(xué)時的朋友給我發(fā)了我未婚夫和他白月光在一起吃飯的照片。...
    茶點故事閱讀 42,996評論 1 369
  • 序言:一個原本活蹦亂跳的男人離奇死亡,死狀恐怖,靈堂內(nèi)的尸體忽然破棺而出,到底是詐尸還是另有隱情,我是刑警寧澤,帶...
    沈念sama閱讀 38,543評論 5 360
  • 正文 年R本政府宣布,位于F島的核電站,受9級特大地震影響,放射性物質(zhì)發(fā)生泄漏。R本人自食惡果不足惜,卻給世界環(huán)境...
    茶點故事閱讀 44,233評論 3 347
  • 文/蒙蒙 一、第九天 我趴在偏房一處隱蔽的房頂上張望。 院中可真熱鬧,春花似錦、人聲如沸。這莊子的主人今日做“春日...
    開封第一講書人閱讀 34,662評論 0 26
  • 文/蒼蘭香墨 我抬頭看了看天上的太陽。三九已至,卻和暖如春,著一層夾襖步出監(jiān)牢的瞬間,已是汗流浹背。 一陣腳步聲響...
    開封第一講書人閱讀 35,926評論 1 286
  • 我被黑心中介騙來泰國打工, 沒想到剛下飛機就差點兒被人妖公主榨干…… 1. 我叫王不留,地道東北人。 一個月前我還...
    沈念sama閱讀 51,702評論 3 392
  • 正文 我出身青樓,卻偏偏與公主長得像,于是被迫代替她去往敵國和親。 傳聞我的和親對象是個殘疾皇子,可洞房花燭夜當晚...
    茶點故事閱讀 47,991評論 2 374

推薦閱讀更多精彩內(nèi)容

  • 1.解決信號量丟失和假喚醒 public class MyWaitNotify3{ MonitorObject m...
    Q羅閱讀 890評論 0 1
  • 本文出自 Eddy Wiki ,轉(zhuǎn)載請注明出處:http://eddy.wiki/interview-java.h...
    eddy_wiki閱讀 2,202評論 0 14
  • layout: posttitle: 《Java并發(fā)編程的藝術(shù)》筆記categories: Javaexcerpt...
    xiaogmail閱讀 5,849評論 1 19
  • 人是會懷念的,并非要到特定的老齡或者身體機能漸漸退化了才細想過去,而后被動拉扯些舊日里的光景潤色眼下局限著...
    及客閱讀 215評論 0 2
  • 早秋的天空顏色總是很淡,穹頂?shù)挠羲{淺薄的近乎透明;云彩像斑斑魚鱗又像裸露的河床,分來一點夕陽的秋意,散發(fā)著干凈松爽...
    小野風信子閱讀 475評論 0 2