1.定义:保证一个类有且仅有一个实例并且提供一个访问该实例的全局访问点
2.UML
3.理论基础:封装
4.涉及角色:NA
5.优点
1.类的创建繁琐或者需要耗费很大的资源
2.避免多次new,减少GC压力
3.实例控制,保证实例唯一性,避免系统信息混乱
4.由于创建类的实例自己控制,可以灵活的改变创建实例的方式
6.缺点
1.系统开销,为了保证实例的唯一性,在创建实例时需要检测,增加系统开销
2.编程混淆,使用者发现自己无法创建实例
3.只有单例类能够销毁对象因为只有单例类持有实例的引用
4.需要考虑多线程下的单例,增加了复杂性
5.单例类承担的职责过多,创建&维护实例
7.使用场景
1.对象的创建耗时/活着耗费过多的资源
2.某些关键的业务流程eg控制流程,交易核心业务
8.Code
package com.patterns.build.single.version1;
public class Single {
//持有自身实例,单例模式的核心,赋值为null,延迟加载属于饿汉模式
private static Single instatnce = null;
//构造方法私有,防止被其他类创建也是单例模式的核心
private Single(){}
//提供外界统一访问点
public static Single getInstatnce() {
if (null == instatnce) {
instatnce = new Single();
}
return instatnce;
}
}
package com.patterns.build.single.version1;
import java.util.concurrent.Executor;
public class Cilent {
public static void main(String[] args) {
for(int i=0;i<10;i++) {
new Thread(){
@Override
public void run() {
System.out.println(Single.getInstatnce());
}
}.start();
}
}
}
以上为多线程下的单例模式,通过输出发现某些情况下实例是唯一的,某些情况下实例不唯一
考虑到多线程的我们首先想到的是synchronized
public class Single {
private static Single instatnce = null;
private Single(){}
public static synchronized Single getInstatnce() {
if (null == instatnce) {
instatnce = new Single();
}
return instatnce;
}
}
1.每次调用都会加锁,但是我们只需要在第一次创建的时候加锁,采用以上的方式浪费系统资源,效率不高
public class Single {
private static volatile Single instatnce = null;
private Single(){}
public static Single getInstatnce() {
if (null == instatnce) {
synchronized (Single.class) {
if(null == instatnce) {
instatnce = new Single();
}
}
}
return instatnce;
}
}
该种模式也叫双重锁模式:同时使用volatile & synchronized
但是该模式依然存在问题:赋值操作并不是原子操作。java指令中创建实例和赋值操作是分开的,也就是所instance = new Single();被分为两个步骤,但是jvm并不会保证这两个步骤的执行顺序.也就是说jvm可能先分配空间,然后在赋值,最后初始化该实例.使用volatile关键字,线程能够自发的发现volatile关键字修饰的变量的最新值.
分析如下:
假设现在有两个线程TA,TB
1.TA,TB同时进入第一个if
2.TA进入synchronized并执行instance = new SingleIntance(),jvm分配内存给该实例并执行赋值操作
3.TA CPU时间使用结束
4.TB进入synchronized,发现该实例不为空,返回该实例但是该实例没有被初始化,存在风险
进一步优化public class Single {
private Single(){}
private static class SingleFactory {
private static final Single instance = new Single();
}
public static Single getInstatnce() {
return SingleFactory.instance;
}
}
采用内部类来维护该实例,同样可以避免多线程的风险,在jvm中类的加载时候是线程互斥的,也就是所采用内部内完成了分配内存,赋值以及初始化的整个过程.
因为我们只需要在创建类的时候进行同步,所以只要将创建和getInstance()分开,单独为创建加synchronized关键字
public class Single {
private static Single instance;
private Single(){}
private synchronized static void createInstance() {
instance = new Single();
}
public Single getInstance() {
if(null == instance){
createInstance();
}
return instance;
}
}
考虑性能的话,整个程序只需创建一次实例,所以性能也不会有什么影响同时单例模式分为3种模式.
1.饿汉模式:一般会对实例进行判断.在初次使用时,创建实例
2.饱汉模式:提前创建好实例,直接使用
3.双重锁模式:同时使用volatile & synchronized