单例模式
定义:一个类自始至终仅有一个实例。
优点:在内存中仅有一个实例对象,减少内存开销。
缺点:没有接口,拓展困难
饿汉式单例
- 构造方法私有化,保证在类外不能创建对象
- 在类中创一个实例对象
- 提供对外的方法,将实例对象返回。
public class Singleton {
private static Singleton ourInstance = new Singleton();
public static Singleton getInstance() {
return ourInstance;
}
private Singleton() {
}
}
因为采取上来就new的风格,多线程并发的情况下也没有线程安全问题。
懒汉式单例
public class LazySingleton {
private static LazySingleton ourInstance = null;
public static LazySingleton getInstance() {
if(ourInstance == null){
ourInstance = new LazySingleton();
}
return ourInstance;
}
private LazySingleton() {
}
}
多线程下的问题:单线程下确实没有问题,但多个线程同时调用getInstance方法时,因为CPU的上下文切换,很大程度上new了多个对象。
双重加锁单例模式
package com.xust.Singleton;
public class DoubleCheckSingleton {
private static DoubleCheckSingleton ourInstance = null;
public static DoubleCheckSingleton getInstance() {
if(ourInstance == null) {
synchronized (DoubleCheckSingleton.class) {
if (ourInstance == null) {
ourInstance = new DoubleCheckSingleton();
}
}
}
return ourInstance;
}
private DoubleCheckSingleton() {
}
}
指令重排序的问题
ourInstance = new DoubleCheckSingleton();这句代码,虽然仅有一句,但它本省不是原子的。这也就是说创建对象的过程包含三个步骤:
- 在堆上开辟一块空间,即为该对象开辟内存
- 初始化对象
- 栈上的引用指向开辟的空间
代码在编译允许的时候,会由于编译器的优化,发生一些程序员不知道的指令重排序问题,假如第二步和第三步发生交换,只要栈上的引用指向堆空间则判断的时候就不为null,因此当其他线程进行第一次的判null时,就已经不为null,但初始化还未完成,因此就会返回一个不完整的对象,为了针对这种情况需要加一个volatile关键字。禁止指令重排序。
package com.xust.Singleton;
public class DoubleCheckSingleton {
private volatile static DoubleCheckSingleton ourInstance = null;
public static DoubleCheckSingleton getInstance() {
if(ourInstance == null) {
synchronized (DoubleCheckSingleton.class) {
if (ourInstance == null) {
ourInstance = new DoubleCheckSingleton();
}
}
}
return ourInstance;
}
private DoubleCheckSingleton() {
}
}
静态内部类
public class StaticInnerClassSingleton {
private static class InnerClass{
private static StaticInnerClassSingleton instance = new StaticInnerClassSingleton();
}
public static StaticInnerClassSingleton getInstance(){
return InnerClass.instance;
}
private StaticInnerClassSingleton(){}
}
类在初始化的时候,JVM会获取本类的Class对象的锁,保证多个线程的情况下,类只会被初始化一次。
静态代码块与单例模式
public class StaticBlock {
private static StaticBlock instance = null;
private StaticBlock(){}
static{
instance = new StaticBlock();
}
public static StaticBlock getInstance() {
return instance;
}
}
静态代码块在类加载的时候执行,且只会执行一次,即使多线程下也只会执行一次,因此静态代码块本质上与静态内部类是一样的。
反射与单例
package com.xust.Singleton;
import java.lang.reflect.Constructor;
import java.lang.reflect.InvocationTargetException;
import java.lang.reflect.Method;
/**
* @auther plg
* @date 2019/7/17 17:36
*/
public class ReflectDemo {
public static void main(String[] args) throws NoSuchMethodException, IllegalAccessException, InvocationTargetException, InstantiationException {
Class cls = LazySingleton.class;
Constructor constructor = cls.getDeclaredConstructor();
constructor.setAccessible(true);
LazySingleton l1 = (LazySingleton) constructor.newInstance();
LazySingleton l2 = (LazySingleton) constructor.newInstance();
System.out.println(l1 == l2); // false非同一个对象
}
}
通过反射可以破坏封装,进而在类的外面直接调用构造方法生成对象。
枚举与单例模式
public enum EnumSingleton {
INSTANCE;
public static EnumSingleton getInstance(){
return EnumSingleton.INSTANCE;
}
}
枚举的单例模式不能通过反射破坏,且简单容易书写。
JDK中的单例模式
饿汉式单例。