单例模式(Singleton Pattern):在应用中保证一个类只有一个实例,并且提供了该该实例的全局访问点
主要解决一个全局使用的类频繁的创建与销毁
单例模式分为懒汉式和饿汉式
懒汉式:每次获取实例都会判断是否需要创建实例,浪费时间。当没有人使用时则不会创建实例。以时间换空间。懒汉模式在不加同步锁时是线程不安全的
饿汉式:在类初始化的时候就会创建实例,每次获取时不进行判断。以空间换时间。是线程安全的
单例模式的实现
懒汉模式
public class LazySingleton {
private static LazySingleton instance;
private LazySingleton(){}
public static LazySingleton getInstance() {
if (instance == null) {
instance = new LazySingleton();
}
return instance;
}
}
public static void main(String[] args) throws InterruptedException {
LazySingleton instance1 = LazySingleton.getInstance();
LazySingleton instance2 = LazySingleton.getInstance();
System.out.println(instance1==instance2); // 输出true
}
当多个线程同时访问时可能会返回多个实例
public static LazySingleton getInstance() throws InterruptedException {
if (instance == null) {
Thread.sleep(8000);
instance = new LazySingleton();
}
return instance;
}
public static void main(String[] args) {
new Thread(() -> {
try {
LazySingleton instance = LazySingleton.getInstance();
System.out.println(instance); // 输出cn.pattern.LazySingleton@674827d5
} catch (InterruptedException e) {
e.printStackTrace();
}
}).start();
new Thread(() -> {
try {
LazySingleton instance = LazySingleton.getInstance();
System.out.println(instance); // 输出cn.pattern.LazySingleton@6e5802d8
} catch (InterruptedException e) {
e.printStackTrace();
}
}).start();
}
可以添加同步锁解决,同步锁只允许一个线程通过,其他线程会被阻塞,造成一定性能损耗
public static LazySingleton getInstance() {
if (instance == null) {
synchronized(LazySingleton.class) {
if (instance == null) {
instance = new LazySingleton();
}
}
}
return instance;
}
在字节码的层面上看简单new一个对象对应的指令如下
0: new #2 // class cn/pattern/T
3: dup
4: invokespecial #3 // Method cn/pattern/T."<init>":()V
7: astore_1
8: return
new: 会在堆内存中分配一块空间,并将该空间的引用压入操作数栈的栈顶
dup:将栈顶的引用复制一份
invokespecial:调用初始化方法进行初始化,此时操作数栈顶的引用会出栈,作为this参数传递给构造器
astore_1:把栈顶的引用出栈赋值到变量
return:终止该方法执行
有一些指令调换执行顺序并不会产生影响,如invokespecial和astore_1,为了提升性能JIT,CPU可能会对指令进行重排,可能会先执行astore_1再执行invokespecial。
指令重排后当一个线程执行astore_1后另一个线程进入发现已经有引用就会直接返回,可能会造成空指针异常
所以加上volatile关键字阻止指令重排,防止出现上述情况
public class LazySingleton {
private volatile static LazySingleton instance;
private LazySingleton() throws Exception {
if (instance != null) {
throw new Exception("已存在实例");
}
}
public static LazySingleton getInstance() throws Exception {
if (instance == null) {
synchronized(LazySingleton.class) {
if (instance == null) {
instance = new LazySingleton();
}
}
}
return instance;
}
}
饿汉模式
public class HungerSingleton {
private static HungerSingleton instance = new HungerSingleton();
private HungerSingleton(){}
public static HungerSingleton getInstance() {
return instance;
}
}
使用static通过jvm的类加载机制保证类的唯一性。
当该类还有别的静态方法instance会在别的静态方法被调用时就初始化,可以通过内部类的方式解决(懒汉模式)
public class InnerSingleton {
private InnerSingleton(){}
public static InnerSingleton getInstance() {
return Singleton.instance;
}
private static class Singleton {
public static InnerSingleton instance = new InnerSingleton();
}
}
虽然构造器被定义成了private,但是通过反射的方式还是可以访问
public static void main(String[] args) throws ClassNotFoundException,NoSuchMethodException, IllegalAccessException, InvocationTargetException, InstantiationException {
Class cls = Class.forName("cn.pattern.LazySingleton");
Constructor constructor = cls.getDeclaredConstructor();
constructor.setAccessible(true);
LazySingleton lazySingleton = (LazySingleton) constructor.newInstance();
System.out.println(lazySingleton); // 输出cn.pattern.LazySingleton@1b6d3586
}
而且通过RPC调用服务,序列号传参还是会生产新的对象,我们实现序列化接口测试一下
public class InnerSingleton implements Serializable {
private static final long serialVersionUID = 1L;
private InnerSingleton() {
if (Singleton.instance != null) {
throw new RuntimeException("单例对象已存在");
}
}
public static InnerSingleton getInstance() {
return Singleton.instance;
}
private static class Singleton {
public static InnerSingleton instance = new InnerSingleton();
}
}
public static void main(String[] args) {
InnerSingleton instance = InnerSingleton.getInstance();
// 序列化
// try(ObjectOutputStream oot = new ObjectOutputStream(new FileOutputStream("SerializableTest"))) {
// oot.writeObject(instance);
// }catch (Exception e) {
// e.printStackTrace();
// }
// 反序列化
try(ObjectInputStream oi = new ObjectInputStream(new FileInputStream("SerializableTest"))) {
InnerSingleton innerSingleton = (InnerSingleton) oi.readObject();
System.out.println(instance==innerSingleton); // 输出false
}catch (Exception e){
e.printStackTrace();
}
}
可以添加readObject()方法来解决反序列化问题
public class InnerSingleton implements Serializable {
private static final long serialVersionUID = 1L;
private InnerSingleton() {
if (Singleton.instance != null) {
throw new RuntimeException("单例对象已存在");
}
}
public static InnerSingleton getInstance() {
return Singleton.instance;
}
private Object readResolve() throws ObjectStreamException {
return Singleton.instance;
}
private static class Singleton {
public static InnerSingleton instance = new InnerSingleton();
}
}
当单例对象实现Cloneable接口时,调用clone()方法也会返回新的实例,可以重写clone()方法,返回单例对象解决。