目录
一、饿汉式单例模式
1. 描述
饿汉式单例模式,在类加载的时候就立即初始化,并且创建单例对象。
它绝对线程安全,在其他线程还没出现之前,就已经完成了实例化,不可能存在访问安全问题。
2. 优缺点
优点:没有任何加锁,执行效率比较高;
缺点:类加载的时候就初始化,不管用或者不用,都占着资源。
3. Java实现
/**
* 单例模式:懒汉
*/
public class HungrySingleton {
// 方式一
// private static final HungrySingleton INSTANCE;
//
// static {
// INSTANCE = new HungrySingleton();
// }
// 方式二
private static final HungrySingleton INSTANCE = new HungrySingleton();
private HungrySingleton() {
}
public static HungrySingleton getInstance() {
return INSTANCE;
}
}
饿汉式单例模式,比较适合单例对象较少的情况。
二、懒汉式单例模式
1. 描述
区别于“饿汉”类初始化就创建实例,懒汉式单例模式只在对象第一次使用的时候创建示例,之后都用这个实例,如果没有任何机会使用的话,不会创建对象。
2. Java实现一
/**
* 单例模式:饿汉-不考虑线程安全
*/
public class LazySimpleSingleton {
private LazySimpleSingleton() {
}
private static LazySimpleSingleton instance;
public static LazySimpleSingleton getInstance() {
if (instance == null) {
instance = new LazySimpleSingleton();
}
return instance;
}
}
3. 懒汉式单例模式实现一存在的问题
我们写如下测试代码测试一下:
package com.zhang.pattern;
import com.zhang.pattern.singleton.LazySimpleSingleton;
import org.junit.Test;
public class LazySimpleSingletonTest {
@Test
public void testLazySimpleSingleton() {
Thread thread1 = new Thread(() -> System.out.println(Thread.currentThread().getName() + ": " + LazySimpleSingleton.getInstance()));
Thread thread2 = new Thread(() -> System.out.println(Thread.currentThread().getName() + ": " + LazySimpleSingleton.getInstance()));
thread1.start();
thread2.start();
try {
thread1.join();
thread2.join();
} catch (InterruptedException e) {
System.out.println("caught InterruptedException, ignore it");
}
}
}
程序可能输出以下结果:
Thread-0: com.zhang.pattern.singleton.LazySimpleSingleton@5e8c92f4
Thread-1: com.zhang.pattern.singleton.LazySimpleSingleton@5e8c92f4
也可能输出这种结果:
Thread-1: com.zhang.pattern.singleton.LazySimpleSingleton@29441bf6
Thread-0: com.zhang.pattern.singleton.LazySimpleSingleton@63a1a1b3
结果二就是多线程模式下可能会发生的一种情况,返回的不是同一个对象。
不会多线程调试的同学可以看下这篇文章:IDEA多线程调试单例模式,限于篇幅,这里不多讲了。
4. 加锁解决线程安全问题
想解决线程安全问题,加个锁就好了呗,看看示例代码:
package com.zhang.pattern.singleton;
public class LazySynchronizedSingleton {
private LazySynchronizedSingleton() {
}
private static LazySynchronizedSingleton instance = null;
public static synchronized LazySynchronizedSingleton getInstance() {
if (instance == null) {
instance = new LazySynchronizedSingleton();
}
return instance;
}
}
可以按照上面的测试方法再调试一把,发现不会有线程安全的问题了。
5. 双检测加锁
上面的方式,每次进入getInstance方法,都需要先获得锁,如果线程比较多的话,即使现在实例已经存在了(不会再有重复创建实例的问题了),也需要等,没办法直接返回。
我们可以通过减小锁的粒度,来让程序更加的高效,看看示例代码:
package com.zhang.pattern.singleton;
public class LazyDoubleCheckLockSingleton {
private static LazyDoubleCheckLockSingleton instance = null;
private LazyDoubleCheckLockSingleton() {
}
public static LazyDoubleCheckLockSingleton getInstance() {
if (instance == null) {
synchronized (LazyDoubleCheckLockSingleton.class) {
if (instance == null) {
instance = new LazyDoubleCheckLockSingleton();
}
}
}
return instance;
}
}
第一层if的作用是判断实例是否已经存在;
synchronized加锁保证线程安全;
第二场if的作用是保证只创建一个实例。假如首次调用getInstance方法的线程比较多,在实例还没有创建的情况下,会有很多线程等待锁,等前面线程创建了实例释放锁之后,就会有很多其他等待在这把锁的线程去获取锁,如果内层不加限制,会多次创建实例。
6. 使用静态内部类创建实例
用到synchronized关键字,总归是要上锁,我们从类初始化的角度考虑,可以采用静态内部类实现单例,以下是实现方式:
package com.zhang.pattern.singleton;
public class LazyInnerSingleton {
private LazyInnerSingleton() {
}
public static final LazyInnerSingleton getInstance() {
return LazyHolder.INSTANCE;
}
private static class LazyHolder {
private static final LazyInnerSingleton INSTANCE = new LazyInnerSingleton();
}
}
内部类会在方法调用前初始化,巧妙的避免了线程安全问题。
三、 注册式单例模式
1. Spring中容器式单例模式
使用类名作为key,集中管理所有单例:
package com.zhang.pattern.singleton;
import java.lang.reflect.Constructor;
import java.lang.reflect.InvocationTargetException;
import java.util.Map;
import java.util.concurrent.ConcurrentHashMap;
public class ContainerSingleton {
private ContainerSingleton() {
}
private static final Map<String, Object> ioc = new ConcurrentHashMap<>();
@SuppressWarnings("all")
public static Object getBean(String className) {
synchronized (ioc) {
if (!ioc.containsKey(className)) {
Object obj = null;
try {
Constructor<?> c = Class.forName(className).getDeclaredConstructor(null);
obj = c.newInstance();
ioc.put(className, obj);
} catch (ClassNotFoundException |
NoSuchMethodException |
InstantiationException |
IllegalAccessException |
InvocationTargetException exception) {
exception.printStackTrace();
}
return obj;
}
return ioc.get(className);
}
}
}
四、单例模式小结
单例模式可以保证内存中只有一个实例,减小了内存的开销,还可以避免对资源的多重占用。