懒汉式单例:
public class LazySingleton {
/** 懒汉模式的话,开始没有进行初始化 */
private static LazySingleton lazySingleton = null;
/** 构造器要进行私有化 */
private LazySingleton(){
}
public static LazySingleton getInstance() {
/** 这个是有线程安全的问题 */
if (lazySingleton == null) {
/**
* 如果一个线程进来了,但是这个时候,在new实例的时候,阻塞了或者还没有new出实例,
* 这个时候,另外一个线程判断lazySingleton依然是空的,那么就这时候,也进来了,
* 那么这个时候,就是有线程安全问题的
*/
lazySingleton = new LazySingleton();
}
return lazySingleton;
}
}
我们来测试一下:
public class Test {
public static void main(String[]args){
LazySingleton lazySingleton = LazySingleton.getInstance();
System.out.println(lazySingleton);
}
}
我们再来看看多线程的时候,会出现什么问题:
public class T implements Runnable{
@Override
public void run() {
LazySingleton lazySingleton = LazySingleton.getInstance();
System.out.println(Thread.currentThread().getName()+" "+lazySingleton);
}
}
测试代码如下:
public class Test {
public static void main(String[]args){
Thread t1 = new Thread(new T());
Thread t2 = new Thread(new T());
t1.start();
t2.start();
System.out.println("program end");
}
}
program end
Thread-1 com.ldc.design.pattern.creational.singleton.LazySingleton@5102a646
Thread-0 com.ldc.design.pattern.creational.singleton.LazySingleton@5102a646
模拟两个线程,一个线程在进入if之后,还没有new出实例, 这个时候,另外一个线程也进来了,if判断这个时候还没有实例,于是也进入了if里面,这个时候,就new出来两个实例:
我们接着向下执行:
Thread0和Thread1都放过:
虽然此时,两个对象还是同一个对象,但是是经过了修改了的:
我们再debug返回不同的对象:
我们先都两个线程进入if判断,然后一个线程直接执行完成,再另外一个线程执行完成,这个时候,返回的就是两个对象了:
我们对懒汉式单例模式的线程安全问题有几种解决方案:
方式一:在获取实例的方法上添加synchronized关键字
如果锁加载静态方法上 ,那么就相当于锁是加在这个类的class文件;如果不是静态方法相当于是在堆内存中生成的对象:
public class LazySingleton {
/** 懒汉模式的话,开始没有进行初始化 */
private static LazySingleton lazySingleton = null;
/** 构造器要进行私有化 */
private LazySingleton(){
}
public synchronized static LazySingleton getInstance() {
if (lazySingleton == null) {
lazySingleton = new LazySingleton();
}
return lazySingleton;
}
}
方式二:把获取实例的方法内添加synchronized代码块
public class LazySingleton {
/** 懒汉模式的话,开始没有进行初始化 */
private static LazySingleton lazySingleton = null;
/** 构造器要进行私有化 */
private LazySingleton(){
}
public static LazySingleton getInstance() {
synchronized (LazySingleton.class) {
if (lazySingleton == null) {
lazySingleton = new LazySingleton();
}
}
return lazySingleton;
}
}
我们再运行Thread1,发现运行不下去了,已经是阻塞的状态了:
最后拿的就是同一个对象:
但是,我们知道,加锁和解锁的时候,是会带来额外的开销,对性能会有一定的影响;我们再来进行演进,在性能和安全上进行平衡;
我们 可以这样来写:
public class LazyDoubleCheckSingleton {
/** 懒汉模式的话,开始没有进行初始化 */
private static LazyDoubleCheckSingleton lazyDoubleCheckSingleton = null;
/** 构造器要进行私有化 */
private LazyDoubleCheckSingleton(){
}
public static LazyDoubleCheckSingleton getInstance() {
if (lazyDoubleCheckSingleton == null) {
synchronized (LazyDoubleCheckSingleton.class) {
if (lazyDoubleCheckSingleton == null) {
lazyDoubleCheckSingleton = new LazyDoubleCheckSingleton();
}
}
}
return lazyDoubleCheckSingleton;
}
}
这个时候,会有一个风险:那就是发生了重排序:
public class LazyDoubleCheckSingleton {
/** 懒汉模式的话,开始没有进行初始化 */
private static LazyDoubleCheckSingleton lazyDoubleCheckSingleton = null;
/** 构造器要进行私有化 */
private LazyDoubleCheckSingleton(){
}
public static LazyDoubleCheckSingleton getInstance() {
/** 如果2和3进行重排序,那么这里的判断并不为空,这个时候,实际上对象还没有初始化好,就可以进行这个判断 */
if (lazyDoubleCheckSingleton == null) {
synchronized (LazyDoubleCheckSingleton.class) {
if (lazyDoubleCheckSingleton == null) {
/**
* 实际上有三个步骤:
* 1. 分配内存给这个对象
* 2. 初始化对象
* 3.设置lazyDoubleCheckSingleton指向刚分配的内存地址
* 2和3的顺序有可能会被颠倒,
*
* 这个时候,就规定所有的线程在执行java程序的时候,必须要遵守intra-thread semantics这么一个规定
* 它保证了重排序不会改变单线程内的程序执行结果
*/
lazyDoubleCheckSingleton = new LazyDoubleCheckSingleton();
}
}
}
return lazyDoubleCheckSingleton;
}
}
下图就是表示单线程的情况:规定所有的线程在执行java程序的时候,必须要遵守intra-thread semantics这么一个规定,它保证了重排序不会改变单线程内的程序执行结果。
我们可以尝试不允许重排序:
我们在初始化的时候,给它加上一个volatile关键字:这个时候,就可以实现线程安全的延迟初始化,这样的话,重排序就是会被禁止,在多线程的时候,CPU也有共享内存,我们加上了这个关键字了之后,所有线程就能看到共享内存的最新状态,保证了内存的可见性,使用volatile的时候,在进行写操作的时候,会多出一些汇编代码,起到两个作用1)将当前处理器缓存好的数据写回到系统内存中,其他内存从共享内存中同步数据,这样的话,就保证了共享内存的可见性,这里就是使用了缓存一致性的协议,当发现缓存内存中的数据无效,会重新从系统内存中把数据读回处理器的内存里;
public class LazyDoubleCheckSingleton {
/** 懒汉模式的话,开始没有进行初始化 */
private volatile static LazyDoubleCheckSingleton lazyDoubleCheckSingleton = null;
/** 构造器要进行私有化 */
private LazyDoubleCheckSingleton(){
}
public static LazyDoubleCheckSingleton getInstance() {
/** 如果2和3进行重排序,那么这里的判断并不为空,这个时候,实际上对象还没有初始化好,就可以进行这个判断 */
if (lazyDoubleCheckSingleton == null) {
synchronized (LazyDoubleCheckSingleton.class) {
if (lazyDoubleCheckSingleton == null) {
/**
* 实际上有三个步骤:
* 1. 分配内存给这个对象
* 2. 初始化对象
* 3.设置lazyDoubleCheckSingleton指向刚分配的内存地址
* 2和3的顺序有可能会被颠倒,
*
* 这个时候,就规定所有的线程在执行java程序的时候,必须要遵守intra-thread semantics这么一个规定
* 它保证了重排序不会改变单线程内的程序执行结果
*/
lazyDoubleCheckSingleton = new LazyDoubleCheckSingleton();
}
}
}
return lazyDoubleCheckSingleton;
}
}
通过volatile和doubleCheck的这种方式既兼顾了性能,又兼顾了线程安全的问题;
这个就是用静态内部类来实现单例模式:
public class StaticInnerClassSingleton {
private static class InnerClass {
private static StaticInnerClassSingleton staticInnerClassSingleton = new StaticInnerClassSingleton();
}
public static StaticInnerClassSingleton getInstance() {
return InnerClass.staticInnerClassSingleton;
}
private StaticInnerClassSingleton(){
}
}
在类加载的时候,就完成了实例化,避免了线程同步的问题,缺点就是在类加载的时候,就完成了初始化,没有延迟加载,这个时候,就是会造成内存的浪费
public class HungrySingleton {
private final static HungrySingleton hungrySingleton = new HungrySingleton();
private HungrySingleton() {
}
public static HungrySingleton getInstance() {
return hungrySingleton;
}
}
也可以这样来写:
public class HungrySingleton {
private final static HungrySingleton hungrySingleton;
static {
hungrySingleton = new HungrySingleton();
}
private HungrySingleton() {
}
public static HungrySingleton getInstance() {
return hungrySingleton;
}
}
饿汉式和懒汉式最大的区别就是在有没有延迟加载;
我们给这类实现一个序列化接口:
public class HungrySingleton implements Serializable {
private final static HungrySingleton hungrySingleton;
static {
hungrySingleton = new HungrySingleton();
}
private HungrySingleton() {
}
public static HungrySingleton getInstance() {
return hungrySingleton;
}
}
我们来测试:
public class Test {
public static void main(String[]args) throws IOException, ClassNotFoundException {
HungrySingleton instance = HungrySingleton.getInstance();
ObjectOutputStream oos = new ObjectOutputStream(new FileOutputStream("singleton_file"));
oos.writeObject(instance);
File file = new File("singleton_file");
ObjectInputStream ois = new ObjectInputStream(new FileInputStream(file));
HungrySingleton newInstance = (HungrySingleton) ois.readObject();
System.out.println(instance);
System.out.println(newInstance);
System.out.println(instance == newInstance);
}
}
输出结果:
com.ldc.design.pattern.creational.singleton.HungrySingleton@312b1dae
com.ldc.design.pattern.creational.singleton.HungrySingleton@443b7951
false
这个时候,通过序列化和反序列化拿了不同的对象;而我们希望拿到的是同一的对象,我们可以这样来做:
public class HungrySingleton implements Serializable {
private final static HungrySingleton hungrySingleton;
static {
hungrySingleton = new HungrySingleton();
}
private HungrySingleton() {
}
public static HungrySingleton getInstance() {
return hungrySingleton;
}
/** 我们加上这样的一个方法 */
private Object readResolve() {
return hungrySingleton;
}
}
我们再来进行测试,这个时候,两个对象就是同一个对象:
com.ldc.design.pattern.creational.singleton.HungrySingleton@312b1dae
com.ldc.design.pattern.creational.singleton.HungrySingleton@312b1dae
true
我们通过用debug的方式查看源码,我们可以看出这个方法是通过反射出来的:
一旦,我们在程序中使用了序列化的时候,一定要考虑序列化对单例破坏;
我们用反射来写:
public class Test {
public static void main(String[]args) throws IOException, ClassNotFoundException, NoSuchMethodException, IllegalAccessException, InvocationTargetException, InstantiationException {
Class objectClass = HungrySingleton.class;
Constructor constructor = objectClass.getDeclaredConstructor();
constructor.setAccessible(true);
HungrySingleton instance = HungrySingleton.getInstance();
HungrySingleton newInstance = (HungrySingleton) constructor.newInstance();
System.out.println(instance);
System.out.println(newInstance);
System.out.println(instance==newInstance);
}
}
测试结果:
com.ldc.design.pattern.creational.singleton.HungrySingleton@12edcd21
com.ldc.design.pattern.creational.singleton.HungrySingleton@34c45dca
false
现在,我们就来写反射防御的代码:
在用静态内部类来生成的也可以用上这个反射防御的方式:
public class StaticInnerClassSingleton {
private static class InnerClass {
private static StaticInnerClassSingleton staticInnerClassSingleton = new StaticInnerClassSingleton();
}
public static StaticInnerClassSingleton getInstance() {
return InnerClass.staticInnerClassSingleton;
}
private StaticInnerClassSingleton(){
if (InnerClass.staticInnerClassSingleton != null) {
throw new RuntimeException("单例构造器禁止反射调用");
}
}
}
public enum EnumInstance {
INSTANCE;
private Object data;
public Object getData() {
return data;
}
public void setData(Object data) {
this.data = data;
}
public static EnumInstance getInstance() {
return INSTANCE;
}
}
我们要调用方法的话,那么我们就是可以这样来写:
public enum EnumInstance {
INSTANCE {
@Override
protected void printTest() {
System.out.println("Geely Print Test");
}
};
protected abstract void printTest();
private Object data;
public Object getData() {
return data;
}
public void setData(Object data) {
this.data = data;
}
public static EnumInstance getInstance() {
return INSTANCE;
}
}
我们可以这样来写:
public class ContainerSingleton {
private static Map<String, Object> singletonMap = new HashMap<>();
public static void putInstance(String key,Object instance) {
if (StringUtils.isNotBlank(key) && instance!=null) {
if (!singletonMap.containsKey(key)) {
singletonMap.put(key, instance);
}
}
}
public static Object getInstance(String key) {
return singletonMap.get(key);
}
private ContainerSingleton() {
}
}
我们来测试一下:
public class Test {
public static void main(String[]args) throws IOException, ClassNotFoundException, NoSuchMethodException, IllegalAccessException, InvocationTargetException, InstantiationException {
Thread t1 = new Thread(new T());
Thread t2 = new Thread(new T());
t1.start();
t2.start();
System.out.println("program end");
}
}
如果map使用了HashTable,那么它就是线程安全的,但是这样的话,性能会有降低;