設(shè)計(jì)模式 | 中介者模式及典型應(yīng)用

本文的主要內(nèi)容:

  • 介紹中介者模式
  • 數(shù)據(jù)同步示例
  • 中介者模式總結(jié)
  • 源碼分析中介者模式的典型應(yīng)用
    • Java Timer 中的中介者模式

中介者模式

世界上存在著各種各樣的數(shù)據(jù)庫(kù)破讨,不同數(shù)據(jù)庫(kù)有各自的應(yīng)用場(chǎng)景,對(duì)于同一份數(shù)據(jù)烫沙,最開(kāi)始可能使用關(guān)系型數(shù)據(jù)庫(kù)(如MySQL)進(jìn)行存儲(chǔ)查詢,使用Redis作為緩存數(shù)據(jù)庫(kù)煤率,當(dāng)數(shù)據(jù)量較大時(shí)使用MySQL進(jìn)行查詢可能較慢,所以需要將數(shù)據(jù)同步到Elasticsearch或者列式數(shù)據(jù)庫(kù)如Hbase中進(jìn)行大數(shù)據(jù)查詢昼捍。

如何設(shè)計(jì)數(shù)據(jù)同步方案是一個(gè)重要的問(wèn)題妒茬。數(shù)據(jù)源眾多肛循,目標(biāo)端也眾多,設(shè)計(jì)得不好可能 "牽一發(fā)而動(dòng)全身"浩考。

如果我們這樣設(shè)計(jì):每個(gè)數(shù)據(jù)源直接同步數(shù)據(jù)到目標(biāo)端數(shù)據(jù)庫(kù)的搭伤,如果數(shù)據(jù)庫(kù)有 N 個(gè)闷畸,那么最多可能的同步作業(yè)將達(dá)到 N * N 個(gè),當(dāng)修改了其中一個(gè)數(shù)據(jù)庫(kù)的某些配置裁赠,可能需要修改另外的 N - 1 個(gè)數(shù)據(jù)庫(kù)的同步作業(yè)佩捞。

現(xiàn)在介紹另一種方案,DataX 是阿里巴巴集團(tuán)內(nèi)被廣泛使用的離線數(shù)據(jù)同步工具/平臺(tái)帘营,實(shí)現(xiàn)包括 MySQL芬迄、Oracle、SqlServer塞耕、Postgre荷科、HDFS、Hive、ADS蝎毡、HBase、TableStore(OTS)氧枣、MaxCompute(ODPS)沐兵、DRDS 等各種異構(gòu)數(shù)據(jù)源之間高效的數(shù)據(jù)同步功能。

DataX

DataX 其實(shí)相當(dāng)于一個(gè)中介便监,從數(shù)據(jù)源讀取數(shù)據(jù)扎谎,寫(xiě)入到目標(biāo)端,數(shù)據(jù)源不再需要維護(hù)到目標(biāo)端的同步作業(yè)烧董,只需要與 DataX 通信即可毁靶。DataX 體現(xiàn)了中介者模式的思想拐叉。

中介者模式(Mediator Pattern):用一個(gè)中介對(duì)象(中介者)來(lái)封裝一系列的對(duì)象交互,中介者使各對(duì)象不需要顯式地相互引用,從而使其耦合松散,而且可以獨(dú)立地改變它們之間的交互岳服。中介者模式又稱為調(diào)停者模式拖吼,它是一種對(duì)象行為型模式。

角色

Mediator(抽象中介者):它定義一個(gè)接口,該接口用于與各同事對(duì)象之間進(jìn)行通信。

ConcreteMediator(具體中介者):它是抽象中介者的子類,通過(guò)協(xié)調(diào)各個(gè)同事對(duì)象來(lái)實(shí)現(xiàn)協(xié)作行為,它維持了對(duì)各個(gè)同事對(duì)象的引用笆檀。

Colleague(抽象同事類):它定義各個(gè)同事類公有的方法樱衷,并聲明了一些抽象方法來(lái)供子類實(shí)現(xiàn),同時(shí)它維持了一個(gè)對(duì)抽象中介者類的引用涣达,其子類可以通過(guò)該引用來(lái)與中介者通信鸦概。

ConcreteColleague(具體同事類):它是抽象同事類的子類摄狱;每一個(gè)同事對(duì)象在需要和其他同事對(duì)象通信時(shí)踊跟,先與中介者通信,通過(guò)中介者來(lái)間接完成與其他同事類的通信沈矿;在具體同事類中實(shí)現(xiàn)了在抽象同事類中聲明的抽象方法醒颖。

中介者模式的核心在于中介者類的引入,在中介者模式中侵俗,中介者類承擔(dān)了兩方面的職責(zé)

  • 中轉(zhuǎn)作用(結(jié)構(gòu)性):通過(guò)中介者提供的中轉(zhuǎn)作用澄耍,各個(gè)同事對(duì)象就不再需要顯式引用其他同事,當(dāng)需要和其他同事進(jìn)行通信時(shí),可通過(guò)中介者來(lái)實(shí)現(xiàn)間接調(diào)用。該中轉(zhuǎn)作用屬于中介者在結(jié)構(gòu)上的支持纽门。
  • 協(xié)調(diào)作用(行為性):中介者可以更進(jìn)一步的對(duì)同事之間的關(guān)系進(jìn)行封裝败玉,同事可以一致的和中介者進(jìn)行交互沦补,而不需要指明中介者需要具體怎么做具壮,中介者根據(jù)封裝在自身內(nèi)部的協(xié)調(diào)邏輯,對(duì)同事的請(qǐng)求進(jìn)行進(jìn)一步處理,將同事成員之間的關(guān)系行為進(jìn)行分離和封裝溜腐。

示例

我們來(lái)實(shí)現(xiàn)一個(gè)簡(jiǎn)化版的數(shù)據(jù)同步方案,有三種數(shù)據(jù)庫(kù) Mysql典蝌、Redis截驮、Elasticsearch,其中的 Mysql 作為主數(shù)據(jù)庫(kù),當(dāng)增加一條數(shù)據(jù)時(shí)需要同步到另外兩個(gè)數(shù)據(jù)庫(kù)中哲鸳;Redis 作為緩存數(shù)據(jù)庫(kù)逼龟,當(dāng)增加一條數(shù)據(jù)時(shí)不需要同步到另外另個(gè)數(shù)據(jù)庫(kù)评凝;而 Elasticsearch 作為大數(shù)據(jù)查詢數(shù)據(jù)庫(kù),有一個(gè)統(tǒng)計(jì)功能审轮,當(dāng)增加一條數(shù)據(jù)時(shí)只需要同步到 Mysql肥哎,所以它們之間的關(guān)系圖如下所示。

簡(jiǎn)化的數(shù)據(jù)同步需求

首先我們來(lái)實(shí)現(xiàn)第一種不使用中介者模式的數(shù)據(jù)同步方案疾渣,各數(shù)據(jù)源維護(hù)各自的同步作業(yè)。

抽象數(shù)據(jù)庫(kù)

public abstract class AbstractDatabase {
    public abstract void add(String data);

    public abstract void addData(String data);
}

具體數(shù)據(jù)庫(kù) Mysql崖飘,維護(hù)同步到 Redis和Elasticsearch 的同步作業(yè)

public class MysqlDatabase extends AbstractDatabase {
    private List<String> dataset = new ArrayList<String>();
    @Setter
    private RedisDatabase redisDatabase;
    @Setter
    private EsDatabase esDatabase;

    @Override
    public void addData(String data) {
        System.out.println("Mysql 添加數(shù)據(jù):" + data);
        this.dataset.add(data);
    }

    @Override
    public void add(String data) {
        addData(data);
        this.redisDatabase.addData(data);   // 維護(hù)同步到Redis的同步作業(yè)
        this.esDatabase.addData(data);  // 維護(hù)同步到Elasticsearch的同步作業(yè)
    }

    public void select() {
        System.out.println("- Mysql 查詢榴捡,數(shù)據(jù):" + this.dataset.toString());
    }
}

具體數(shù)據(jù)庫(kù) Redis,不需要同步到其它數(shù)據(jù)庫(kù)

public class RedisDatabase extends AbstractDatabase {
    private List<String> dataset = new LinkedList<String>();

    @Override
    public void addData(String data) {
        System.out.println("Redis 添加數(shù)據(jù):" + data);
        this.dataset.add(data);
    }

    @Override
    public void add(String data) {
        addData(data); // 不同步到其它數(shù)據(jù)庫(kù)
    }

    public void cache() {
        System.out.println("- Redis 緩存的數(shù)據(jù):" + this.dataset.toString());
    }
}

Elasticsearch 朱浴,只需要同步到Mysql

public class EsDatabase extends AbstractDatabase {
    private List<String> dataset = new CopyOnWriteArrayList<String>();
    @Setter
    private MysqlDatabase mysqlDatabase;
    @Override
    public void addData(String data) {
        System.out.println("ES 添加數(shù)據(jù):" + data);
        this.dataset.add(data);
    }

    @Override
    public void add(String data) {
        addData(data);
        this.mysqlDatabase.addData(data);   // 維護(hù)同步到MySQL的同步作業(yè)
    }

    public void count() {
        int count = this.dataset.size();
        System.out.println("- Elasticsearch 統(tǒng)計(jì)吊圾,目前有 " + count + " 條數(shù)據(jù),數(shù)據(jù):" + this.dataset.toString());
    }
}

測(cè)試客戶端翰蠢,分別往三個(gè)數(shù)據(jù)庫(kù)中加入一些數(shù)據(jù)查看同步效果

public class Client {
    public static void main(String[] args) {
        MysqlDatabase mysqlDatabase = new MysqlDatabase();
        RedisDatabase redisDatabase = new RedisDatabase();
        EsDatabase esDatabase = new EsDatabase();

        mysqlDatabase.setRedisDatabase(redisDatabase);
        mysqlDatabase.setEsDatabase(esDatabase);
        esDatabase.setMysqlDatabase(mysqlDatabase);

        System.out.println("\n---------mysql 添加數(shù)據(jù) 1项乒,將同步到Redis和ES中-----------");
        mysqlDatabase.add("1");

        mysqlDatabase.select();
        redisDatabase.cache();
        esDatabase.count();

        System.out.println("\n---------Redis添加數(shù)據(jù) 2,將不同步到其它數(shù)據(jù)庫(kù)-----------");
        redisDatabase.add("2");

        mysqlDatabase.select();
        redisDatabase.cache();
        esDatabase.count();

        System.out.println("\n---------ES 添加數(shù)據(jù) 3梁沧,只同步到 Mysql-----------");
        esDatabase.add("3");

        mysqlDatabase.select();
        redisDatabase.cache();
        esDatabase.count();
    }
}

輸出結(jié)果

---------mysql 添加數(shù)據(jù) 1檀何,將同步到Redis和ES中-----------
Mysql 添加數(shù)據(jù):1
Redis 添加數(shù)據(jù):1
ES 添加數(shù)據(jù):1
- Mysql 查詢,數(shù)據(jù):[1]
- Redis 緩存的數(shù)據(jù):[1]
- Elasticsearch 統(tǒng)計(jì)廷支,目前有 1 條數(shù)據(jù)频鉴,數(shù)據(jù):[1]

---------Redis添加數(shù)據(jù) 2,將不同步到其它數(shù)據(jù)庫(kù)-----------
Redis 添加數(shù)據(jù):2
- Mysql 查詢恋拍,數(shù)據(jù):[1]
- Redis 緩存的數(shù)據(jù):[1, 2]
- Elasticsearch 統(tǒng)計(jì)垛孔,目前有 1 條數(shù)據(jù),數(shù)據(jù):[1]

---------ES 添加數(shù)據(jù) 3施敢,只同步到 Mysql-----------
ES 添加數(shù)據(jù):3
Mysql 添加數(shù)據(jù):3
- Mysql 查詢周荐,數(shù)據(jù):[1, 3]
- Redis 緩存的數(shù)據(jù):[1, 2]
- Elasticsearch 統(tǒng)計(jì),目前有 2 條數(shù)據(jù)僵娃,數(shù)據(jù):[1, 3]

其實(shí)這樣已經(jīng)實(shí)現(xiàn)了我們的需求概作,但是存在一些問(wèn)題

  • 系統(tǒng)結(jié)構(gòu)復(fù)雜且耦合度高。數(shù)據(jù)源需要維護(hù)目標(biāo)端數(shù)據(jù)庫(kù)的引用悯许,以便完成數(shù)據(jù)同步
  • 組件的可重用性差仆嗦。由于每一個(gè)數(shù)據(jù)源和目標(biāo)端之間具有很強(qiáng)的關(guān)聯(lián),若沒(méi)有目標(biāo)端的支持先壕,這個(gè)組件很難被另一個(gè)系統(tǒng)或模塊重用
  • 系統(tǒng)的可擴(kuò)展性差:如果需要增加瘩扼、修改或刪除其中一個(gè)數(shù)據(jù)庫(kù)谆甜、將導(dǎo)致多個(gè)類的源代碼需要修改,這違反了 "開(kāi)閉原則"集绰,可擴(kuò)展性和靈活性欠佳规辱。

我們使用中介者模式來(lái)重構(gòu),將數(shù)據(jù)同步的功能遷移到中介者中栽燕,由中介者來(lái)管理數(shù)據(jù)同步作業(yè)

首先還是抽象數(shù)據(jù)庫(kù)類(抽象同事類)罕袋,維護(hù)了一個(gè)中介者

public abstract class AbstractDatabase {
    public static final String MYSQL = "mysql";
    public static final String REDIS = "redis";
    public static final String ELASTICSEARCH = "elasticsearch";

    protected AbstractMediator mediator;    // 中介者

    public AbstractDatabase(AbstractMediator mediator) {
        this.mediator = mediator;
    }

    public abstract void addData(String data);

    public abstract void add(String data);
}

Mysql 數(shù)據(jù)庫(kù)(具體同事類)

public class MysqlDatabase extends AbstractDatabase {
    private List<String> dataset = new ArrayList<String>();

    public MysqlDatabase(AbstractMediator mediator) {
        super(mediator);
    }

    @Override
    public void addData(String data) {
        System.out.println("Mysql 添加數(shù)據(jù):" + data);
        this.dataset.add(data);
    }

    @Override
    public void add(String data) {
        addData(data);
        this.mediator.sync(AbstractDatabase.MYSQL, data); // 數(shù)據(jù)同步作業(yè)交給中介者管理
    }

    public void select() {
        System.out.println("Mysql 查詢,數(shù)據(jù):" + this.dataset.toString());
    }
}

Redis 數(shù)據(jù)庫(kù)(具體同事類)

public class RedisDatabase extends AbstractDatabase {
    private List<String> dataset = new LinkedList<String>();

    public RedisDatabase(AbstractMediator mediator) {
        super(mediator);
    }

    @Override
    public void addData(String data) {
        System.out.println("Redis 添加數(shù)據(jù):" + data);
        this.dataset.add(data);
    }

    @Override
    public void add(String data) {
        addData(data);
        this.mediator.sync(AbstractDatabase.REDIS, data);    // 數(shù)據(jù)同步作業(yè)交給中介者管理
    }

    public void cache() {
        System.out.println("Redis 緩存的數(shù)據(jù):" + this.dataset.toString());
    }
}

Elasticsearch(具體同事類)

public class EsDatabase extends AbstractDatabase {
    private List<String> dataset = new CopyOnWriteArrayList<String>();

    public EsDatabase(AbstractMediator mediator) {
        super(mediator);
    }

    @Override
    public void addData(String data) {
        System.out.println("ES 添加數(shù)據(jù):" + data);
        this.dataset.add(data);
    }

    @Override
    public void add(String data) {
        addData(data);
        this.mediator.sync(AbstractDatabase.ELASTICSEARCH, data);    // 數(shù)據(jù)同步作業(yè)交給中介者管理
    }

    public void count() {
        int count = this.dataset.size();
        System.out.println("Elasticsearch 統(tǒng)計(jì)碍岔,目前有 " + count + " 條數(shù)據(jù)浴讯,數(shù)據(jù):" + this.dataset.toString());
    }
}

抽象中介者

@Data
public abstract class AbstractMediator {
    protected MysqlDatabase mysqlDatabase;
    protected RedisDatabase redisDatabase;
    protected EsDatabase esDatabase;

    public abstract void sync(String databaseName, String data);
}

具體中介者

public class SyncMediator extends AbstractMediator {
    @Override
    public void sync(String databaseName, String data) {
        if (AbstractDatabase.MYSQL.equals(databaseName)) {
            // mysql 同步到 redis 和 Elasticsearch
            this.redisDatabase.addData(data);
            this.esDatabase.addData(data);
        } else if (AbstractDatabase.REDIS.equals(databaseName)) {
            // redis 緩存同步,不需要同步到其他數(shù)據(jù)庫(kù)
        } else if (AbstractDatabase.ELASTICSEARCH.equals(databaseName)) {
            // Elasticsearch 同步到 Mysql
            this.mysqlDatabase.addData(data);
        }
    }
}

測(cè)試客戶端

public class Client {
    public static void main(String[] args) {
        AbstractMediator syncMediator = new SyncMediator();
        MysqlDatabase mysqlDatabase = new MysqlDatabase(syncMediator);
        RedisDatabase redisDatabase = new RedisDatabase(syncMediator);
        EsDatabase esDatabase = new EsDatabase(syncMediator);

        syncMediator.setMysqlDatabase(mysqlDatabase);
        syncMediator.setRedisDatabase(redisDatabase);
        syncMediator.setEsDatabase(esDatabase);

        System.out.println("\n---------mysql 添加數(shù)據(jù) 1蔼啦,將同步到Redis和ES中-----------");
        mysqlDatabase.add("1");
        mysqlDatabase.select();
        redisDatabase.cache();
        esDatabase.count();

        System.out.println("\n---------Redis添加數(shù)據(jù) 2榆纽,將不同步到其它數(shù)據(jù)庫(kù)-----------");
        redisDatabase.add("2");
        mysqlDatabase.select();
        redisDatabase.cache();
        esDatabase.count();

        System.out.println("\n---------ES 添加數(shù)據(jù) 3,只同步到 Mysql-----------");
        esDatabase.add("3");
        mysqlDatabase.select();
        redisDatabase.cache();
        esDatabase.count();
    }
}

輸出結(jié)果捏肢,與預(yù)期一致

---------mysql 添加數(shù)據(jù) 1奈籽,將同步到Redis和ES中-----------
Mysql 添加數(shù)據(jù):1
Redis 添加數(shù)據(jù):1
ES 添加數(shù)據(jù):1
- Mysql 查詢,數(shù)據(jù):[1]
- Redis 緩存的數(shù)據(jù):[1]
- Elasticsearch 統(tǒng)計(jì)鸵赫,目前有 1 條數(shù)據(jù)衣屏,數(shù)據(jù):[1]

---------Redis添加數(shù)據(jù) 2,將不同步到其它數(shù)據(jù)庫(kù)-----------
Redis 添加數(shù)據(jù):2
- Mysql 查詢辩棒,數(shù)據(jù):[1]
- Redis 緩存的數(shù)據(jù):[1, 2]
- Elasticsearch 統(tǒng)計(jì)狼忱,目前有 1 條數(shù)據(jù),數(shù)據(jù):[1]

---------ES 添加數(shù)據(jù) 3盗温,只同步到 Mysql-----------
ES 添加數(shù)據(jù):3
Mysql 添加數(shù)據(jù):3
- Mysql 查詢藕赞,數(shù)據(jù):[1, 3]
- Redis 緩存的數(shù)據(jù):[1, 2]
- Elasticsearch 統(tǒng)計(jì),目前有 2 條數(shù)據(jù)卖局,數(shù)據(jù):[1, 3]

畫(huà)出類圖如下

示例.中介者模式

中介者模式總結(jié)

中介者模式的主要優(yōu)點(diǎn)

  • 中介者模式簡(jiǎn)化了對(duì)象之間的交互斧蜕,它用中介者和同事的一對(duì)多交互代替了原來(lái)同事之間的多對(duì)多交互,一對(duì)多關(guān)系更容易理解砚偶、維護(hù)和擴(kuò)展批销,將原本難以理解的網(wǎng)狀結(jié)構(gòu)轉(zhuǎn)換成相對(duì)簡(jiǎn)單的星型結(jié)構(gòu)。

  • 中介者模式可將各同事對(duì)象解耦染坯。中介者有利于各同事之間的松耦合均芽,我們可以獨(dú)立的改變和復(fù)用每一個(gè)同事和中介者,增加新的中介者和新的同事類都比較方便单鹿,更好地符合 "開(kāi)閉原則"掀宋。

  • 可以減少子類生成,中介者將原本分布于多個(gè)對(duì)象間的行為集中在一起,改變這些行為只需生成新的中介者子類即可劲妙,這使各個(gè)同事類可被重用湃鹊,無(wú)須對(duì)同事類進(jìn)行擴(kuò)展。

中介者模式的主要缺點(diǎn)

  • 具體中介者類中包含了大量同事之間的交互細(xì)節(jié)镣奋,可能會(huì)導(dǎo)致具體中介者類非常復(fù)雜币呵,使得系統(tǒng)難以維護(hù)。(也就是把具體同事類之間的交互復(fù)雜性集中到了中介者類中侨颈,結(jié)果中介者成了最復(fù)雜的類)

適用場(chǎng)景

  • 系統(tǒng)中對(duì)象之間存在復(fù)雜的引用關(guān)系余赢,系統(tǒng)結(jié)構(gòu)混亂且難以理解。

  • 一個(gè)對(duì)象由于引用了其他很多對(duì)象并且直接和這些對(duì)象通信哈垢,導(dǎo)致難以復(fù)用該對(duì)象妻柒。

  • 想通過(guò)一個(gè)中間類來(lái)封裝多個(gè)類中的行為,而又不想生成太多的子類温赔「蛏荩可以通過(guò)引入中介者類來(lái)實(shí)現(xiàn),在中介者中定義對(duì)象交互的公共行為陶贼,如果需要改變行為則可以增加新的具體中介者類。

中介者模式的典型應(yīng)用

Java Timer 中的中介者模式

敲一個(gè) java.util.Timer 的Demo

兩個(gè)任務(wù)類

public class MyOneTask extends TimerTask {
    private static int num = 0;
    @Override
    public void run() {
        System.out.println("I'm MyOneTask " + ++num);
    }
}

public class MyTwoTask extends TimerTask {
    private static int num = 1000;
    @Override
    public void run() {
        System.out.println("I'm MyTwoTask " + num--);
    }
}

客戶端測(cè)試待秃,3秒后開(kāi)始執(zhí)行拜秧,循環(huán)周期為 1秒

public class TimerTest {
    public static void main(String[] args) {
        // 注意:多線程并行處理定時(shí)任務(wù)時(shí),Timer運(yùn)行多個(gè)TimeTask時(shí)章郁,只要其中之一沒(méi)有捕獲拋出的異常枉氮,
        // 其它任務(wù)便會(huì)自動(dòng)終止運(yùn)行,使用ScheduledExecutorService則沒(méi)有這個(gè)問(wèn)題
        Timer timer = new Timer();
        timer.schedule(new MyOneTask(), 3000, 1000); // 3秒后開(kāi)始運(yùn)行暖庄,循環(huán)周期為 1秒
        timer.schedule(new MyTwoTask(), 3000, 1000);
    }
}

輸出

I'm MyOneTask 1
I'm MyTwoTask 1000
I'm MyTwoTask 999
I'm MyOneTask 2
I'm MyOneTask 3
I'm MyTwoTask 998
I'm MyTwoTask 997
I'm MyOneTask 4
I'm MyOneTask 5
I'm MyTwoTask 996
I'm MyTwoTask 995
I'm MyOneTask 6
...

Timer 的部分關(guān)鍵源碼如下

public class Timer {

    private final TaskQueue queue = new TaskQueue();
    private final TimerThread thread = new TimerThread(queue);
    
    public void schedule(TimerTask task, long delay) {
        if (delay < 0)
            throw new IllegalArgumentException("Negative delay.");
        sched(task, System.currentTimeMillis()+delay, 0);
    }
    
    public void schedule(TimerTask task, Date time) {
        sched(task, time.getTime(), 0);
    }
    
    private void sched(TimerTask task, long time, long period) {
        if (time < 0)
            throw new IllegalArgumentException("Illegal execution time.");

        if (Math.abs(period) > (Long.MAX_VALUE >> 1))
            period >>= 1;

        // 獲取任務(wù)隊(duì)列的鎖(同一個(gè)線程多次獲取這個(gè)鎖并不會(huì)被阻塞,不同線程獲取時(shí)才可能被阻塞)
        synchronized(queue) {
            // 如果定時(shí)調(diào)度線程已經(jīng)終止了,則拋出異常結(jié)束
            if (!thread.newTasksMayBeScheduled)
                throw new IllegalStateException("Timer already cancelled.");

            // 再獲取定時(shí)任務(wù)對(duì)象的鎖(為什么還要再加這個(gè)鎖呢?想不清)
            synchronized(task.lock) {
                // 判斷線程的狀態(tài),防止多線程同時(shí)調(diào)度到一個(gè)任務(wù)時(shí)多次被加入任務(wù)隊(duì)列
                if (task.state != TimerTask.VIRGIN)
                    throw new IllegalStateException(
                        "Task already scheduled or cancelled");
                // 初始化定時(shí)任務(wù)的下次執(zhí)行時(shí)間
                task.nextExecutionTime = time;
                // 重復(fù)執(zhí)行的間隔時(shí)間
                task.period = period;
                // 將定時(shí)任務(wù)的狀態(tài)由TimerTask.VIRGIN(一個(gè)定時(shí)任務(wù)的初始化狀態(tài))設(shè)置為T(mén)imerTask.SCHEDULED
                task.state = TimerTask.SCHEDULED;
            }
            
            // 將任務(wù)加入任務(wù)隊(duì)列
            queue.add(task);
            // 如果當(dāng)前加入的任務(wù)是需要第一個(gè)被執(zhí)行的(也就是他的下一次執(zhí)行時(shí)間離現(xiàn)在最近)
            // 則喚醒等待queue的線程(對(duì)應(yīng)到上面提到的queue.wait())
            if (queue.getMin() == task)
                queue.notify();
        }
    }
    
    // cancel會(huì)等到所有定時(shí)任務(wù)執(zhí)行完后立刻終止定時(shí)線程
    public void cancel() {
        synchronized(queue) {
            thread.newTasksMayBeScheduled = false;
            queue.clear();
            queue.notify();  // In case queue was already empty.
        }
    }
    // ...
}

Timer 中在 schedulexxx 方法中通過(guò) TaskQueue 協(xié)調(diào)各種 TimerTask 定時(shí)任務(wù)聊替,Timer 是中介者,TimerTask 是抽象同事類培廓,而我們自己寫(xiě)的任務(wù)則是具體同事類

TimerThreadTimer 中定時(shí)調(diào)度線程類的定義惹悄,這個(gè)類會(huì)做為一個(gè)線程一直運(yùn)行來(lái)執(zhí)行 Timer 中任務(wù)隊(duì)列中的任務(wù)。

Timer 這個(gè)中介者的功能就是定時(shí)調(diào)度我們寫(xiě)的各種任務(wù)肩钠,將任務(wù)添加到 TaskQueue 任務(wù)隊(duì)列中泣港,給 TimerThread 執(zhí)行,讓任務(wù)與執(zhí)行線程解耦

其他的中介者模式應(yīng)用

  • java.util.concurrent.Executor#executejava.util.concurrent.ExecutorService#submitTimer#schedule 類似

  • MVC模式中价匠,Controller 是中介者当纱,根據(jù) View 層的請(qǐng)求來(lái)操作 Model 層

參考:
劉偉:設(shè)計(jì)模式Java版
慕課網(wǎng)java設(shè)計(jì)模式精講 Debug 方式+內(nèi)存分析
java.util系列源碼解讀之Timer定時(shí)器

后記

歡迎評(píng)論、轉(zhuǎn)發(fā)踩窖、分享坡氯,您的支持是我最大的動(dòng)力

更多內(nèi)容可訪問(wèn)我的個(gè)人博客:http://laijianfeng.org

關(guān)注【小旋鋒】微信公眾號(hào),及時(shí)接收博文推送

長(zhǎng)按關(guān)注【小旋鋒】微信公眾號(hào)

推薦閱讀

設(shè)計(jì)模式 | 享元模式及典型應(yīng)用
設(shè)計(jì)模式 | 組合模式及典型應(yīng)用
設(shè)計(jì)模式 | 模板方法模式及典型應(yīng)用
設(shè)計(jì)模式 | 迭代器模式及典型應(yīng)用
設(shè)計(jì)模式 | 策略模式及典型應(yīng)用
設(shè)計(jì)模式 | 觀察者模式及典型應(yīng)用
設(shè)計(jì)模式 | 備忘錄模式及典型應(yīng)用

?著作權(quán)歸作者所有,轉(zhuǎn)載或內(nèi)容合作請(qǐng)聯(lián)系作者
  • 序言:七十年代末,一起剝皮案震驚了整個(gè)濱河市箫柳,隨后出現(xiàn)的幾起案子手形,更是在濱河造成了極大的恐慌,老刑警劉巖滞时,帶你破解...
    沈念sama閱讀 207,113評(píng)論 6 481
  • 序言:濱河連續(xù)發(fā)生了三起死亡事件叁幢,死亡現(xiàn)場(chǎng)離奇詭異,居然都是意外死亡坪稽,警方通過(guò)查閱死者的電腦和手機(jī)曼玩,發(fā)現(xiàn)死者居然都...
    沈念sama閱讀 88,644評(píng)論 2 381
  • 文/潘曉璐 我一進(jìn)店門(mén),熙熙樓的掌柜王于貴愁眉苦臉地迎上來(lái)窒百,“玉大人黍判,你說(shuō)我怎么就攤上這事「萆遥” “怎么了顷帖?”我有些...
    開(kāi)封第一講書(shū)人閱讀 153,340評(píng)論 0 344
  • 文/不壞的土叔 我叫張陵,是天一觀的道長(zhǎng)渤滞。 經(jīng)常有香客問(wèn)我贬墩,道長(zhǎng),這世上最難降的妖魔是什么妄呕? 我笑而不...
    開(kāi)封第一講書(shū)人閱讀 55,449評(píng)論 1 279
  • 正文 為了忘掉前任陶舞,我火速辦了婚禮,結(jié)果婚禮上绪励,老公的妹妹穿的比我還像新娘肿孵。我一直安慰自己,他們只是感情好疏魏,可當(dāng)我...
    茶點(diǎn)故事閱讀 64,445評(píng)論 5 374
  • 文/花漫 我一把揭開(kāi)白布停做。 她就那樣靜靜地躺著,像睡著了一般大莫。 火紅的嫁衣襯著肌膚如雪蛉腌。 梳的紋絲不亂的頭發(fā)上葵硕,一...
    開(kāi)封第一講書(shū)人閱讀 49,166評(píng)論 1 284
  • 那天眉抬,我揣著相機(jī)與錄音,去河邊找鬼懈凹。 笑死蜀变,一個(gè)胖子當(dāng)著我的面吹牛爬舰,可吹牛的內(nèi)容都是我干的。 我是一名探鬼主播情屹,決...
    沈念sama閱讀 38,442評(píng)論 3 401
  • 文/蒼蘭香墨 我猛地睜開(kāi)眼喂很,長(zhǎng)吁一口氣:“原來(lái)是場(chǎng)噩夢(mèng)啊……” “哼惜颇!你這毒婦竟也來(lái)了?” 一聲冷哼從身側(cè)響起少辣,我...
    開(kāi)封第一講書(shū)人閱讀 37,105評(píng)論 0 261
  • 序言:老撾萬(wàn)榮一對(duì)情侶失蹤凌摄,失蹤者是張志新(化名)和其女友劉穎,沒(méi)想到半個(gè)月后漓帅,有當(dāng)?shù)厝嗽跇?shù)林里發(fā)現(xiàn)了一具尸體锨亏,經(jīng)...
    沈念sama閱讀 43,601評(píng)論 1 300
  • 正文 獨(dú)居荒郊野嶺守林人離奇死亡,尸身上長(zhǎng)有42處帶血的膿包…… 初始之章·張勛 以下內(nèi)容為張勛視角 年9月15日...
    茶點(diǎn)故事閱讀 36,066評(píng)論 2 325
  • 正文 我和宋清朗相戀三年忙干,在試婚紗的時(shí)候發(fā)現(xiàn)自己被綠了器予。 大學(xué)時(shí)的朋友給我發(fā)了我未婚夫和他白月光在一起吃飯的照片。...
    茶點(diǎn)故事閱讀 38,161評(píng)論 1 334
  • 序言:一個(gè)原本活蹦亂跳的男人離奇死亡捐迫,死狀恐怖劣摇,靈堂內(nèi)的尸體忽然破棺而出,到底是詐尸還是另有隱情弓乙,我是刑警寧澤,帶...
    沈念sama閱讀 33,792評(píng)論 4 323
  • 正文 年R本政府宣布钧惧,位于F島的核電站暇韧,受9級(jí)特大地震影響,放射性物質(zhì)發(fā)生泄漏浓瞪。R本人自食惡果不足惜懈玻,卻給世界環(huán)境...
    茶點(diǎn)故事閱讀 39,351評(píng)論 3 307
  • 文/蒙蒙 一、第九天 我趴在偏房一處隱蔽的房頂上張望乾颁。 院中可真熱鬧涂乌,春花似錦、人聲如沸英岭。這莊子的主人今日做“春日...
    開(kāi)封第一講書(shū)人閱讀 30,352評(píng)論 0 19
  • 文/蒼蘭香墨 我抬頭看了看天上的太陽(yáng)诅妹。三九已至罚勾,卻和暖如春毅人,著一層夾襖步出監(jiān)牢的瞬間,已是汗流浹背尖殃。 一陣腳步聲響...
    開(kāi)封第一講書(shū)人閱讀 31,584評(píng)論 1 261
  • 我被黑心中介騙來(lái)泰國(guó)打工丈莺, 沒(méi)想到剛下飛機(jī)就差點(diǎn)兒被人妖公主榨干…… 1. 我叫王不留,地道東北人送丰。 一個(gè)月前我還...
    沈念sama閱讀 45,618評(píng)論 2 355
  • 正文 我出身青樓缔俄,卻偏偏與公主長(zhǎng)得像,于是被迫代替她去往敵國(guó)和親器躏。 傳聞我的和親對(duì)象是個(gè)殘疾皇子俐载,可洞房花燭夜當(dāng)晚...
    茶點(diǎn)故事閱讀 42,916評(píng)論 2 344

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

  • 關(guān)于Mongodb的全面總結(jié) MongoDB的內(nèi)部構(gòu)造《MongoDB The Definitive Guide》...
    中v中閱讀 31,898評(píng)論 2 89
  • 1 場(chǎng)景問(wèn)題# 1.1 如果沒(méi)有主板## 大家都知道,電腦里面各個(gè)配件之間的交互邀桑,主要是通過(guò)主板來(lái)完成的(事實(shí)上主...
    七寸知架構(gòu)閱讀 2,162評(píng)論 0 56
  • 設(shè)計(jì)模式匯總 一瞎疼、基礎(chǔ)知識(shí) 1. 設(shè)計(jì)模式概述 定義:設(shè)計(jì)模式(Design Pattern)是一套被反復(fù)使用、多...
    MinoyJet閱讀 3,906評(píng)論 1 15
  • 設(shè)計(jì)模式基本原則 開(kāi)放-封閉原則(OCP)壁畸,是說(shuō)軟件實(shí)體(類贼急、模塊、函數(shù)等等)應(yīng)該可以拓展捏萍,但是不可修改太抓。開(kāi)-閉原...
    西山薄涼閱讀 3,753評(píng)論 3 13
  • 蠢事指愚蠢、 荒唐事令杈、 愚笨的事走敌,也指在無(wú)意識(shí)的情況下做出了不感到滿意的事和太過(guò)相信自己的謊言,讓自己感到不安逗噩。 ...
    賴小木閱讀 427評(píng)論 6 1