继续上一篇 饿汉式单例模式
为了解决饿汉式单例模式,带来的内存占用问题 ,于是就出现了懒汉式单例的写法,懒汉式单例模式的特点是,单例对象要在被使用时才会初始化,下面看懒汉式单例模式的简单实现。
懒汉单例,在测试时可以将断点打在创建懒汉单例处。
public class LazySimpleSingleton {
private static LazySimpleSingleton lazySimpleSingleton = null;
private LazySimpleSingleton() {};
//懒汉式单例,使用时才实例化
//但是存在线程安全问题,多个线程同时调用可能出现两个实例
public static LazySimpleSingleton getInstance() {
if(lazySimpleSingleton == null) {
lazySimpleSingleton = new LazySimpleSingleton();//断点位置模拟CUP执行顺利
}
return lazySimpleSingleton;
}
}
线程类,创建多线程,模拟线程安全问题。
public class MyThread implements Runnable{
public void run() {
LazySimpleSingleton lazy = LazySimpleSingleton.getInstance();
System.out.println("lazy:" + lazy);
}
}
测试类,打印输出结果
public class LazyTest {
public static void main(String[] args) {
Thread one = new Thread(new MyThread());
Thread two = new Thread(new MyThread());
one.start();
two.start();
System.out.println("OK");
}
}
控制台,创建了两个实例,破坏了单例模式。
通过多线程方式,我们发现在线程环境下LazysimpleSingleton被实例化了两次,也就说这种懒汉式存在线程安全隐患。那么,我们如何来优化代码,使得懒汉式单例模式在线程环境下安全呢?来看下面的代码给getlnstance()加上synchronized关键字,使这个方法变成线程同步方法∶
public class LazySyncSimpleSingleton {
private static LazySyncSimpleSingleton lazySimpleSingleton = null;
private LazySyncSimpleSingleton() {};
//懒汉式单例,使用时才实例化
//利用加锁机制,一次只允许一个线程访问,但是会造成任务排队现象
public static synchronized LazySyncSimpleSingleton getInstance() {
if(lazySimpleSingleton == null) {
lazySimpleSingleton = new LazySyncSimpleSingleton();
}
return lazySimpleSingleton;
}
}
上图通synchronized加锁在整个方法,线程安全的问题解决了。但是,用synchronized加锁时,在线程数量比较多的情况下,如果CPU分配压力上升,则会导致大批线程阻塞,从而导致程序性能大幅下降。那么,有没有一种更好的方式,既能兼顾线程安全又能提升程序性能呢?答案是肯定的。我们来看双重检查锁的单例模式∶
public class LazyDoubleCheckSingleton {
private static LazyDoubleCheckSingleton lazySimpleSingleton = null;
private LazyDoubleCheckSingleton() {};
//双重锁机制
//如果存在则直接反回,如果不存在则加锁,并进行二次判断是否存在
public static LazyDoubleCheckSingleton getInstance() {
if(lazySimpleSingleton == null) {
synchronized(LazyDoubleCheckSingleton.class) {
if(lazySimpleSingleton == null) {
lazySimpleSingleton = new LazyDoubleCheckSingleton();
}
}
}
return lazySimpleSingleton;
}
}
但是,用到synchronized关键字总归要上锁,对程序性能还是存在一定影响的。有没有更好的方式,对内存与性能都有优化呢?当然有,我们可以从类初始化的角度来考虑,看下面的代码,采用静态内部类的方式:
public class LazyInnerClassSingleton {
private LazyInnerClassSingleton() {};
// 懒汉式单例,静态内部类
// 如果方法没有使用,内部类不会加载
public static final LazyInnerClassSingleton getInstance() {
// 返回结果时,先加载内部类
return MyInnerClass.sing;
}
// 默认不加载 ,只创建一次
private static class MyInnerClass {
private static final LazyInnerClassSingleton sing = new LazyInnerClassSingleton();
}
}
这种方式兼顾了饿汉式单例模式的内存浪费问题和synchronized的性能问题。内部类一定是要在方法调用之前初始化,巧妙地避免了线程安全问题。