Java设计模式——单例模式
1、饿汉模式
public class Singleton {
private static Singleton instance = new Singleton();
private Singleton() {
}
public static Singleton newInstance() {
return instance;
}
}
将对象构建放在了static语句中,JVM加载类的时候会执行静态语句和静态代码块,而且虚拟机保证类只被初始化一次,所以类的初始化是单线程执行的,所以这种是线程安全的,缺点就是初始化太早,可能造成空间浪费。
2、懒汉模式,延迟加载
public class Singleton {
private static Singleton instance;
private Singleton() {
}
public static Singleton newInstance() {
if (instance == null) {
instance = new Singleton();
}
return instance;
}
}
在加载类的时候不会进行实例化,在获取实例的话,如果为空,则进行实例化,单线程不会出问题,多线程可能会创建多个实例,造成不是单例,可以进行改进:
public class Singleton {
private static Singleton instance;
private Singleton() {
}
public static synchronized Singleton newInstance() {
if (instance == null) {
instance = new Singleton();
}
return instance;
}
}
使用synchronized
控制线程同步访问
3、双重校验锁
上述模式实现了延迟加载和线程安全,但是由于使用synchronized
会造成性能下降,因为同时只有一个进程可以调用newInstance()
方法,采用双重校验锁可以提高性能
public class Singleton {
private static Singleton instance;
private Singleton() {
}
public static Singleton newInstance() {
if (instance == null) {
synchronized (Singleton.class) {
if (instance == null) {
instance = new Singleton();
}
}
}
return instance;
}
}
synchronized
并没有加在方法上,一般情况下,当俩个线程执行newInstance()
方法,判断instance
都为null
时,就会创建俩个不同的实例,所以为了解决这个问题,所以在同步代码块中还得加入验证instance == null
代码。
这里还有一个问题,由于Java的指令重排优化的存在,instance = new Singleton()
语句不具有原子性,他是分几步执行的:
1.给对象分配内存空间
2.在内存空间创建对象
3.将对象引用赋值给instance
2必须在1之后执行,但是3和2之间没有依赖关系,程序执行的时候顺序可以是1->2->3,也可能是1->3->2
如果是单线程程序,无论执行的顺序是什么,使用instances
的时候,虚拟机可以保证它是初始化完成的
如果是多线程程序,一个线程在执行instance = new Singleton()
的时候,其他的线程是要执行第一个if (instance == null)
语句的,因为这个语句在同步锁的外面,这个时候就涉及到执行顺序:
如果按1->2->3执行,在执行3之前,instance
的引用一直都是null
,如果这时候第二个线程执行newInstance()
方法,就会一直在同步代码块外面等待,直到同步代码块执行完3后,第二个线程进入,判断instance
不为空,然后执行return语句,返回的是第一个线程初始化的对象,这是正确的。
如果按1->3->2执行,执行完3但是还没有执行2的时候,第二个线程执行newInstance()
方法,由于现在的instance
已经有了引用对象,不是null
,所有第二个线程不会在同步代码块外面等待,会直接执行return语句,此时返回的instance
还没有完成初始化,是错误的对象。
上面的问题就是因为JVM的自动优化操作,可以使用volatile
禁止指令重排优化,也就是保证先分配空间,然后赋值引用,代码如下:
public class Singleton {
private static volatile Singleton instance;
private Singleton() {
}
public static Singleton newInstance() {
if (instance == null) {
synchronized (Singleton.class) {
if (instance == null) {
instance = new Singleton();
}
}
}
return instance;
}
}
4、静态内部类
public class Singleton {
private static class SingletonHolder {
public static Singleton instance = new Singleton();
}
private Singleton() {}
public static Singleton newInstance() {
return SingletonHolder.instance;
}
}
不会在一开始就加载静态内部类SingletonHolder
,其他地方使用SingletonHolder
的时候才会加载,比如第一次调用newInstance()
方法,从而实现了延迟加载。和饿汉模式一样,也利用了类加载机制,所以可以保证线程安全。
5、枚举
public enum Singleton {
INSTANCE;
}