单例设计模式
单例模式,就是只有一个实例,并且自己负责创建自己的对象,对外暴露获取对象的方法,不允许外部实例化该类。
核心代码:构造方法私有化,private,持有自己类型的属性,对外提供获取实例的静态方法
1:懒汉模式:
代码如下:
1 public class Singleton { 2 private static Singleton instance; 3 private Singleton (){} 4 5 public static Singleton getInstance() { 6 if (instance == null) { 7 instance = new Singleton(); 8 } 9 return instance; 10 } 11 }
特点:
当使用方调用 getInstance() 方法时,对象才进行加载。当多线程调用getInstance() 方法时,产生竞争条件,可能在 instance = new Singleton(); 这段逻辑生成多个对象。造成线程不安全。
2:饿汉模式:
代码如下:
1 public class Singleton { 2 private static Singleton instance = new Singleton(); 3 private Singleton (){} 4 public static Singleton getInstance() { 5 return instance; 6 } 7 }
特点:
这里 Singleton 使用static 进行修饰,当类通过类加载器进行加载的时候 会执行 static 修饰的变量,被static修饰的变量是随着类的加载而存在,随着类的消失而消失的。
所这里的instance 变量在Singleton类加载的时候会开始执行 private static Singleton instance = new Singleton(); 这段代码的逻辑。
所以在全局中只会存在一个实例。这样是线程安全的,只是这种方式不管用到了还是没用到这个实例都会去实例化它,容易产生内存。
3:双重锁模式
保证使用到这个对象是采取初始化这个对象,切线程安全:
代码如下:
1 public class Singleton { 2 private volatile static Singleton singleton; 3 private Singleton (){} 4 public static Singleton getSingleton() { 5 if (singleton == null) { 6 synchronized (Singleton.class) { 7 if (singleton == null) { 8 singleton = new Singleton(); 9 } 10 } 11 } 12 return singleton; 13 } 14 }
这里当用户需要调用 getSingleton() 方法获取对象实例的时候,
1:判断实例是否存在,如果存在则直接返回对象,提升代码执行效率
2:判断对象确实不存在的情况下,进行加锁后创建对象,这里再次进行一次判断对象是否存在。对象不存在的情况下创建对象。
下面对以下几行代码进行分析:
第二行 第五行 第七行 代码进行分析
第二行:private volatile static Singleton singleton;
这行代码用了 volatile 关键字进行修饰,volatile可以 保证 修饰的变量 可见性 但是不能保证原子性,volatile修饰保证jvm加载代码不会重排序,保证多线程下访问安全。
当线程已经执行完第8行代码,创建好了对象了,这个时候新的线程执行进入第5行代码进行判断,如果没有volatile 进行修饰的话,这时候该线程中获取的私有变量为null,则会进入同步的创建代码的逻辑。
使用volatile修饰,可以保证同步到全局变量中获取;
第五行 if (singleton == null)
这行代码主要是判断是否存在实例,如果存在直接返回,如果没有这行代码,这接下来直接进入加锁的逻辑,多线程访问的情况下,可以保证线程的安全,但是每个线程都会排队进入同步的方法,导致效率低下。
第七行 if (singleton == null)
假设第一个线程进入 singleton = new Singleton(); 创建了对象,其他线程在第一层if (singleton == null) 判断是为空,然后进入同步代码中,这是如果不进行判断,则会进入对象创建的逻辑