我的世界幸运方块模组下载手机版 app(我的世界幸运方块模组下载手机版大全)

大家好,欢迎关注极客架构师,极客架构师,专注架构师成长,我是码农老吴。

本期是《架构师基本功之设计模式》的第8期,我将基于单例模式(singleton pattern)及Google的guava cache缓存组件,打造高并发,线程安全的本地缓存。

我的世界幸运方块模组下载手机版 app(我的世界幸运方块模组下载手机版大全)

由于单例模式本身的简单性,还有就是单例模式一般不是通过重构代码获得的,而是直接完成,所以,本次分享的基本思路,不同于前面几个设计模式。这次我提前分享单例模式的定义及分析等概念性内容,然后进行案例和代码讲解,两者的顺序进行了对调。

一提到单例模式的代码实现方式,各种教程,形形色色,林林总总,你说两三种,我就说四五种,你说四五种,我就说个七八种,更有甚者,会介绍十多种,大有愈演愈烈,高度内卷的趋势。然而千变万化,不离其宗,我们要透过迷雾直达本质,单例模式根本上只有两种,其他的都是语法上的小技巧而已,大家不要本末倒置,舍本逐末。

结论先行

根据REIS分析模型,对单例模式进行分析,单例模式包含两种角色,单例服务方角色和单例客户方角色。它的宗旨是,单例服务方角色,通过私有化的构造函数,静态的类属性以及静态的getInstance()方法等措施,确保所有单例客户方,都只能获取到它的唯一一个对象。该模式的研究重点是对象实例化的时机问题,可以分为两种实例化方式,提前实例化和延迟实例化。

基本思路

高并发互联网项目的缓存架构

单例模式定义

使用REIS模型分析单例模式

提前实例化:第一版代码(属性初始化,推荐方案

提前实例化:第二版代码(静态块初始化)

延迟实例化:第一版代码(非线程安全,常见方案

延迟实例化:第一版代码优化(线程安全,低效)

延迟实例化:第一版代码再优化(线程安全,双重检测,高效,推荐方案

延迟实例化:第二版代码(线程安全,双重检测,可重入锁,高效)

延迟实例化:第三版代码(线程安全,静态内部类)

单例模式需要深入思考的点

单例模式通用代码

高并发互联网项目的缓存架构

互联网项目,尤其是电商行业的项目,常常被称为是高并发的软件项目,往往对QPS(或者TPS)吞吐量指标和TP99等性能指标有很高的要求。特别是618,双十一等重大节日,零点大促时,流量峰值,有可能是平时的几倍,甚至几十倍。一不小心,就有可能系统崩溃。而要让系统能达到这些吞吐量和性能指标,除了在大促之前的容器扩展之外,还有一个很重要的方面,就是在进行系统架构设计,技术选型时。充分发挥和挖掘缓存的潜力。可以这么说,缓存是高并发系统的银弹,性能不够,缓存来凑。

互联网项目常见的缓存体系,有客户端缓存(浏览器端),本地缓存(堆缓存,堆外缓存,磁盘缓存),分布式缓存集群(redis)等。这些,我们今天不具体展开了,后面我会有专门的分享,欢迎大家关注。今天,我们聚焦在本地缓存上。看看如何结合单例模式和Google的guava Cache缓存组件,打造高并发,线程安全的本地缓存。

我们今天单刀直入,从单例模式的定义,开始讲起。

单例模式(Singleton pattern)定义

Ensure a class only has one instance, and provide a global point of access to it.

—— Gof《Design Patterns: Elements of Reusable Object-Oriented Software》

中文:

保证一个类,仅有一个实例,并提供一个访问它的全局访问点。

这个定义有两个关键词,一个实例一个全局访问点

一个实例(one instance)

单例模式,顾名思义,就是单个实例。至于为什么只需要一个实例,定义里面并没有说,后面我们再展开说明。

一个全局访问点(a global point of access to it)

这个词如何理解呢?在常见的编程语言里面,全局访问点这个词很少见。但是,正常情况下,如果我们要访问一个实例,或者叫对象,应该如何访问呢。很明显,就是调用这个实例或者叫对象的变量名称。那么“全局访问点”就可以解释为“全局的变量名称”,有过其他开发语言经验的朋友,脑子里面可能就会浮现出“全局变量”这四个字。是的,你想得没错,就是全局变量。

但是java语言里面,并没有全局变量的概念。只能通过一定的编程技巧去模拟。而最常见的一种做法,就是通过类里面的static关键字来模拟全局变量。在后面的案例中,大家就能看到它。

下面我们用REIS模型,对单例模式进行分析,大家就会理解的更透彻一些。

使用REIS模型分析单例模式

REIS模型是我总结的分析设计模式的一种方法论,主要包括场景(scene),角色(role),交互(interaction),效果(effect)四个要素

场景(Scene)

场景,也就是我们在什么情况下,遇到了什么问题,需要使用某个设计模式。

当出现以下情况时,可能需要使用单例模式。

当某个类的对象,在系统中是独一无二的,只可以存在一个对象时,就需要使用单例模式,常见的有缓存管理器,线程池,数据库连接,日志管理器,配置参数,驱动程序等。今天我们就是要实现本地缓存管理器。

角色(Role)

角色,一般为设计模式出现的类,或者对象。每种角色有自己的职责。

单例模式,结构简单,里面只有两个角色,单例服务方角色,单例客户方角色。

单例服务方角色( singleton service role):单例服务方角色,就像概念里面所描述的那样,它是一个类,一个只允许创建一个实例的类,为了做到这一点,它把创建实例的权限对外屏蔽了,只有它自己可以使用。而为了让别人能访问到它的实例,它专门提供了一个static方法(也就是概念里面的全局访问点),用于返回自己的实例。它的职责概括如下:

  1. 私有构造函数:确保类的对象不能从外部创建,只能被它自己创建。
  2. 静态类属性,类型为它自身:通过静态属性,从语法角度确保这个类的对象,在JVM中只存在一个对象。
  3. 静态getInstance()方法,返回类型为它自身:这个静态方法,就是这个类的唯一一个全局访问点,通过这个静态方法,单例客户方可以获取单例服务方的唯一一个对象。

单例客户方角色(singleton client role):单例客户方,是需要访问单例服务方角色的任何对象,而且只能通过单例服务方角色提供的统一的全局访问点(也就是getInstance()方法),来获取单例服务方角色的唯一一个对象。任何企图绕过全局访问点,获取单例对象的单例客户方,都是不友好的,都应该被禁止,都需要后果自负。如果能绕过去,也间接说明单例服务方角色,没有通过有效的措施,确保只有一个全局访问点。

交互(interaction)

交互,是指设计模式中,各种角色是如何交互的,一般用UML中的序列图,活动图来表示。简单地说就是角色之间是如何配合,完成设计模式的使命的。

单例模式,虽然结构简单,交互看上去似乎也很简单,但是这里面另有蹊跷,大有文章,值得我们重视。

对于单例模式,非常重要的一个关键点就是,何时完成对象的实例化。单例角色的实例化(initialization)方式,根据实例化的时机,可以分为以下两种。

提前实例化(Early initialization or Eager initialization)方式

提前实例化,英文叫early initialization 或者 eager initialization,early大家都很明白,eager,中文意思是热切的,渴望的,渴求的,表达了一种急不可耐的情绪,中文文章中常常把这种方式,形象的称之为饿汉方式。

那么,到底要提前多少,愿望到底有多强烈呢,通常是在类加载的时候,不同的实现方式,稍有不同,但是底线还是比较明确的,就是在单例客户方首次访问getInstance()方法之前,也就是确保在客户需要是的时候,已经提前完成了对象初始化。

这种方式,优点是由于对象已经提前初始化了,所以无需考虑线程安全问题,代码比较简洁。而缺点也很明显,就是不管单例客户方是否使用,都提前创建好对象,可能会有一定的资源浪费,但是因为本身是单例,所以浪费的资源往往有限,常常可以忽略不计。所以,提前实例化,一般是首选方案,所以我们后面的案例,首选讲解的就是这种方式。

延迟实例化(Lazy initialization)方式

延迟实例化,英文是Lazy initialization,中文文章中,常常把这种方式,形象地比喻为懒汉方式,和上面的饿汉方式,形成了强烈的对比,表达了一种慵懒的,得过且过,能不做,最好不做的情绪。

那么,到底延迟到什么时候呢,很明显,只有到最后一刻,不得不做的地步,懒汉才会行动,deadline永远是第一生产力。具体的说,这最后一刻,就是单例客户方首次调用getInstance()方法的那一刻。

这种方式,优点就是不会造成资源浪费。但是缺点甚至说是风险是存在的,一个不小心,轻则效率低下,重则违背单例模式的宗旨,创建了多个对象。看来还是老话说得好,成功细中取, 富贵险中求。要想有收获,就需要一定的付出。由于需要考虑多线程安全问题,这里面常常需要使用多线程的相关技术,代码比较复杂一些,需要消耗一些码农的脑细胞,也常常是面试的重要内容。

效果(effect)

效果,使用该设计模式之后,达到了什么效果,有何意义,当然,也可以说说它的缺点,或者风险。

单例模式的宗旨只有确保一个类,只有一个对象,这样造成的客观效果就是数据统一,节省资源。我就不细说了。

前面的定义,概念,讲解完毕,下面我们开始编写代码。切身体会什么是单例模式。

前面我们已经提到过,提前实例化因为不需要考虑多线程问题,所以代码相对来说,比较简单,我们就从它开始讲起。

提前实例化:第一版代码(属性初始化,推荐方案)

POM文件

导入guava cache 组件


<?xml version="1.0" encoding="UTF-8"?>
<project xmlns="http://maven.apache.org/POM/4.0.0"
         xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
         xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 http://maven.apache.org/xsd/maven-4.0.0.xsd">
    <parent>
        <artifactId>DesignPatterns</artifactId>
        <groupId>com.geekarchitect.patterns</groupId>
        <version>1.0-SNAPSHOT</version>
    </parent>
    <modelVersion>4.0.0</modelVersion>

    <artifactId>singleton-pattern</artifactId>

    <dependencies>
        <dependency>
            <groupId>com.google.guava</groupId>
            <artifactId>guava</artifactId>
            <version>23.0</version>
        </dependency>
    </dependencies>

    <properties>
        <maven.compiler.source>8</maven.compiler.source>
        <maven.compiler.target>8</maven.compiler.target>
    </properties>

</project>

ILocalCache:本地缓存接口

缓存接口,比较高频的两个操作是加入缓存和从缓存中取出。

package com.geekarchitect.patterns.singleton.demo01;

/**
 * 本地缓存接口
 *
 * @author 极客架构师@吴念
 * @createTime 2022/5/30
 */
public interface ILocalCache {
    void put(String key, String value);

    String get(String key);
}

LocalCacheManagerV21:第一版代码(属性初始化)

要点提示:

  1. 私有构造函数:确保类不能从外部创建对象,只能被它自己创建。
  2. 静态getInstance()方法,返回类型为它自身:这个静态方法,就是这个类的唯一一个全局访问点,通过这个静态方法,单例客户方,可以获取这个单例服务方的唯一对象。
  3. 静态类属性,类型为它自身:通过静态属性LOCAL_CACHE_MANAGER_V_21,类型为LocalCacheManagerV11,确保这个类的对象,在JVM中只存在一个。
  4. 静态属性LOCAL_CACHE_MANAGER_V_21的初始化方式:这个属性,是在定义的时候,直接初始化,也就是属性初始化。
package com.geekarchitect.patterns.singleton.demo02;

import com.geekarchitect.patterns.singleton.demo01.ILocalCache;
import com.google.common.cache.Cache;
import com.google.common.cache.CacheBuilder;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;

/**
 * @author 极客架构师@吴念
 * @createTime 2022/5/30
 */
public class LocalCacheManagerV21 implements ILocalCache {
    private static final Logger LOG = LoggerFactory.getLogger(LocalCacheManagerV21.class);
    private static final LocalCacheManagerV21 LOCAL_CACHE_MANAGER_V_21 = new LocalCacheManagerV21();
    private final Cache<String, String> guavaCache;

    private LocalCacheManagerV21() {
        LOG.info("提前实例化:第一版代码(属性初始化)");
        LOG.info("初始化缓存");
        guavaCache = CacheBuilder.newBuilder().build();
    }

    public static LocalCacheManagerV21 getInstance() {
        return LOCAL_CACHE_MANAGER_V_21;
    }

    @Override
    public void put(String key, String value) {
        guavaCache.put(key, value);
    }

    @Override
    public String get(String key) {
        return guavaCache.getIfPresent(key);
    }
}

TestLocalCacheManagerV02: 测试类

package com.geekarchitect.patterns.singleton.demo02;

import org.slf4j.Logger;
import org.slf4j.LoggerFactory;

/**
 * @author 极客架构师@吴念
 * @createTime 2022/5/30
 */
public class TestLocalCacheManagerV02 {
    private static final Logger LOG = LoggerFactory.getLogger(TestLocalCacheManagerV02.class);

    public static void main(String[] args) {
        TestLocalCacheManagerV02 testLocalCacheManager = new TestLocalCacheManagerV02();
        testLocalCacheManager.demo01();
    }

    public void demo01() {
        LocalCacheManagerV21 localCacheManagerV2101 = LocalCacheManagerV21.getInstance();
        LocalCacheManagerV21 localCacheManagerV2102 = LocalCacheManagerV21.getInstance();
        localCacheManagerV2101.put("1", "北京");
        LOG.info("localCacheManagerV2101 == localCacheManagerV2102 is {}", (localCacheManagerV2101 == localCacheManagerV2102));
        LOG.info("localCacheManagerV2101.get('1')={} ", (localCacheManagerV2101.get("1")));
        LOG.info("localCacheManagerV2102.get('2')={} ", (localCacheManagerV2102.get("1")));
    }
    public void demo02() {
        LocalCacheManagerV22 localCacheManagerV2201 = LocalCacheManagerV22.getInstance();
        LocalCacheManagerV22 localCacheManagerV2202 = LocalCacheManagerV22.getInstance();
        localCacheManagerV2201.put("1", "北京");
        LOG.info("localCacheManagerV2201 == localCacheManagerV2202 is {}", (localCacheManagerV2201 == localCacheManagerV2202));
        LOG.info("localCacheManagerV2201.get('1')={} ", (localCacheManagerV2201.get("1")));
        LOG.info("localCacheManagerV2202.get('2')={} ", (localCacheManagerV2202.get("1")));
    }
}

运行结果

我的世界幸运方块模组下载手机版 app(我的世界幸运方块模组下载手机版大全)

头脑风暴

这版代码,通过对静态类属性的直接初始化,确保单例客户方在调用getInstance()方法之前,就完成了对象的实例化。单例客户方可以放心大胆使用单例对象,而不用考虑多线程问题。所以使用率比较高,是推荐方案。

但是,对于静态类属性的初始化,还有另外一种初始化方式,就是静态块方式。我们继续看第二版代码。

提前实例化:第二版代码(静态块初始化)

LocalCacheManagerV22:第二版代码(静态块初始化)

要点提示:

  1. 静态属性localCacheManagerV22的初始化方式:这个属性,不是在定义的时候初始化,而是在static块中进行初始化。
package com.geekarchitect.patterns.singleton.demo02;

import com.geekarchitect.patterns.singleton.demo01.ILocalCache;
import com.google.common.cache.Cache;
import com.google.common.cache.CacheBuilder;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;

/**
 * @author 极客架构师@吴念
 * @createTime 2022/5/30
 */
public class LocalCacheManagerV22 implements ILocalCache {
    private static final Logger LOG = LoggerFactory.getLogger(LocalCacheManagerV22.class);
    private final Cache<String, String> guavaCache;
    private static final LocalCacheManagerV22 localCacheManagerV22;

    static {
        LOG.info("提前实例化:第二版代码(静态块初始化,推荐方案)");
        localCacheManagerV22 = new LocalCacheManagerV22();
    }

    private LocalCacheManagerV22() {
        LOG.info("初始化缓存");
        guavaCache = CacheBuilder.newBuilder().build();
    }

    public static LocalCacheManagerV22 getInstance() {
        return localCacheManagerV22;
    }

    @Override
    public void put(String key, String value) {
        guavaCache.put(key, value);
    }

    @Override
    public String get(String key) {
        return guavaCache.getIfPresent(key);
    }
}

运行结果

我的世界幸运方块模组下载手机版 app(我的世界幸运方块模组下载手机版大全)

头脑风暴

这版代码,在static块中初始化静态类属性,代码会更灵活一些。

提前实例化方式实现的单例模式,有其固有的缺点,那就是可能会造成资源浪费,但由于是单例,所以一般浪费的都不多,所以它可以满足项目的大部分情况。但是对于追求技术精益求精,眼里不容沙子的研发人员,就需要精通下面的延迟实例化方式了,我们继续。

延迟实例化:第一版代码(非线程安全)

LocalCacheManagerV11:第一版(非线程安全)

要点提示:

  1. 私有构造函数:确保类不能从外部创建对象,只能被它自己创建。
  2. 静态类属性,类型为它自身:通过静态属性localCacheManagerV11,类型为LocalCacheManagerV11,从语法角度确保这个类的对象,在JVM中只存在一个。
  3. 静态getInstance()方法,返回类型为它自身:这个静态方法,就是这个类的唯一一个全局访问点,通过这个静态方法,单例客户方,可以获取这个单例服务的唯一对象。
package com.geekarchitect.patterns.singleton.demo01;

import com.google.common.cache.Cache;
import com.google.common.cache.CacheBuilder;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;

/**
 * 本地缓存管理器
 *
 * @author 极客架构师@吴念
 * @createTime 2022/5/30
 */
public class LocalCacheManagerV11 implements ILocalCache {
    private static final Logger LOG = LoggerFactory.getLogger(LocalCacheManagerV11.class);
    private static Cache<String, String> guavaCache = null;
    private static LocalCacheManagerV11 localCacheManagerV11 = null;

    private LocalCacheManagerV11() {
        initCache();
    }

    public static LocalCacheManagerV11 getInstance() {
        if (null == localCacheManagerV11) {
            LOG.info("延迟实例化:第一版代码(非线程安全)");    
            localCacheManagerV11 = new LocalCacheManagerV11();
        }
        return localCacheManagerV11;
    }

    private void initCache() {
        LOG.info("初始化缓存");
        guavaCache = CacheBuilder.newBuilder().build();
    }

    @Override
    public void put(String key, String value) {
        guavaCache.put(key, value);
    }

    @Override
    public String get(String key) {
        return guavaCache.getIfPresent(key);
    }
}

TestLocalCacheManagerV01:测试类

package com.geekarchitect.patterns.singleton.demo01;

import org.slf4j.Logger;
import org.slf4j.LoggerFactory;

/**
 * @author 极客架构师@吴念
 * @createTime 2022/5/30
 */
public class TestLocalCacheManagerV01 {
    private static final Logger LOG = LoggerFactory.getLogger(TestLocalCacheManagerV01.class);

    public static void main(String[] args) {
        TestLocalCacheManagerV01 testLocalCacheManagerV01 = new TestLocalCacheManagerV01();
        testLocalCacheManagerV01.demo01();
    }

    public void demo01() {
        LocalCacheManagerV11 localCacheManagerV1101 = LocalCacheManagerV11.getInstance();
        LocalCacheManagerV11 localCacheManagerV1102 = LocalCacheManagerV11.getInstance();
        localCacheManagerV1101.put("1", "北京");
        LOG.info("localCacheManagerV1101 == localCacheManagerV1102 is {}", (localCacheManagerV1101 == localCacheManagerV1102));
        LOG.info("localCacheManagerV1101.get('1')={} ", (localCacheManagerV1101.get("1")));
        LOG.info("localCacheManagerV1102.get('2')={} ", (localCacheManagerV1102.get("1")));

    }

    public void demo02() {
        LocalCacheManagerV12 localCacheManagerV1201 = LocalCacheManagerV12.getInstance();
        LocalCacheManagerV12 localCacheManagerV1202 = LocalCacheManagerV12.getInstance();
        localCacheManagerV1201.put("1", "北京");
        LOG.info("localCacheManagerV1201 == localCacheManagerV1202 is {}", (localCacheManagerV1201 == localCacheManagerV1202));
        LOG.info("localCacheManagerV1201.get('1')={} ", (localCacheManagerV1201.get("1")));
        LOG.info("localCacheManagerV1202.get('2')={} ", (localCacheManagerV1202.get("1")));
    }

    public void demo03() {
        LocalCacheManagerV13 localCacheManagerV1301 = LocalCacheManagerV13.getInstance();
        LocalCacheManagerV13 localCacheManagerV1302 = LocalCacheManagerV13.getInstance();
        localCacheManagerV1301.put("1", "北京");
        LOG.info("localCacheManagerV1301 == localCacheManagerV1302 is {}", (localCacheManagerV1301 == localCacheManagerV1302));
        LOG.info("localCacheManagerV1301.get('1')={} ", (localCacheManagerV1301.get("1")));
        LOG.info("localCacheManagerV1302.get('2')={} ", (localCacheManagerV1302.get("1")));
    }
}

运行结果

我的世界幸运方块模组下载手机版 app(我的世界幸运方块模组下载手机版大全)

头脑风暴

这版代码,就是典型的延迟实例化的单例模式,存储单例对象的静态属性localCacheManagerV11,只有在单例客户方第一次调用静态getInstance()方法时,才会进行实例化。

#一种比较常见的方式,在一般的系统中已经够用了。但是,在多线程环境里面,也就是单例客户方是分布在多个线程里面,并发调用,上面的代码就会出现问题。什么问题呢?

当多个处于不同线程的单例客户方并发调用getInstance()方法,可能会发生,创建多个单例服务方对象,破坏了单例模式的宗旨。问题还是挺严重的。所以需要进行后面的优化。

延迟实例化:第一版代码优化(线程安全,低效)

LocalCacheManagerV11:第一版优化后

要点提示:

  1. 静态getInstance()方法:为了线程安全,这个静态方法增加了synchronized关键字,确保统一时刻,只有一个线程可以访问这个方法。
package com.geekarchitect.patterns.singleton.demo01;

import com.google.common.cache.Cache;
import com.google.common.cache.CacheBuilder;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;

/**
 * 本地缓存管理器
 *
 * @author 极客架构师@吴念
 * @createTime 2022/5/30
 */
public class LocalCacheManagerV11 implements ILocalCache {
    private static final Logger LOG = LoggerFactory.getLogger(LocalCacheManagerV11.class);
    private static Cache<String, String> guavaCache = null;
    private static LocalCacheManagerV11 localCacheManagerV11 = null;

    private LocalCacheManagerV11() {
        initCache();
    }

    
    public static synchronized LocalCacheManagerV11 getInstance() {
        if (null == localCacheManagerV11) {
            LOG.info("延迟实例化:第一版代码优化(线程安全,低效)");
            localCacheManagerV11 = new LocalCacheManagerV11();
        }
        return localCacheManagerV11;
    }
     
    
    private void initCache() {
        LOG.info("初始化缓存");
        guavaCache = CacheBuilder.newBuilder().build();
    }

    @Override
    public void put(String key, String value) {
        guavaCache.put(key, value);
    }

    @Override
    public String get(String key) {
        return guavaCache.getIfPresent(key);
    }
}

运行结果

我的世界幸运方块模组下载手机版 app(我的世界幸运方块模组下载手机版大全)

头脑风暴

这版代码,与上一版代码,只有一个细微的差别,就是为了线程安全,这个静态getInstance()方法增加了synchronized关键字。解决了多线程的安全问题,不论有多少个线程访问这个方法,同一时刻,都只能有一个线程获取到锁,这样就避免了创建多个对象的风险。但是代码太大了,大到不能承受。

由于我们增加了synchronized关键字导致调用getInstance()方法,由并发调用,变成了低效率的串行调用,也就是排队,一个接一个调用。在高并发的互联网项目中,是不能容忍的,所以这版代码作废,我们必须继续升级。

延迟实例化:第一版代码再优化(线程安全,双重检测,高效,推荐方案)

LocalCacheManagerV11:第一版二次优化

要点提示:

  1. 静态getInstance()方法:为了线程安全,并且高效,使用了双重检测,大家注意,这里面有两个if语句
package com.geekarchitect.patterns.singleton.demo01;

import com.google.common.cache.Cache;
import com.google.common.cache.CacheBuilder;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;

/**
 * 本地缓存管理器
 *
 * @author 极客架构师@吴念
 * @createTime 2022/5/30
 */
public class LocalCacheManagerV11 implements ILocalCache {
    private static final Logger LOG = LoggerFactory.getLogger(LocalCacheManagerV11.class);
    private static Cache<String, String> guavaCache = null;
    private static LocalCacheManagerV11 localCacheManagerV11 = null;

    private LocalCacheManagerV11() {
        initCache();
    }

    
    public static LocalCacheManagerV11 getInstance() {
        if (null == localCacheManagerV11) {
            synchronized (LocalCacheManagerV11.class) {
                if (null == localCacheManagerV11) {
                    localCacheManagerV11 = new LocalCacheManagerV11();
                }
            }
        }
        return localCacheManagerV11;
    }
     
    
    private void initCache() {
        LOG.info("初始化缓存");
        guavaCache = CacheBuilder.newBuilder().build();
    }

    @Override
    public void put(String key, String value) {
        guavaCache.put(key, value);
    }

    @Override
    public String get(String key) {
        return guavaCache.getIfPresent(key);
    }
}

运行结果

我的世界幸运方块模组下载手机版 app(我的世界幸运方块模组下载手机版大全)

头脑风暴

这版代码,对于静态getInstance()方法,使用了双重检测机制,减少了synchronized 加锁的范围,当多个线程中的单例客户方,并发调用这个方法时,如果对象还没有创建,哪个线程先获取到锁,哪个线程执行创建对象的任务。如果对象已经创建,则直接返回即可,效率非常高。

可以说,这版代码,高效,线程安全,简洁,所以是推荐方案。但是在java5的多线程体现中,引入了一个新的锁,ReentrantLock可重入锁,比较流行,非常灵活,所以对于这版代码,我们还可以换一种实现方式,我们继续。

延迟实例化:第二版代码(线程安全,双重检测,可重入锁,高效)

LocalCacheManagerV12:第二版

要点提示:

1,建立一个可重入锁:ReentrantLock

2,基于ReentrantLock,实现了双重检测机制

package com.geekarchitect.patterns.singleton.demo01;

import com.google.common.cache.Cache;
import com.google.common.cache.CacheBuilder;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;

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

/**
 *
 * @author 极客架构师@吴念
 * @createTime 2022/5/30
 */
public class LocalCacheManagerV12 implements ILocalCache {
    private static final Logger LOG = LoggerFactory.getLogger(LocalCacheManagerV12.class);
    private static Cache<String, String> guavaCache = null;
    private static LocalCacheManagerV12 localCacheManagerV01 = null;
    private static final Lock reentrantLock = new ReentrantLock();

    private LocalCacheManagerV12() {
        initCache();
    }

    public static LocalCacheManagerV12 getInstance() {
        if (null == localCacheManagerV01) {
            reentrantLock.lock();
            LOG.info("第二版代码(线程安全,双重检测,可重入锁,高效)");
            try {
                if (null == localCacheManagerV01) {
                    localCacheManagerV01 = new LocalCacheManagerV12();
                }
            } finally {
                reentrantLock.unlock();
            }
        }
        return localCacheManagerV01;
    }

    private void initCache() {
        LOG.info("初始化缓存");
        guavaCache = CacheBuilder.newBuilder().build();
    }

    @Override
    public void put(String key, String value) {
        guavaCache.put(key, value);
    }

    @Override
    public String get(String key) {
        return guavaCache.getIfPresent(key);
    }
}

运行结果

我的世界幸运方块模组下载手机版 app(我的世界幸运方块模组下载手机版大全)

头脑风暴

这版代码实现的代码,也满足线程安全和高效,也实现了双重检测机制,甚至在复杂情况下,可以使用ReentrantLock编写出更简洁,更高效,更灵活的多线程代码,这也是ReentrantLock在java5出现,要对synchronized关键字取而代之的一个重要原因

但是,但是,但是,凡事都有代价,前提就是你必须精通ReentrantLock,如果不精通,编写出的代码就可能存在漏洞。你如果能明白,我为什么要把ReentrantLock的unlock()方法调用,放在finally块中,应该就具备这种资格。

延迟实例化:第三版代码(线程安全,静态内部类)

LocalCacheManagerV13:第三版

要点提示:

1,增加了一个静态内部类:LocalCacheManagerHolder

2,静态内部类中定义了一个静态属性,用于存储单例服务方对象。

package com.geekarchitect.patterns.singleton.demo01;

import com.google.common.cache.Cache;
import com.google.common.cache.CacheBuilder;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;

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

/**
 * @author 极客架构师@吴念
 * @createTime 2022/5/30
 */
public class LocalCacheManagerV13 implements ILocalCache {
    private static final Logger LOG = LoggerFactory.getLogger(LocalCacheManagerV13.class);
    private static Cache<String, String> guavaCache = null;
   
    private LocalCacheManagerV13() {
        initCache();
    }

    public static LocalCacheManagerV13 getInstance() {
        return LocalCacheManagerHolder.localCacheManagerV13;
    }

    private void initCache() {
        LOG.info("第三版代码(线程安全,静态内部类)");
        LOG.info("初始化缓存");
        guavaCache = CacheBuilder.newBuilder().build();
    }

    @Override
    public void put(String key, String value) {
        guavaCache.put(key, value);
    }

    @Override
    public String get(String key) {
        return guavaCache.getIfPresent(key);
    }

    public static class LocalCacheManagerHolder {
        private static final LocalCacheManagerV13 localCacheManagerV13 = new LocalCacheManagerV13();
    }
}

运行结果

我的世界幸运方块模组下载手机版 app(我的世界幸运方块模组下载手机版大全)

头脑风暴

这版代码,从代码复杂度上看,精简了不少,也不用考虑什么多线程问题,它也是线程安全的。这些都有赖于java的静态内部类,里面的静态属性初始化机制。也就是多线程问题我们不用考虑了,全部交给JVM来处理。但是静态内部类,在常见的项目中,大部分人并不了解它的机制,精通的人更少,所以比较少见。这里仅供大家参考。我是不建议使用大家都不太熟悉的,奇奇怪怪的语法,在团队开发中,有百害而无一利,得不偿失。

单例模式的延迟实例化方式,我们全部讲解完毕。下面我们看另外一个方式,提前实例化。

单例模式需要深入思考的点

提前实例化和延迟实例化,该选哪个?

如果搞不清楚,就选提前实例化,代码简单,还线程安全。如果对代码质量精益求精,则在单例服务方有可能不会被使用的业务场景,考虑使用延迟实例化,降低资源浪费的概率。

单例模式通用代码

UML类图

我的世界幸运方块模组下载手机版 app(我的世界幸运方块模组下载手机版大全)

单例模式-提前实例化

package com.geekarchitect.patterns.singleton.demo03;import com.geekarchitect.patterns.singleton.demo02.LocalCacheManagerV22;import org.slf4j.Logger;import org.slf4j.LoggerFactory;/** * 单例模式:提前实例化 * * @author 极客架构师@吴念 * @createTime 2022/6/1 */public class EarlyInstance {    private static final Logger LOG = LoggerFactory.getLogger(EarlyInstance.class);    private static final EarlyInstance EARLY_INSTANCE = new EarlyInstance();    private EarlyInstance() {    }    public static EarlyInstance getInstance() {        return EARLY_INSTANCE;    }    public void doService(){       LOG.info("单例模式:提前实例化 ");    }}

单例模式-延迟实例化

package com.geekarchitect.patterns.singleton.demo03;import org.slf4j.Logger;import org.slf4j.LoggerFactory;/** * 单例模式:延迟实例化 * * @author 极客架构师@吴念 * @createTime 2022/6/1 */public class LazyInstance {    private static final Logger LOG = LoggerFactory.getLogger(LazyInstance.class);    private static LazyInstance LAZY_INSTANCE;    private LazyInstance() {    }    public static LazyInstance getInstance() {        if (null == LAZY_INSTANCE) {            synchronized (LazyInstance.class) {                if (null == LAZY_INSTANCE) {                    LAZY_INSTANCE = new LazyInstance();                }            }        }        return LAZY_INSTANCE;    }    public void doService(){        LOG.info("单例模式:延迟实例化");    }}

结论

综上所述,根据REIS分析模型,对单例模式进行分析,单例模式包含两种角色,单例服务方角色和单例客户方角色。它的宗旨是,单例服务方角色,通过私有化的构造函数,静态的类属性以及静态的getInstance()方法等措施,确保所有单例客户方,都只能获取到它的唯一一个对象。该模式的研究重点是对象实例化的时机问题,可以分为两种实例化方式,提前实例化和延迟实例化。

本期我们就分享到这里,关注我,我将持续分享更多架构师的相关文章和视频,我们下期见。

本文内容由互联网用户自发贡献,该文观点仅代表作者本人。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如发现本站有涉嫌抄袭侵权/违法违规的内容, 请发送邮件至 sumchina520@foxmail.com 举报,一经查实,本站将立刻删除。
如若转载,请注明出处:https://www.sumedu.com/faq/96337.html