概述
单例模式的英文原话是:Ensure a class has only one instance,and provide a global point of access to it
。意思就是确保一个类只有一个实例存在,并提供一个全局访问点。正是因为单例模式有这样的特性,单例模式可以用在建立目录、数据库连接等需要单线程操作的场合,用于实现对系统资源的控制。
懒汉式单例类
特点:在第一次调用的时候实例化自己。
//在第一次调用的时候实例化自己
public class Singleton {
//1.私有构造器
private Singleton() {}
//2.静态变量
private static Singleton singleton = null;
//3.静态方法
public static Singleton getInstance() {
if (Objects.isNull(singleton)) {
singleton = new Singleton();
}
return singleton;
}
}
缺点:线程不安全,并发环境下可能出现多个实例。
解决方案:
在静态方法上面加同步锁
特点:解决线程不安全问题,保证在并发环境下只会出现一个实例。
缺点:只有第一次执行该静态方法时,才真正需要同步。而实际上,每次执行该方法时,都会进行同步,会影响性能,毕竟99%的情况下是不需要同步的。
//在第一次调用的时候实例化自己
public class Singleton {
//1.私有构造器
private Singleton() {}
//2.静态变量
private static Singleton singleton = null;
//3.静态方法
//synchronize同步锁
public static synchronized Singleton getInstance() {
if (Objects.isNull(singleton)) {
singleton = new Singleton();
}
return singleton;
}
}
双重检查加锁
特点:保证了当静态变量被初始化成具体实例时,多个线程能正确地处理静态变量。
缺点:双重检查加锁不适用于Java 4或更早版本的Java。
//在第一次调用的时候实例化自己
public class Singleton {
// 1.私有构造器
private Singleton() {}
// 2.静态变量
private static volatile Singleton singleton = null;
// 3.静态方法
public static Singleton getInstance() {
// 双重检查加锁
if (Objects.isNull(singleton)) {
synchronized (Singleton.class) {
if (Objects.isNull(singleton)) {
singleton = new Singleton();
}
}
}
return singleton;
}
}
注:volatile关键字是为了确保当静态变量被初始化成具体实例时,多个线程正确地处理静态变量。
静态内部类
public class Singleton {
//1.私有构造方法
private Singleton() {}
//2.静态内部类
private static class LazyHolder {
private static final Singleton INSTANCE = new Singleton();
}
//3.静态方法
public static Singleton getInstance() {
return LazyHolder.INSTANCE;
}
}
饿汉式单例
特点:饿汉式在类创建的同时就已经创建好一个静态的对象供系统使用,以后不再改变,所以天生是线程安全的。
// 在类初始化时,已经自行实例化
public class Singleton {
// 1.私有构造
private Singleton() {}
// 2.静态变量
private static final Singleton single = new Singleton();
// 3.静态方法
public static Singleton getInstance() {
return single;
}
}
总结
- 单例模式能够确保在程序中一个类最多只有一个实例,并提供一个唯一的全局访问点
- 由于构造函数是私有的,所以单例类不能被继承
- 在Java中实现单例模式时需要
私有的构造器
、一个静态方法
、一个静态变量
- Java 5以下版本时,双重检查加锁实现会失效
- 使用多个类加载器时,单例会失效而产生多个实例
- 使用
JVM1.2
或之前的版本,必须建立单例注册表,以免垃圾收集器将单例回收 - 确定在性能和资源上的限制,然后小心地选择适当的方案来实现单例,以解决多线程的问题