如何手写一个单例模式
什么是单例模式
单例模式,指的是在程序运行过程中,类只有一个对象实例。
为了实现单例,就不能允许程序员在使用这个类时,可以用new关键字来创建一个该类的对象。为了实现这一目的,我们可以将类的构造器设为私有,并提供一个方法来返回唯一的一个实例对象。
那么,这里引出了一个问题:该类的实例对象是在什么时候创建的呢?
一种思路是:在该类加载的时候就创建该类的实例对象,这种方式叫作饿汉式单例。
饿汉式单例可以的具体实现如下所示:
public class Singleton{
private static Singleton instance = new Singleton();
private Singleton(){}
public static Singleton getInstance(){
return instance;
}
}
另一种思路是:只有在第一次使用该类的实例化对象的时候,才去创建该类的实例对象,这种方式叫作懒汉式单例。
懒汉式单例的具体实现方式如下:
public class Singleton{
private static Singleton instance;
private Singleton(){}
public static Singleton getInstance(){
if(instance == null){
instance = new Singleton();
}
return instance;
}
}
有什么问题
以上代码在单线程的时候并不会产生问题,但是在多线程高并发情况下,类Singleton可能会有多个实例对象,这就破坏了单例。
我们用具体代码来演示这一种情况:
在这里,我们使用20个线程,每个线程都会调用类Singleton的getInstance()方法,打印获取的对象的哈希码。按照单例模式的定义,由于类Singleton只有一个实例化的对象,那么按理说每一次调用getInstance()方法得到的是同一个对象,打印的哈希码是一致的。
public class Test {
public static void main(String[] args) {
int len = 20;
Thread[] tn = new Thread[len];
for(int i = 0; i < len; i++){
tn[i] = new Thread(()->{
System.out.println(Singleton.getInstance());
});
tn[i].start();
}
}
}
然而,我们发现,上述代码的运行结果出现了不同的哈希码,说明创建出了不同的对象,这不符合单例模式的特点。
单例模式被破坏的原因是:当启动多线程后,假设线程A进入if(instance==null){...}之后,判断instance的值为null,之后线程A将会执行创建类Singleton的对象的语句。在线程A将要创建类Singleton的对象的时候,线程B也进入到if(instance == null){...},此时由于线程A还没创建类Singleton的对象,线程B判断instance的值为null,也执行了创建类Singleton的对象的语句。这样子,就会造成类Singleton有多个实例对象。
如何解决线程安全的问题
声明synchronized关键字
由于多线程下调用getInstance()方法会产生线程安全的问题,因此通过synchronized关键字加锁,就可以解决这个问题。这个方法的效率比较低
synchronized public static Singleton getInstance(){
if(instance == null){
instance = new Singleton();
}
return instance;
}
对代码块用synchronized关键字修饰,这个效率比用synchronized关键字修饰整个方法效率高一点
public static Singleton getInstance(){
synchronized(Singleton.class){
if(instance == null){
instance = new Singleton();
}
}
return instance;
}
使用DCL双检查锁机制
public class Singleton{
private volatile static Singleton instance;
private Singleton(){}
public static Singleton getInstance(){
if(instance == null){
synchronized(Singleton.class){
if(instance == null){
instance = new Singleton();
}
}
}
return instance;
}
}
使用静态内部类实现单例
public class Singleton{
private Singleton(){}
private static class SingletonHandler{
private static Singleton instance = new Singleton();
}
public static Singleton getInstance(){
return SingletonHandler.instance;
}
}
序列化和反序列化单例模式的实现
使用静态内部类可以达到线程安全,但是如果遇到序列化对象时候,使用默认的方式运行得到的结果还是多例的
问题:当一个单例序列化后再反序列化,序列化前对象的hashCode和反序列化后对象的hashCode不一样
解决办法:在反序列化中使用readResolve()方法
readResolve()需要定义在单例类中,但是不需要显式地调用,在反序列化过程中,JVM会自动调用;
注意,该方法是私有的
public class Singleton implements Serializable{
private static final long serialVersionUID = 888L;
//内部类方式
private static class SingletonHandler{
private static Singleton instance = new Singleton();
}
private Singleton(){}
public static Singleton getInstance(){
return SingletonHandler.instance;
}
private Object readResolve() throws ObjectStreamException{
return SingletonHandler.instance;
}
}