singleton
英 [ˈsɪŋɡəltən] 美 [ˈsɪŋgəltən]
n. 一个,独生子,独身
意图:保证一个类仅有一个实例,并提供一个访问它的全局访问点。
主要解决:一个全局使用的类频繁地创建与销毁。
何时使用:当您想控制实例数目,节省系统资源的时候。
常用是实现方式有以下几种:
- 1.懒汉模式:类加载时不做初始化;线程不安全;
public class MySingleton {
private MySingleton(){}
private static MySingleton instance;
public static MySingleton getInstance(){
if(null == instance){
instance = new MySingleton();
}
return instance;
}
}
- 2.懒汉模式改进版:用synchronized关键字确保线程安全
//缺点:synchronized关键字影响开销,而且getInstance方法实际只需要第一次调用时候需要保证线程独占,但是每次都会加锁;
public class MySingleton {
private MySingleton() {
}
private static MySingleton instance;
public synchronized static MySingleton getInstance() {
if (null == instance) {
instance = new MySingleton();
}
return instance;
}
}
- 3.懒汉模式改进版2:用双重检查锁+volatile
public class MySingleton {
private MySingleton() {
}
private volatile static MySingleton instance;
public static MySingleton getInstance() {
if (null == instance) {//...................................①
//至此都没有加锁,确保第2次以及以后调用该方法时,都直接返回不会阻塞其他线程
//初始化的时候会出现加锁的情况,后续的所有调用都会避免加锁而直接返回,解决了性能消耗的问题
synchronized (MySingleton.class) {
if (null == instance){
instance = new MySingleton();//.................②
// instance = new MySingleton();可以拆解为3个步骤:
// 1.分配内存空间
// 2.初始化对象
// 3.将句柄指向分配的内存空间
// 在多线程环境下JVM编译器可能为了优化,进行重排序,如果不加volatile禁止重排序,可能出现下面情况:
// A线程执行到步骤②,按照132顺序执行,且刚刚完成3,没有到2;
// B线程执行到步骤①,判断为false,得到了一个没有初始化的句柄。
}
}
}
return instance;
}
}
- 4.饿汉模式:类加载时初始化,加载时间长,如果不用对象造成浪费;线程安全;
public class MySingleton {
private MySingleton() {
}
private static MySingleton instance = new MySingleton();
public MySingleton getInstance() {
return instance;
}
}
- 5.静态内部类模式
//只有第一次调用getInstance方法时,虚拟机才加载 Inner 并初始化instance ,
// 只有一个线程可以获得对象的初始化锁,其他线程无法进行初始化,保证对象的唯一性。
public class MySingleton{
private MySingleton(){}
public static MySingleton getInstance(){
return Inner.instance;
}
private static class Inner{
private final static MySingleton instance = new MySingleton();
}
}
凭借对象的private构造器来实现的单例模式有2个问题:
1.反射攻击:享有特权的客户端可以借助AccessibleObject.setAccessible方法,通过反射机制调用私有构造器。让它在被要求创建第二个实例的时候抛出异常。
2.反序列化问题:任何一个readObject方法,不管是显式的还是默认的,它都会返回一个新建的实例,这个新建的实例不同于该类初始化时创建的实例。因此:推荐用枚举方式实现单例模式:
- 6.枚举方式实现单例模式
public class User {
//私有化构造函数
private User(){ }
//定义一个静态枚举类
static enum SingletonEnum{
//创建一个枚举对象,该对象天生为单例
INSTANCE;
private User user;
//私有化枚举的构造函数
private SingletonEnum(){
user=new User();
}
public User getInstnce(){
return user;
}
}
//对外暴露一个获取User对象的静态方法
public static User getInstance(){
return SingletonEnum.INSTANCE.getInstnce();
}
}
public class Test {
public static void main(String [] args){
System.out.println(User.getInstance());
System.out.println(User.getInstance());
System.out.println(User.getInstance()==User.getInstance());
}
}
//结果为true