一起学设计模式 - 单例模式

单例设计模式(Singleton Pattern)是最简单且常见的设计模式之一,主要作用是提供一个全局访问且只实例化一次的对象,避免多实例对象的情况下引起逻辑性错误(实例化数量可控)

概述

Java中,单例模式主要分三种:懒汉式单例、饿汉式单例、登记式单例三种。

  • 懒汉:非线程安全,需要用一定的风骚操作控制,装逼失败有可能导致看一周的海绵宝宝
  • 饿汉:天生线程安全,ClassLoad的时候就已经实例化好,该操作过于风骚会造成资源浪费
  • 单例注册表:Spring初始化Bean的时候,默认单例用的就是该方式

特点

  • 私有构造方法,只能有一个实例。
  • 私有静态引用指向自己实例,必须是自己在内部创建的唯一实例。
  • 单例类给其它对象提供的都是自己创建的唯一实例

案例

  • 在计算机系统中,内存、线程、CPU等使用情况都可以再任务管理器中看到,但始终只能打开一个任务管理器,它在Windows操作系统中是具备唯一性的,因为弹多个框多次采集数据浪费性能不说,采集数据存在误差那就有点逗比了不是么…
  • 每台电脑只有一个打印机后台处理程序
  • 线程池的设计一般也是采用单例模式,方便对池中的线程进行控制

注意事项

  • 实现方式种类较多,有的非线程安全方式的创建需要特别注意,且在使用的时候尽量根据场景选取较优的,线程安全了还需要去考虑性能问题。
  • 不适用于变化的对象,如果同一类型的对象总是要在不同的用例场景发生变化,单例就会引起数据的错误,不能保存彼此的状态。
  • 没有抽象层,扩展有困难。
  • 职责过重,在一定程度上违背了单一职责原则
  • 使用时不能用反射模式创建单例,否则会实例化一个新的对象

解锁姿势

第一种:单一检查(懒汉)非线程安全

public class LazyLoadBalancer {

    private static LazyLoadBalancer loadBalancer;
    private List<String> servers = null;

    private LazyLoadBalancer() {
        servers = new ArrayList<>();
    }

    public void addServer(String server) {
        servers.add(server);
    }

    public String getServer() {
        Random random = new Random();
        int i = random.nextInt(servers.size());
        return servers.get(i);
    }

    public static LazyLoadBalancer getInstance() {
        // 第一步:假设T1,T2两个线程同时进来且满足 loadBalancer == null
        if (loadBalancer == null) {
            // 第二步:那么 loadBalancer 即会被实例化2次
            loadBalancer = new LazyLoadBalancer();
        }
        return loadBalancer;
    }

    public static void main(String[] args) {
        LazyLoadBalancer balancer1 = LazyLoadBalancer.getInstance();
        LazyLoadBalancer balancer2 = LazyLoadBalancer.getInstance();
        System.out.println("hashCode:"+balancer1.hashCode());
        System.out.println("hashCode:"+balancer2.hashCode());
        balancer1.addServer("Server 1");
        balancer2.addServer("Server 2");
        IntStream.range(0, 5).forEach(i -> System.out.println("转发至:" + balancer1.getServer()));
    }
}

日志

hashCode:460141958
hashCode:460141958
转发至:Server 2
转发至:Server 2
转发至:Server 2
转发至:Server 1
转发至:Server 2

分析: 在单线程环境一切正常,balancer1balancer2两个对象的hashCode一模一样,由此可以判断出堆栈中只有一份内容,不过该代码块中存在线程安全隐患,因为缺乏竞争条件,多线程环境资源竞争的时候就显得不太乐观了,请看上文代码注释内容

第二种:无脑上锁(懒汉)线程安全,性能较差,第一种升级版

public synchronized static LazyLoadBalancer getInstance() {
    if (loadBalancer == null) {
        loadBalancer = new LazyLoadBalancer();
    }
    return loadBalancer;
}

分析: 毫无疑问,知道synchronized关键字的都知道,同步方法在锁没释放之前,其它线程都在排队候着呢,想不安全都不行啊,但在安全的同时,性能方面就显得短板了,我就初始化一次,你丫的每次来都上个锁,不累的吗(没关系,它是为了第三种做铺垫的)..

第三种:双重检查锁(DCL),完全就是前两种的结合体啊,有木有,只是将同步方法升级成了同步代码块

//划重点了 **volatile**
private volatile static LazyLoadBalancer loadBalancer;

public static LazyLoadBalancer getInstance() {
    if (loadBalancer == null) {
        synchronized (LazyLoadBalancer.class) {
            if (loadBalancer == null) {
                loadBalancer = new LazyLoadBalancer();
            }
        }
    }
    return loadBalancer;
}

1.假设new LazyLoadBalancer()加载内容过多
2.因重排而导致loadBalancer提前不为空
3.正好被其它线程观察到对象非空直接返回使用

mem = allocate();                  //LazyLoadBalancer 分配内存
instance = mem;                   //注意当前实例已经不为空了                      
initByLoadBalancer(instance);    //但是还有其它实例未初始化

存在问题: 首先我们一定要清楚,DCL是不能保证线程安全的,稍微了解过JVM的就清楚,对比C/C++它始终缺少一个正式的内存模型,所以为了提升性能,它还会做一次指令重排操作,这个时候就会导致loadBalancer提前不为空,正好被其它线程观察到对象非空直接返回使用(但实际还有部分内容没加载完成)

解决方案:volatile修饰loadBalancer,因为volatile修饰的成员变量可以确保多个线程都能够顺序处理,它会屏蔽JVM指令重排带来的性能优化

volatile详解:http://blog.battcn.com/2017/10/18/java/thread/thread-volatile/

第四种:Demand Holder (懒汉)线程安全,推荐使用

private LazyLoadBalancer() {}

private static class LoadBalancerHolder {
    //在JVM中 final 对象只会被实例化一次,无法修改
    private final static LazyLoadBalancer INSTANCE = new LazyLoadBalancer();
}

public static LazyLoadBalancer getInstance() {
    return LoadBalancerHolder.INSTANCE;
}

分析:Demand Holder中,我们在LazyLoadBalancer里增加一个静态(static)内部类,在该内部类中创建单例对象,再将
该单例对象通过getInstance()方法返回给外部使用,由于静态单例对象没有作为LazyLoadBalancer的成员变量直接实例化,类加载时并不会实例化LoadBalancerHolder,因此既可以实现延迟加载,又可以保证线程安全,不影响系统性能(居家旅行必备良药啊)

第五种:枚举特性(懒汉)线程安全,推荐使用

enum Lazy {
    INSTANCE;
    private LazyLoadBalancer loadBalancer;

    //枚举的特性,在JVM中只会被实例化一次
    Lazy() {
        loadBalancer = new LazyLoadBalancer();
    }

    public LazyLoadBalancer getInstance() {
        return loadBalancer;
    }
}

分析: 相比上一种,该方式同样是用到了JAVA特性:枚举类保证只有一个实例(即使使用反射机制也无法多次实例化一个枚举量)

第六种:饿汉单例(天生线程安全),

public class EagerLoadBalancer {
    private final static EagerLoadBalancer INSTANCE = new EagerLoadBalancer();

    private EagerLoadBalancer() {}

    public static EagerLoadBalancer getInstance() {
        return INSTANCE;
    }
}

分析: 利用ClassLoad机制,在加载时进行实例化,同时静态方法只在编译期间执行一次初始化,也就只有一个对象。使用的时候已被初始化完毕可以直接调用,但是相比懒汉模式,它在使用的时候速度最快,但这玩意就像自己挖的坑哭着也得跳,你不用也得初始化一份在内存中占个坑…

- 说点什么

全文代码:https://gitee.com/battcn/design-pattern/tree/master/Chapter2/battcn-singleton

  • 个人QQ:1837307557
  • battcn开源群(适合新手):391619659

微信公众号:battcn(欢迎调戏)

  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
Lazy-Balancer 项目起源于好哥们需要一个 7 层负载均衡器,无奈商业负载均衡器成本高昂,操作复杂。又没有特别喜欢(好看,好用)的开源产品,作为一名大 Ops 怎么能没有办法?正好最近在看 Django 框架,尝试自己给 Nginx 画皮,项目诞生!非专业开发,代码凑合看吧。 项目基于 Django   AdminLTE 构建,在 Ubuntu 14.04 上测试通过;为了保证良好的兼容性,请使用 Chrome 浏览器。 因为增加了 iptables 自动控制,所以暂时不支持 docker 方式部署;需要本地测试的同请使用 vagrant 方式 为了后续扩展方便,请大家使用 Tengine 替代 Nginx 服务 项目地址 GITHUB - https://github.com/v55448330/lazy-balancer 码云 - http://git.oschina.net/v55448330/lazy-balancer OSCHINA - http://www.oschina.net/p/nginx-balancer 更新 将 Nginx 更换为 Tengine 以提供更灵活的功能支持以及性能提升 新增 HTTP 状态码方式检测后端服务器,默认 TCP 方式 新增 HTTP 状态码方式支持查看后端服务器状态 修复因前方有防火墙导致无法获取后端服务器状态 修复因主机头导致后端服务器探测失败 新增自定义管理员用户 新增配置通过文件备份和还原 新增实时查看访问日志和错误日志 新增实时请求统计 更新 Vagrantfile 修复其他 Bug 功能 Nginx 可视化配置 Nginx 负载均衡(反向代理)配置 Nginx 证书支持 系统状态监测 自动维护防火墙规则(白名单) 支持 TCP 被动后端节点宕机检测 支持 HTTP 主动后端节点宕机检测 运行 克隆代码 mkdir -p /app git clone https://github.com/v55448330/lazy-balancer.git /app/lazy_balancer cd /app/lazy_balancer 卸载 nginx apt-get -y purge nginx-* nginx* apt-get -y autoremove 安装 tengine git submodule update --init --recursive cd resource/nginx/tengine apt-get install -y build-essential libssl-dev libpcre3 libpcre3-dev zlib1g-dev ./configure --user=www-data --group=www-data --prefix=/etc/nginx --sbin-path=/usr/sbin --error-log-path=/var/log/nginx/error.log --conf-path=/etc/nginx/nginx.conf --pid-path=/run/nginx.pid make make install mkdir -p /etc/nginx/conf.d echo "daemon off;" >> /etc/nginx/nginx.conf 安装 supervisor apt-get install supervisor update-rc.d supervisor enable 配置 supervisor cp -rf service/* /etc/supervisor/ 安装依赖 apt-get install -y python-dev python-pip iptables libcurl4-openssl-dev pip install -r requirements.txt 初始化数据库 python manage.py makemigrations python manage.py migrate 启动服务 service supervisor restart

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值