一、懒汉式
1.最简单的懒汉模式(线程不安全)
package com.java.设计模式;
//懒汉式
public class Singleton1 {
// 节省内存开销
private static Singleton1 singleton1 = null;
// 私有构造器,防止外部实例化对象(先不谈反射)
private Singleton1() {
}
// 提供外部访问方法
// 存在线程安全问题,当多个线程同时访问可能会创建不是同一个对象。下面会改造
public static Singleton1 getSingleton1() {
//简单判断,也就是这里防不住并发安全问题
if (singleton1 == null) {
singleton1 = new Singleton1();
}
return singleton1;
}
}
2.getSingleton1方法加锁synchronized关键字
//这里的锁对象用到的是 this锁
//synchronized保证了线程安全性,每次只能允许一个线程进行访问
public static synchronized Singleton1 getSingleton1() {
if (singleton1 == null) {
singleton1 = new Singleton1();
}
return singleton1;
}
3.双重检验锁
public static Singleton1 getSingleton1() {
if (singleton1 == null) {
synchronized (Singleton1.class) {
if (singleton1 == null) {
singleton1 = new Singleton1();
}
}
}
return singleton1;
}
疑问:为什么要加另个if判断?
a.假设没有第一个判断,当发生并发操作时。每次调用这个方法的线程就会上锁,资源开销是相当大啊,对比判断与上锁。显然是判断语句的开销要小得多。如果已经创建了实例对象,那么就会被直接拦截。不会再进行上锁操作了。
b.假设没有第二个判断,当实例对象没有创建的时候,又同时有多个线程调用此方法。那么第一个判断肯定为true,然后会有一个线程先持有锁对象并创建对象最后释放锁资源。另外躲过利第一个if判断的线程已经进来了,不可能再跳出判断重新进来一次吧?这个时候一定会再次创建多个对象。所以第二个if判断也是很有必要的!
二、饿汉式
package com.java.设计模式;
//饿汉式
public class Singleton2 {
//随着类的加载而加载
private static final Singleton2 singleton2 = new Singleton2();
//私有构造器
private Singleton2(){}
//提供外部访问方法
public Singleton2 getSingleton2(){
return singleton2;
}
}
饿汉式:随着类的加载而加载,在类初始化的时候就已经把类的实例化对象创建好了。所以天生线程安全,就是占内存。要是用 不到就亏了。
三、静态内部类
package com.java.设计模式;
public class Singleton3 {
//私有构造器
private Singleton3(){
System.out.println("getInstance");
}
//静态内部类
public static class SingletonInstance {
private static final Singleton3 singleton3 = new Singleton3();
}
//提供外部访问方法
public static Singleton3 getInstance() {
return SingletonInstance.singleton3;
}
}
优点:兼顾了懒汉模式的内存优化(使用时才初始化)以及饿汉模式的安全性(不会被反射入侵)。
缺点:需要两个类去做到这一点,虽然不会创建静态内部类的对象,但是其 Class 对象还是会被创建,而且是属于永久带的对象。