java 手寫併發框架(二)異步轉同步框架封裝鎖策略

序言

上一節咱們學習了異步查詢轉同步的 7 種實現方式,今天咱們就來學習一下,如何對其進行封裝,使其成爲一個更加便於使用的工具。java

思惟導圖以下:git

異步轉同步

拓展閱讀

java 手寫併發框架(1)異步查詢轉同步的 7 種實現方式github

異步轉同步的便利性

實現方式

  • 循環等待spring

  • wait & notifyapi

  • 使用條件鎖安全

  • 使用 CountDownLatchspringboot

  • 使用 CyclicBarrier併發

  • Future框架

  • Spring EventListener

上一節咱們已經對上面的 7 種實現方式進行了詳細的介紹,沒有看過的同窗能夠去簡單回顧一下。異步

可是這樣我的以爲仍是不夠方便,懶惰是進步的階梯。

更進一步簡化

咱們但願達到下面的效果:

@Sync
public String queryId() {
    System.out.println("開始查詢");
    return id;
}

@SyncCallback(value = "queryId")
public void queryIdCallback() {
    System.out.println("回調函數執行");
    id = "123";
}

經過註解直接須要同步的方法,和回調的方法,代碼中直接調用便可。

咱們首先實現基於字節碼加強的版本,後續將實現整合 spring, springboot 的版本。

鎖的代碼實現

鎖的定義

咱們將原來的實現抽象爲加鎖和解鎖,爲了便於拓展,接口定義以下:

package com.github.houbb.sync.api.api;

/**
 * @author binbin.hou
 * @since 0.0.1
 */
public interface ISyncLock {

    /**
     * 等待策略
     * @param context 上下文
     * @since 0.0.1
     */
    void lock(final ISyncLockContext context);

    /**
     * 解鎖策略
     * @param context 上下文
     * @since 0.0.1
     */
    void unlock(final ISyncUnlockContext context);

}

其中上下文加鎖和解鎖作了區分,不過暫時內容是同樣的。

主要是超時時間和單位:

package com.github.houbb.sync.api.api;

import java.util.concurrent.TimeUnit;

/**
 * @author binbin.hou
 * @since 0.0.1
 */
public interface ISyncLockContext {

    /**
     * 超時時間
     * @return 結果
     */
    long timeout();

    /**
     * 超時時間單位
     * @return 結果
     */
    TimeUnit timeUnit();

}

鎖策略實現

咱們本節主要實現下上一節中的幾種鎖實現。

目前咱們選擇其中的是個進行實現:

wait & notify

package com.github.houbb.sync.core.support.lock;

import com.github.houbb.log.integration.core.Log;
import com.github.houbb.log.integration.core.LogFactory;
import com.github.houbb.sync.api.api.ISyncLock;
import com.github.houbb.sync.api.api.ISyncLockContext;
import com.github.houbb.sync.api.api.ISyncUnlockContext;
import com.github.houbb.sync.api.exception.SyncRuntimeException;

/**
 * 等待通知同步
 *
 * @author binbin.hou
 * @since 0.0.1
 */
public class WaitNotifyLock implements ISyncLock {

    private static final Log log = LogFactory.getLog(WaitNotifyLock.class);

    /**
     * 聲明對象
     */
    private final Object lock = new Object();

    @Override
    public synchronized void lock(ISyncLockContext context) {
        synchronized (lock) {
            try {
                long timeoutMills = context.timeUnit().toMillis(context.timeout());
                log.info("進入等待,超時時間爲:{}ms", timeoutMills);
                lock.wait(timeoutMills);
            } catch (InterruptedException e) {
                log.error("中斷異常", e);
                throw new SyncRuntimeException(e);
            }
        }
    }

    @Override
    public void unlock(ISyncUnlockContext context) {
        synchronized (lock) {
            log.info("喚醒全部等待線程");
            lock.notifyAll();
        }
    }

}

加鎖的部分比較簡單,咱們從上下文中獲取超時時間和超時單位,直接和上一節內容相似,調用便可。

至於上下文中的信息是怎麼來的,咱們後續就會講解。

條件鎖實現

這個在有了上一節的基礎以後也很是簡單。

核心流程:

(1)建立鎖

(2)獲取鎖的 condition

(3)執行加鎖和解鎖

package com.github.houbb.sync.core.support.lock;

import com.github.houbb.log.integration.core.Log;
import com.github.houbb.log.integration.core.LogFactory;
import com.github.houbb.sync.api.api.ISyncLock;
import com.github.houbb.sync.api.api.ISyncLockContext;
import com.github.houbb.sync.api.api.ISyncUnlockContext;

import java.util.concurrent.locks.Condition;
import java.util.concurrent.locks.Lock;
import java.util.concurrent.locks.ReentrantLock;

/**
 * 等待通知同步
 *
 * @author binbin.hou
 * @since 0.0.1
 */
public class LockConditionLock implements ISyncLock {

    private static final Log log = LogFactory.getLog(LockConditionLock.class);

    private final Lock lock = new ReentrantLock();

    private final Condition condition = lock.newCondition();

    @Override
    public synchronized void lock(ISyncLockContext context) {
        lock.lock();
        try{
            log.info("程序進入鎖定狀態");
            condition.await(context.timeout(), context.timeUnit());
        } catch (InterruptedException e) {
            log.error("程序鎖定狀態異常", e);
        } finally {
            lock.unlock();
        }
    }

    @Override
    public void unlock(ISyncUnlockContext context) {
        lock.lock();
        try{
            log.info("解鎖狀態,喚醒全部等待線程。");
            condition.signalAll();
        } finally {
            lock.unlock();
        }
    }

}

CountDownLatch 實現

package com.github.houbb.sync.core.support.lock;

import com.github.houbb.log.integration.core.Log;
import com.github.houbb.log.integration.core.LogFactory;
import com.github.houbb.sync.api.api.ISyncLock;
import com.github.houbb.sync.api.api.ISyncLockContext;
import com.github.houbb.sync.api.api.ISyncUnlockContext;

import java.util.concurrent.CountDownLatch;

/**
 * 等待通知同步
 *
 * @author binbin.hou
 * @since 0.0.1
 */
public class CountDownLatchLock implements ISyncLock {

    private static final Log log = LogFactory.getLog(CountDownLatchLock.class);

    /**
     * 閉鎖
     * 調用1次,後續方法便可通行。
     */
    private CountDownLatch countDownLatch = new CountDownLatch(1);

    @Override
    public synchronized void lock(ISyncLockContext context) {
        countDownLatch = new CountDownLatch(1);

        try {
            log.info("進入等待,超時時間爲:{},超時單位:{}", context.timeout(),
                    context.timeUnit());
            boolean result = countDownLatch.await(context.timeout(), context.timeUnit());
            log.info("等待結果: {}", result);
        } catch (InterruptedException e) {
            log.error("鎖中斷異常", e);
        }
    }

    @Override
    public void unlock(ISyncUnlockContext context) {
        log.info("執行 unlock 操做");
        countDownLatch.countDown();
    }

}

注意:這裏爲了保證 countDownLatch 能夠屢次使用,咱們在每一次加鎖的時候,都會從新建立 CountDownLatch。

CyclicBarrierLock 鎖實現

package com.github.houbb.sync.core.support.lock;

import com.github.houbb.log.integration.core.Log;
import com.github.houbb.log.integration.core.LogFactory;
import com.github.houbb.sync.api.api.ISyncLock;
import com.github.houbb.sync.api.api.ISyncLockContext;
import com.github.houbb.sync.api.api.ISyncUnlockContext;
import com.github.houbb.sync.api.exception.SyncRuntimeException;

import java.util.concurrent.BrokenBarrierException;
import java.util.concurrent.CyclicBarrier;
import java.util.concurrent.TimeoutException;

/**
 * @author binbin.hou
 * @since 0.0.1
 */
public class CyclicBarrierLock implements ISyncLock {

    private static final Log log = LogFactory.getLog(CyclicBarrierLock.class);

    private final CyclicBarrier cyclicBarrier = new CyclicBarrier(2);

    @Override
    public synchronized void lock(ISyncLockContext context) {
        try {
            log.info("進入鎖定狀態, timeout:{}, timeunit: {}",
                    context.timeout(), context.timeUnit());
            cyclicBarrier.await(context.timeout(), context.timeUnit());

            log.info("重置 cyclicBarrier");
            cyclicBarrier.reset();
        } catch (InterruptedException | BrokenBarrierException | TimeoutException e) {
            log.error("鎖定時遇到異常", e);
            throw new SyncRuntimeException(e);
        }
    }

    @Override
    public void unlock(ISyncUnlockContext context) {
        try {
            log.info("解鎖信息");
            cyclicBarrier.await(context.timeout(), context.timeUnit());
        } catch (InterruptedException | TimeoutException | BrokenBarrierException e) {
            log.error("解鎖時遇到異常", e);
        }
    }

}

這裏和 CountDownLatchLock 的實現很是相似,不過 CyclicBarrier 有一個好處,就是能夠複用。

咱們在每一次解鎖以後,重置一下柵欄:

log.info("重置 cyclicBarrier");
cyclicBarrier.reset();

鎖的工具類

爲了簡單的生成上述幾種鎖的實例,咱們提供了一個簡單的工具類方法:

package com.github.houbb.sync.core.support.lock;

import com.github.houbb.heaven.support.instance.impl.Instances;
import com.github.houbb.sync.api.api.ISyncLock;
import com.github.houbb.sync.api.constant.LockType;

import java.util.HashMap;
import java.util.Map;

/**
 * 鎖策略
 * @author binbin.hou
 * @since 0.0.1
 */
public final class Locks {

    private Locks(){}

    /**
     * MAP 信息
     * @since 0.0.1
     */
    private static final Map<LockType, ISyncLock> MAP = new HashMap<>();

    static {
        MAP.put(LockType.WAIT_NOTIFY, waitNotify());
        MAP.put(LockType.COUNT_DOWN_LATCH, countDownLatch());
        MAP.put(LockType.CYCLIC_BARRIER, cyclicBarrier());
        MAP.put(LockType.LOCK_CONDITION, lockCondition());
    }

    /**
     * 獲取鎖實現
     * @param lockType 鎖類型
     * @return 實現
     * @since 0.0.1
     */
    public static ISyncLock getLock(final LockType lockType) {
        return MAP.get(lockType);
    }

    /**
     * @since 0.0.1
     * @return 實現
     */
    private static ISyncLock waitNotify() {
        return Instances.singleton(WaitNotifyLock.class);
    }

    /**
     * @since 0.0.1
     * @return 實現
     */
    private static ISyncLock countDownLatch() {
        return Instances.singleton(CountDownLatchLock.class);
    }

    /**
     * @since 0.0.1
     * @return 實現
     */
    private static ISyncLock lockCondition() {
        return Instances.singleton(LockConditionLock.class);
    }

    /**
     * @since 0.0.1
     * @return 實現
     */
    private static ISyncLock cyclicBarrier() {
        return Instances.singleton(CyclicBarrierLock.class);
    }

}

上述的鎖實現都是線程安全的,因此所有使用單例模式建立。

LockType 類是一個鎖的枚舉類,會在註解中使用。

小結

好了,到這裏咱們就把上一節中的常見的 4 種鎖策略就封裝完成了。

你可能好奇上下文的時間信息哪裏來?這些鎖又是如何被調用的?

咱們將經過註解+字節碼加強的方式來實現調用(就是 aop 的原理),因爲篇幅緣由,字節碼篇幅較長,爲了閱讀體驗,實現部分將放在下一節。

感興趣的能夠關注一下,便於實時接收最新內容。

以爲本文對你有幫助的話,歡迎點贊評論收藏轉發一波。你的鼓勵,是我最大的動力~

不知道你有哪些收穫呢?或者有其餘更多的想法,歡迎留言區和我一塊兒討論,期待與你的思考相遇。

文中若是連接失效,能夠點擊 {閱讀原文}。

深刻學習

相關文章
相關標籤/搜索