单例模式
单例模式,顾名思义就是保证内存中只保留一份对象的实例,因为创建对象开销其实蛮大的,它在实际应用中非常广泛,包括著名的Spring框架等,单例模式有好几种写法,常用的有懒汉式单例、饿汉式单例,分别适用于不同的业务场景。
定义
保证一个类仅有一个实例,并提供一个访问它的全局访问点。
使用场景
1. 当类只能有一个实例而且客户可以从一个众所周知的访问点访问它时。
2. 当这个唯一的实例应该是通过子类化可扩展的,而且客户应该无需更改代码就能使用一个扩展的实例时。
结构
实现
饿汉式单例
public class Singleton1 {
private static Singleton1 singleton1 = new Singleton1();
private Singleton1(){}
/**
* 饿汉式单例
*/
public static Singleton1 getInstance(){
return singleton1;
}
}
懒汉式单例
public class Singleton {
private static Singleton singleton = null;
private Singleton(){}
/**
* 懒汉式单例
*/
public static Singleton getInstance(){
if(null==singleton){
singleton = new Singleton();
}
return singleton;
}
}
从上面的代码我们会发现,当在多线程环境中,如果两个线程同时要获取实例,则可能发生执行两次创建实例的过程,导致内存中有两份实例。因此我们可以加个锁来确保只有一份实例,改造如下:
public static synchronized Singleton getInstance(){
if(null==singleton){
singleton = new Singleton();
}
return singleton;
}
上述改造虽然可以实现单例的功能。但是每次调用getInstance方法都会给对象加锁,影响性能,而其实我们只需要在第一次创建的时候加锁就可以,所以我们再优化一下:
public static Singleton getInstance(){
if(null==singleton){
synchronized(singleton){
if(null==singleton){
singleton = new Singleton();
}
}
}
return singleton;
}
上述看似完美的解决了所有问题,但是有些问题却是始料未及的,其实JVM的创建对象的操作和赋值操作是分开的,所以singleton = new Singleton();这句话就会出问题,假如线程1进入synchronized块,并且判断singleton是null的,于是执行singleton = new Singleton();由于JVM内部的优化机制,JVM先划出了一些分配给Singleton实例的空白内存,并赋值给instance成员(注意此时JVM没有开始初始化这个实例),然后线程1离开了synchronized块。此时线程2进来发现instance不是null的,于是出去了,而当线程2调用的时候却没有初始化,于是就出错了。所以我们需要进一步完善程序:
private static class SingletonFactory{
private static Singleton instance = new Singleton();
}
public static Singleton getInstance(){
return SingletonFactory.instance;
}
单例模式使用内部类来维护单例的实现,JVM内部的机制能够保证当一个类被加载的时候,这个类的加载过程是线程互斥的。这样当我们第一次调用getInstance的时候,JVM能够帮我们保证instance只被创建一次,并且会保证把赋值给instance的内存初始化完毕。单例模式虽然理解起来很简单,但是具体实现起来还是要考虑到方方面面的问题,希望通过本文的学习,能少走点弯路。