历史最全单例模式总结
Posted 码农赵大爷
tags:
篇首语:本文由小常识网(cha138.com)小编为大家整理,主要介绍了历史最全单例模式总结相关的知识,希望对你有一定的参考价值。
单例模式是最适合手撕代码的设计模式了,也可能是代码最少的设计模式了,但是少不一定意味着简单,想要用好,用对单例模式,还是需要费一些脑筋的。况且最近在准备面试刷《剑指Offer》时候,发现第一个就是写单例模式,而对单例模式没有一个全局的认识 ,经常看到懒汉式,饿汉
式这种词,今天决定全面梳理一下,加入我的面试题库中,当作标准答案,
背下来 下面对Java中常见的单例模式做一个最全的总结。 一个好的单例一
定满足:线程安全,延迟加载,序列化与反序列化安全
饿汉式:在第一次引用该类的时候就创建对象实例,而不管实际是否需
要创建,但是这样无法延迟加载,所以这种无法满足需求,由此就需要懒汉式:
public class Singleton {
private static Singleton singleton = new Singleton();
private Singleton(){}
public static Singleton getSingleton(){
return singleton;
}
}
懒汉式:分为单线程写法,考虑线程安全的写法,兼顾安全和效率的写法单线程写法:由私有构造器和一个公有静态工厂方法构成,在工厂方法中
对singleton进行null判断,如果是null就new一个出来,最后返回singleton
对象,这种方法可以实现延迟加载,但是有一个致命弱点:线程不安全。如果
两个线程同时调用getSingleton()方法,就有很大可能导致重复创建对象,由此引出 考虑线程安全的写法:
public class Singleton{
private static Singleton singleton = null;
private Singleton(){}
public static Singleton getSingleton(){
if (singleton == null) {
singleton = new Singleton();
}
return singleton;
}
}
考虑线程安全的写法:这种写法考虑到了线程安全,将对singleton的
null判断以及 new的部分使用synchronized进行加锁。虽然这种写法
可以正确运行,但是效率低下,无法实际应用,原因是每次调用
getSingleton()方法,都需要在synchronized这里排队,而真正用到
new的情况是非常少的,所以引出下面的写法 :
public class Singleton{
private static Singleton singleton = null;
private Singleton(){}
public static Singleton getSingleton(){
synchronized (Singleton.class){
if (singleton == null){
singleton = new Singleton();
}
return singleton;
}
}
}
兼顾安全和效率的写法:运用双重检查锁。在getSingleton()方法中,
进行两次null检查,看似多此一举,实际确极大提高了并发度。为什么
可以提高并发度呢,上文中提到过,在单例中 new的情况是非常少的
,绝大部分都是可以并行的读操作,因此在加锁前多进行一次null检查
,可以杜绝绝大部分的加锁操作,达到提高并发度的目的。这种写法要
注意一点,由于用到了volatile这个关键字的禁止指令重排序语义,而
在JDK1.5及以后,可以保证禁止指令重排序。而在jdk1.5以前的版本,
即使将变量声明为volatile也无法完全避免重排序导致的问题。(这个关
键字有两个作用:1是作用于变量时的可见性,可见性指的是在一个线程
中对该变量的修改会马上由工作内存写回主内存,工作线程是线程独享
的,主内存是线程共享的。2是禁止指令重排序优化。由于现在的cpu都
是多核心的,为了利用好多核性能,提升运行速度,编译器会对指令优
化,在实际执行的时候可能和我们编写的顺序不同,编译器能做的保证
是执行结果与源代码相同,而不保证实际指令的顺序与源代码相同。)
问题1:为什么singleton用private且static修饰?
答:这里用static是因为下面的方法是静态方法,静态方法不能调用非静态变量,所以设为static,
用private是因为由于对象是static,必须要设置为private,因为若设置成public,就可以直接
调用对象而不用执行访问入口了
问题2:为什么构造器是私有的?
答:因为不想被直接实例化,所以将构造器设为private的
问题3:getSingleton方法用static的原因?
答:若不用static,为实例方法,而实例方法必须通过对象调用,但是构造方法是private的,
无法直接new,所以设置为static,成为一个静态方法
public class Singleton{
private static volatile Singleton singleton = null;
private Singleton(){}
public static Singleton getSingleton(){
if (singleton == null){
synchronized (Singleton.class){
if (singleton == null){
singleton = new Singleton();
}
}
}
return singleton;
}
}
推荐写法:静态内部类法:推荐理由:把singleton实例放到一个静态内
部类中,避免了静态实例在singleton类加载时候就创建对象,并且由于
静态内部类只会被加载一次,所以这种写法也是线程安全的
public class Singleton{
private static class Holder{
private static Singleton singleton = new Singleton();
}
private Singleton(){}
public static Singleton getSingleton(){
return Holder.singleton;
}
}
上面的所有实现方式,都有两个共同的缺点: 1. 都需要额外的工作
(Serializable,transient,readResolve())来实现序列化,否则每次
反序列化一个序列化的对象实例时都会创建一个新的实例 2. 可能会有人
使用反射强行调用我们的私有构造器 effective java推荐写法:枚举写法,
理由:线程安全,可以防止反射强行调用构造器,提供了自动序列化机制
,防止反序列化时候创建新的对象。
public enum Singleton{
INSTANCE;
private String name;
public String getName(){
return name;
}
public void setName(String name){
this.name = name;
}
}
以上是关于历史最全单例模式总结的主要内容,如果未能解决你的问题,请参考以下文章