单例模式涉及到单一的一个类,该类负责创建自己的对象,同时保证只有单个对象被创建。提供了唯一的访问对象的方式。其对象可以直接访问不需要通过实例化。
单例模式的结构
- 单例类:只能创建一个实例的类
- 访问类:使用单例类
单例模式的两种实现方式:
饿汉式:类加载时创建对象
懒汉式:只有类被首次使用才会创建对象
饿汉式
1. 静态成员变量
/**
* 单例模式:饿汉式
* 静态成员变量
*/
public class SingleTon {
// 构造方法私有:不允许外部直接创建本类对象
private SingleTon() {
}
// 在本类创建本类的对象
private static SingleTon singleTon = new SingleTon();
// 提供公共的访问方式,让外界获取该对象
public static SingleTon getInstance(){
return singleTon;
}
}
2. 静态代码块
/**
* 饿汉式:静态代码块
*/
public class SingleTon1 {
// 私有构造函数
private SingleTon1(){
}
// 声明SingleTon1的静态对象
private static SingleTon1 instance;
static{
// 在静态代码块中赋值
instance = new SingleTon1();
}
// 对外部提供访问本类对象的方法
public static SingleTon1 getInstance(){
return instance;
}
}
饿汉式存在问题:当前类如果没有使用会一直存在造成资源浪费
懒汉式
/**
* 懒汉式:首次使用当前类时才创建对象
*/
public class SingleTonLazy {
// 私有构造函数
private SingleTonLazy(){
}
// 声明当前类型的变量 (并未赋值)
private static SingleTonLazy instance;
// 提供一个对外能访问到该类的方法
public static SingleTonLazy getInstance(){
// 使用时才创建:判断instance是否为null,为null说明还没有创建对象
if(instance == null){
// 存在线程安全问题
instance = new SingleTonLazy();
}
return instance;
}
}
存在问题:存在线程安全问题。当线程1进入if判断后失去cpu执行权,会导致其他线程也进入if判断创建对象,此时会创建两个不同的对象。
改进1:synchronized同步锁
/**
* 饿汉式:线程安全
*/
public class SingleTonLazySafe {
private SingleTonLazySafe(){
}
private static SingleTonLazySafe instance;
// 添加同步锁保证线程安全
public synchronized static SingleTonLazySafe getInstance(){
if(instance == null){
instance = new SingleTonLazySafe();
}
return instance;
}
}
改进2:双重检查锁
对于getInstance()方法来说,直接返回instance对象属于读操作,而进入判断的赋值属于写操作,我们对于所有的读操作都是安全的,没有必要加锁影响其操作性能。因此可以调整加所的时机,从而引出了双重锁的概念。
/**
* 饿汉式:双重锁判断
*/
public class SingleTonLazyDoubleLock {
private SingleTonLazyDoubleLock(){
}
// 添加volatile能保证多线程情况下线程安全也不会有性能问题:防止指令重排序,保证有序性
private static volatile SingleTonLazyDoubleLock instance;
public static SingleTonLazyDoubleLock getInstance(){
// 第一次判断是否为写操作,读操作直接返回
if(instance == null){
synchronized(SingleTonLazyDoubleLock.class){
// 抢到锁后再次判断是否为null
if (instance == null){
instance = new SingleTonLazyDoubleLock();
}
}
}
return instance;
}
}
改进3:静态内部类方式
静态内部类单例模式中实例由内部类创建,由于JVM在加载外部类的过程中,不会加载静态内部类,只有内部类的属性/方法被调用时才会被加载。静态属性由于static修饰所以只会被实例化一次
/**
* 静态内部类实现懒汉式
*/
public class SingleTonLazy3 {
private SingleTonLazy3(){
}
// 静态内部类
private static class SingleHolder{
private static final SingleTonLazy3 INSTANCE = new SingleTonLazy3();
}
// 对外提供访问的方法
public static SingleTonLazy3 getInstance(){
return SingleHolder.INSTANCE;
}
}
小结:静态内部类是一种没有加任何锁的情况下保证了多线程的安全,并且没有任何性能影响和空间浪费