有了一些小成绩就不求上进,这完全不符合我的性格。攀登上一个阶梯,这固然很好,只要还有力气,那就意味着必须再继续前进一步 。
每天进步一点,加油 ! 😄
单例模式,顾名思义就是只有一个实例,并且她自己负责创建自己的对象,这个类提供了一种访问其唯一的对象的方式,可以直接访问,不需要实例化该类的对象
1、懒汉模式
代码如下:
/**
* 懒汉模式
*/
public class LHan {
/**
* 定义一个私有的静态成员,存放在堆里
*/
private static LHan instance = null;
/**
* 私有构造方法
*/
private LHan() {
}
/**
* 获取实例
*
* @return 返回唯一实例化对象
*/
public static LHan GetInstance() {
//判断是否为空,如果为空就重新实例化负责对象
if (instance == null)
instance = new LHan();
return instance;
}
}
缺点: 线程不安全的
原因: 如果有多个线程调用 GetInstance()
的时候,会创建多个实例
代码如下:
/**
* 获取实例
*
* @return 返回唯一实例化对象
*/
public synchronized static LHan GetInstance() {
//判断是否为空,如果为空就重新实例化负责对象
if (instance == null)
instance = new LHan();
return instance;
}
缺点: 但不高效
原因: 因为任何时候只能有一个线程调用 GetInstance()
方法,但同步操作是只需要在第一次调用时才被需要,也就是说第一次创建实例对象的时候,也就是说当有多个线程时,同步操作对第 2
个线程始是不起作用的
解决方案: 双重检验锁
说明:
简单来说,就是一种使用同步块加锁的方法,称其为双重检验锁,是因为它有两次检查instance=null ,一次是在同步块外,一次是在同步块内,在同步块内检查就是为了防止多个线程一起进入同步块外的if里,从而造成多个实例的问题。
代码如下:
/**
* 获取实例
*
* @return 返回唯一实例化对象
*/
public static LHan GetInstance() {
if (instance == null)
synchronized (LHan.class) {
//判断是否为空,如果为空就重新实例化负责对象
if (instance == null) {
instance = new LHan();
}
}
return instance;
}
问题仍然存在: 在 instance = new LHan();
这里存在一个问题,这并非是一个原子操作,实际 JVM
进行了 3 件事也就是给 instance
分配内存、调用 LHan
的构造函数来初始化成员变量、将 instance
对象指向分配的内存空间(执行完这步后,instance
就为非 null
了)。但因为 JVM
的即时编译器中存在指令重排序的优化,所以第 2
和第 3
步的执行顺序是不能保证的。当 3
先执行完后,instance
已经是非空了,但没初始化 ,所以第二个线程将会直接返回 instance
,然后使用。因此存在多线程问题.
由于单线程中遵守 intra-thread semantics
,从而能保证即使 ② 和 ③ 交换顺序后其最终结果不变。但是当在多线程情况下,线程 B
将看到一个还没有被初始化的对象,此时将会出现问题。
解决方案:
1、不允许②和③进行重排序
2、允许②和③进行重排序,但排序之后,不允许其他线程看到。
代码如下:
/**
* 懒汉模式
*/
public class LHan {
/**
* 定义一个私有的静态成员,存放在堆里
*/
private volatile static LHan instance = null;
/**
* 私有构造方法
*/
private LHan() {
}
/**
* 获取实例
*
* @return 返回唯一实例化对象
*/
public static LHan GetInstance() {
if (instance == null)
synchronized (LHan.class) {
//判断是否为空,如果为空就重新实例化负责对象
if (instance == null) {
instance = new LHan();
}
}
return instance;
}
}
使用 volatile
修饰 instance
之后,之前的 ② 和 ③ 之间的 重排序 将在多线程环境下被禁止,从而保证了线程安全执行。
注意:
这个解决方案需要在 JDK 5
版本之上使用
因为从 JDK 5
开始用 JSR-133
内存模型规范,这个规范增强了 volatile
的语义
2、饿汉模式
代码如下:
/**
* 饿汉式
*/
public class EHan {
/**
* 定义一个私有的静态成员,存放在堆里
*/
private static final EHan instance = new EHan();
/**
* 私有构造方法
*/
private EHan() {
}
/**
* 获取实例
*
* @return 返回一个唯一实例对象
*/
public static EHan GetInstance() {
return instance;
}
}
说明:
单例的实例被声明了 static
和 final
变量 ,在类的第一次加载到内存中时就会初始化,所以创建实例本身是线程安全的。
但这不是 懒加载模式 ,因为会在加载类后就初始化,就算客户端没调用 GetInstance()
方法。
静态内部类:
/**
* 饿汉式
*/
public class EHan {
/**
* 在一个静态类里,定义一个私有的静态成员,存放在堆里
*/
private static class EHanHolder {
private static final EHan INSTANCE = new EHan();
}
/**
* 私有构造方法
*/
private EHan() {
}
/**
* 获取实例
*
* @return 返回一个唯一实例对象
*/
public static EHan GetInstance() {
return EHanHolder.INSTANCE;
}
}
枚举:
/**
* Enum 枚举实现单例模式
*/
public enum EasyEHan {
INSTANCE;
public void doSomething() {
System.out.println("啊哈哈哈");
}
}