单例模式之七种不同实现方式
一、饿汉式
顾名思义,这是一个饿汉子,上来就将食物塞进去(实例化),这种方式最简单粗暴
实现方式
/**
* 饿汉式单例模式
*/
public final class HungrySingleton {
private static HungrySingleton instance = new HungrySingleton();
private HungrySingleton() {
System.out.println("HungrySingleton is created");
}
public static HungrySingleton getInstance() {
return instance;
}
}
测试代码
public static void testHungrySingleton() {
for (int i = 0; i < 1000; i++) {
new Thread(new Runnable() {
@Override
public void run() {
for (int i = 0; i < 10; i++) {
System.out.println(Thread.currentThread().getName());
HungrySingleton instance = HungrySingleton.getInstance();
System.out.println(instance);
}
}
}, "Thread" + i).start();
}
}
运行结果
HungrySingleton is created
二、懒汉式
懒汉式,这个方法非常懒,就像我上学时一样,作业不写,偏偏等到要上学的前几天才开始动手疯狂补作业。
/**
* 懒汉式单例模式
* 不要一开始就初始化实例,而是等到要用到的时候再去初始化实例
*/
public final class LazySingleton {
private static LazySingleton instance = null;
private LazySingleton() {
System.out.println("LazySingleton constructor");
}
public static LazySingleton getInstance() {
if (instance == null) {
instance = new LazySingleton();
}
return instance;
}
}
测试代码
/**
* 懒汉式单例模式
*/
public static void testLazySingleton() {
for (int i = 0; i < 1000; i++) {
new Thread(new Runnable() {
@Override
public void run() {
LazySingleton instance = LazySingleton.getInstance();
}
}, "Thread" + i).start();
}
}
运行结果
LazySingleton constructor
LazySingleton constructor
LazySingleton constructor
LazySingleton constructor
LazySingleton constructor
总结
上面代码在多线程情况下出现了多次实例化的情况,假如线程1正在做if (instance == null)
判断,此时CPU开始调度,线程2进入if (instance == null)
并且创建了一个实例,然后放弃CPU使用权,当线程1获取到CPU使用权之后就会从停止的地方继续开始执行未完成的代码,此时就会再一次实例化,出现多次实例化的情况。因此这种情况在多线程情况下并不能保证单例。
三、synchronized方式
为了解决懒汉式多线程情况下多次实例化的问题,简单粗暴的方式就是加锁,直接getInstance
方法上锁,保证仅一个线程同时访问此方法。
public class SynchronizedLazySingleton {
private static SynchronizedLazySingleton instance = null;
private SynchronizedLazySingleton() {
System.out.println("Synchronized Lazy Singleton");
}
public static synchronized SynchronizedLazySingleton getInstance() {
if (instance == null) {
instance = new SynchronizedLazySingleton();
}
return instance;
}
}
测试代码
public static void testSynchronizedLazySingleton() {
for (int i = 0; i < 1000; i++) {
new Thread(new Runnable() {
@Override
public void run() {
SynchronizedLazySingleton instance = SynchronizedLazySingleton.getInstance();
}
}, "Thread" + i).start();
}
}
运行结果
Synchronized Lazy Singleton
思考
上面加锁的粒度是不是太大了,多线程情况下同时仅有一个线程才能访问getInstance
方法了,有没有办法再优化一下呢,下面就是双重检查的实现方式了。
四、双重检查(double check)
在线程进入时判断一次,为空则加锁,进入之后再判断一次,至于为什么要再判断一次,这是为了防止两次实例化。假设线程1进入了第一个判断,此时释放了CPU;线程2进来之后,同样可以执行进入第一个判断并且加锁,然后实例化,最后释放CPU;然后等到线程1再次拿到CPU之后,从上一次未执行的地方开始,对DoubleCheckLazySingleton.class
加锁,由于没有第二个判断,此时线程1就可以完成第二次实例化了;因此,这就是为什么要加两次判断的原因。
/**
* 双重检查懒汉式单例模式
*/
public class DoubleCheckLazySingleton {
private static DoubleCheckLazySingleton instance = null;
private DoubleCheckLazySingleton() {
System.out.println("DoubleCheckLazySingleton is created");
}
public static DoubleCheckLazySingleton getInstance() {
if (instance == null) {
synchronized (DoubleCheckLazySingleton.class) {
if (instance == null) {
instance = new DoubleCheckLazySingleton();
}
}
}
return instance;
}
}
测试代码
public static void testDoubleCheckLazySingleton() {
for (int i = 0; i < 1000; i++) {
new Thread(new Runnable() {
@Override
public void run() {
DoubleCheckLazySingleton instance = DoubleCheckLazySingleton.getInstance();
}
}, "Thread" + i).start();
}
}
运行结果
DoubleCheckLazySingleton is created
五、双重检查+可见性(double check + volatile)
为什么要加volatile,因为我代码没有写完,如果此时单例类中还有其他的字段,就容易出现空指针异常,因为这些字段对于其它线程是不可见的,需要加上volatile保证对于其它线程也可见。
/**
* 双重检查懒汉式单例模式+volatile
*/
public class VolatileDoubleCheckLazySingleton {
private volatile static VolatileDoubleCheckLazySingleton instance = null;
private VolatileDoubleCheckLazySingleton() {
System.out.println("VolatileDoubleCheckLazySingleton construct");
}
public static VolatileDoubleCheckLazySingleton getInstance() {
if (instance == null) {
synchronized (VolatileDoubleCheckLazySingleton.class) {
if (instance == null) {
instance = new VolatileDoubleCheckLazySingleton();
}
}
}
return instance;
}
}
测试代码
/**
* 双重检查单例模式 + volatile
*/
public static void testVolatileDoubleCheckLazySingleton() {
for (int i = 0; i < 1000; i++) {
new Thread(new Runnable() {
@Override
public void run() {
VolatileDoubleCheckLazySingleton instance = VolatileDoubleCheckLazySingleton.getInstance();
}
}, "Thread" + i).start();
}
}
运行结果
VolatileDoubleCheckLazySingleton construct
六、holder内部类实例化
借助于内部类来实例化单例,秀不秀?
public class HolderSingleton {
private static HolderSingleton instance;
private HolderSingleton() {
System.out.println("HolderSingleton constructor");
}
private static class Holder {
private static final HolderSingleton INSTANCE = new HolderSingleton();
}
public static HolderSingleton getInstance() {
return Holder.INSTANCE;
}
}
测试代码
/**
* Holder
*/
public static void testHolderSingleton() {
for (int i = 0; i < 1000; i++) {
new Thread(new Runnable() {
@Override
public void run() {
HolderSingleton instance = HolderSingleton.getInstance();
}
}, "Thread" + i).start();
}
}
运行结果
HolderSingleton constructor
七、枚举类实现方式
王炸!枚举类天然的定义就是单例,这没的说。
public enum EnumSingleton {
INSTANCE,
;
private EnumSingleton() {
System.out.println("INSTANCE will be initialized immediately");
}
public static void method() {
}
public static EnumSingleton getInstance() {
return INSTANCE;
}
}
测试代码
/**
* EnumSingleton
*/
public static void testEnumSingleton() {
for (int i = 0; i < 1000; i++) {
new Thread(new Runnable() {
@Override
public void run() {
EnumSingleton instance = EnumSingleton.getInstance();
}
}, "Thread" + i).start();
}
}
运行结果
INSTANCE will be initialized immediately