一块儿学设计模式 - 单例模式

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

<!-- more -->git

概述

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()));
    }
}

日志dom

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(欢迎调戏)

相关文章
相关标签/搜索