单例设计模式(Singleton Pattern)
是最简单且常见的设计模式之一,主要做用是提供一个全局访问且只实例化一次的对象,避免多实例对象的状况下引发逻辑性错误(实例化数量可控)
...java
<!-- more -->git
Java中,单例模式
主要分三种:懒汉式单例、饿汉式单例、登记式单例三种。设计模式
ClassLoad
的时候就已经实例化好,该操做过于风骚会形成资源浪费特色安全
案例性能优化
任务管理器
中看到,但始终只能打开一个任务管理器
,它在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
分析: 在单线程环境一切正常,balancer1
和balancer2
两个对象的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
微信公众号:battcn
(欢迎调戏)