思维导图:java学习思维导图| ProcessOn免费在线作图,在线流程图,在线思维导图
gittee地址:zsc-design: 设计模式 - Gitee.com
定义:一个类在任何一种情况下都绝对只有一个实例,并提供一个全局访问点
1.饿汉式单例:在启动时就加载
2.懒汉式单例:在使用的时候在进行初始化
3.注册式单例:将每一个实例都缓存到统一的容器中,使用唯一的标识获取实例
4.ThreadLocal单例:天生线程安全,保证线程内部得全局唯一
写法1:
public class HungrySingleton1 {
private final static HungrySingleton1 INSTANCE = new HungrySingleton1();
private HungrySingleton1(){
}
private static HungrySingleton1 getInstance(){
return INSTANCE;
}
}
写法2:
public class HungerSingleton2 {
private final static HungerSingleton2 INSTANCE;
static {
INSTANCE = new HungerSingleton2();
}
private HungerSingleton2(){
}
private static HungerSingleton2 getInstance(){
return INSTANCE;
}
}
优点: 启动时就加载,效率高
缺点:在某些情况下,如果该实例不被使用到那么就会造成内存的浪费
思考:如何避免内存的浪费?在使用时再进行初始化,因此就有了懒汉式单例
懒汉式单例写法1
public class LazySingleton1 {
private static LazySingleton1 INSTANCE;
private LazySingleton1(){
}
private static LazySingleton1 getInstance(){
if (INSTANCE == null) {
return new LazySingleton1();
}
return INSTANCE;
}
}
优点: 在使用的时候调用getInstance方法才初始化实例对象,避免了内存浪费
缺点:线程不安全,当两个线程同时进入到if判断条件时,就会创建两个不同的对象,违背了单例模式的定义
思考:如何避免线程不安全问题? 在方法上增加synchronized关键字,这样就可以只有一个线程进入了
public class LazySingleton2 {
private static LazySingleton2 INSTANCE;
private LazySingleton2(){
}
private synchronized static LazySingleton2 getInstance(){
if (INSTANCE == null) {
return new LazySingleton2();
}
return INSTANCE;
}
}
优点:线程安全了
缺点:但容易影响系统性能,全阻塞在了方法的外面。举个非常形象的例子:在地铁站你不能在把人们都堵在地铁外面,用户体验肯定不好,而应该把人放到地铁里面等。
思考: 如何提高系统性能?把锁加在方法里面,进行双重检查
public class LazySingleton3 {
private static LazySingleton3 INSTANCE;
private LazySingleton3(){
}
private static LazySingleton3 getInstance(){
if (INSTANCE == null) {
synchronized (LazySingleton3.class) {
if (INSTANCE == null) {
return new LazySingleton3();
}
}
}
return INSTANCE;
}
}
优点: 线程安全了,性能提上去了
缺点: 但是还有一个指令重排序的问题 在定义属性时增加volatile关键字(后续博客详解);但是这种写法可读性差,不够优雅
采用静态内部类实现单例
public class LazySingleton4 {
private LazySingleton4(){
}
private static LazySingleton4 getInstance(){
return LazyInner.INSTANCE;
}
private static class LazyInner{
private final static LazySingleton4 INSTANCE = new LazySingleton4();
}
}
优点:写法优雅了
缺点:但是还会有反射可以破坏单例。如下
反射破坏单例
Class> clazz = LazySingleton4.class;
try {
Constructor c = clazz.getDeclaredConstructor(null);
c.setAccessible(true); //设置访问权限
Object instance1 = c.newInstance();
Object instance2 = c.newInstance();
System.out.println(instance1); // com.zsc.singleton.lazysingleton.LazySingleton4@1540e19d
System.out.println(instance2); // com.zsc.singleton.lazysingleton.LazySingleton4@677327b6
System.out.println(instance1 == instance2); //false
} catch (Exception e) {
e.printStackTrace();
}
通过反射可以获得两个不同的对象
思考:如何解决反射破坏单例?反射主要是通过构造器访问到我们创建实例的方法的,因此在构造器里加判断方法就可以解决了。如下:
public class LazySingleton4 {
private LazySingleton4() throws Exception {
if (LazyInner.INSTANCE != null) {
throw new Exception("不允许非法访问!");
}
}
private static LazySingleton4 getInstance(){
return LazyInner.INSTANCE;
}
private static class LazyInner{
private final static LazySingleton4 INSTANCE;
static {
try {
INSTANCE = new LazySingleton4();
} catch (Exception e) {
throw new RuntimeException(e);
}
}
}
}
优点: 反射破环不了单例了
缺点: 写法不够简洁,优雅
枚举式单例(注册式单例的一种)
public enum EnumSingleton {
INSTANCE;
private Object data;
private static EnumSingleton getInstance(){
return INSTANCE;
}
public Object getData() {
return data;
}
public void setData(Object data) {
this.data = data;
}
}
优点:线程安全,不被反射破坏
缺点: 在创建的时候就放入了map,在某种情况下存在资源浪费
思考:为什么不会被反射破坏?为什么是线程安全的?如何解决资源浪费的问题,ioc容器单例模式的创建应运而生
枚举式单例不被反射破坏的源码(与上述懒汉式单例解决的方法一致,但是人家是官方的,咱们自己的就不优雅,手动狗头)
为什么是线程安全的,这是跟enum的结构有关,它是一个map形式的存储方式,在我们sheng'ming
IOC容器单例模式(借鉴了枚举类单例的思想)
public class IocSingleton {
private IocSingleton(){
}
private static Map ioc = new ConcurrentHashMap();
public static Object getInstance(String className) {
Object instance = null;
if (!ioc.containsKey(className)) {
try {
instance = Class.forName(className).newInstance();
ioc.put(className, instance);
} catch (Exception e) {
e.printStackTrace();
}
return instance;
}
return instance;
}
}
缺点:存在线程安全问题,具体解决方法可参考spring源码
public class ThreadLocalSingleton {
private static final ThreadLocal threadLocalInstance =
new ThreadLocal(){
@Override
protected ThreadLocalSingleton initialValue() {
return new ThreadLocalSingleton();
}
};
private ThreadLocalSingleton(){};
public static ThreadLocalSingleton getInstance(){
return threadLocalInstance.get();
}
}
优点:按线程进行隔离的,每个线程具有唯一的实例。原因可参考源码,该this是一个线程内的this
总结:单例模式的思考
1.需要考虑 内存的占用,资源的浪费
2.需要考虑 线程安全 问题
3.需要考虑 反射破坏单例问题
4.需要考虑 序列化破坏单例的问题