单例模式:保证获取到的对象是同一个对象,对象只实例化一次
特点:单例类的构造方法是private
有一个静态发方法getInstance获取单例实例
两大类:懒汉式与饿汉式
懒汉:在需要单例对象时,手动调用getInstance方法时,才会执行第一次创建
饿汉:默认就创建好一个单例对象,不需要手动调用才创建
代码示例:
1.1懒汉,线程不安全示例
/** * 懒汉模式----在工厂方法中实例化 * 单例实例在第一次使用时进行创建 */ @Slf4j @NotRecommend @NotThreadSafe public class SingletonExample1 { private SingletonExample1(){ } private static SingletonExample1 instance=null; public static SingletonExample1 getInstance(){ // 如果是两个线程同时进入到if中,那么就会实例化两个单例实例,虽然这里的单例很简单,里面不含计算等步骤,可能不会有什么影响,但是如果包含某种计算并且依赖单例中的数据,那么就可能导致两个线程获得的计算结果不一样 if(null==instance) instance=new SingletonExample1(); return instance; } }
1.2懒汉,+synchronized
使用synchronized关键字修饰getInstance方法,保证每次只能一个线程调用该方法,保证了线程安全
但是,不推荐,效率比较低,因为单例只实例化一次,也就是说99.999999%的情况下,getInstance返回的是已经初始化的同一个对象
/** * 懒汉模式 * 单例实例在第一次使用时进行创建 */ @Slf4j @ThreadSafe @NotRecommend public class SingletonExample3 { private SingletonExample3(){ } private static SingletonExample3 instance=null; // 使用synchronized修饰,同一时间只能有一个线程进入这个方法, // 是线程安全的,但是会导致效率低下 public static synchronized SingletonExample3 getInstance(){ // 如果是两个线程同时进入到if中,那么就会实例化两个单例实例 if(null==instance) instance=new SingletonExample3(); return instance; } }
1.3懒汉,线程不安全---发生指令重排
/** * 懒汉模式 * 单例实例在第一次使用时进行创建 * 双重检测机制:线程安全问题?如何限制指令重排呢? */ @Slf4j @NotThreadSafe public class SingletonExample4 { private SingletonExample4(){ } // 1, memory=allocate()分配对象的内存空间 // 2,ctorInstance()初始化对象 // 3,instance=memory 设置instance指向刚分配的内存 // JVM和cpu优化,发生指令重排 // 1, memory=allocate()分配对象的内存空间 // 3,instance=memory 设置instance指向刚分配的内存 // 2,ctorInstance()初始化对象 private static SingletonExample4 instance=null; public static SingletonExample4 getInstance(){ // 如果是两个线程同时进入到if中,那么就会实例化两个单例实例 // 加入现在有两个线程A和B if(null==instance)//双重检测机制,为什么需要双重检测?指令重排 // B { synchronized (SingletonExample4.class){//同步锁 if(null==instance) { instance=new SingletonExample4(); // 线程A 到 第 3 步,此时instance!=null,指向一块内存,但是没被初始化 // 线程到没被初始化的instance } } } return instance; } }
1.4懒汉,使用volatile关键字+双重检测机制-》避免指令重排
/** * 懒汉模式 * 单例实例在第一次使用时进行创建 * 双重检测机制:线程安全问题?如何限制指令重排呢? 使用volatile+双重检测机制 */ @Slf4j @ThreadSafe public class SingletonExample5 { private SingletonExample5(){ } // 1, memory=allocate()分配对象的内存空间 // 2,ctorInstance()初始化对象 // 3,instance=memory 设置instance指向刚分配的内存 //单例对象 volatile+双重检测机制-》禁止指令重排 // 被volatile关键字修饰的对象或者变量,对其读写的操作时直接在主存中执行的,也就是 //获取最新的值,新的值也能立刻更新到主存中 private static volatile SingletonExample5 instance=null; public static SingletonExample5 getInstance(){ // 如果是两个线程同时进入到if中,那么就会实例化两个单例实例 if(null==instance)//双重检测机制,为什么需要双重检测?指令重排 // 能否考虑,去掉第一个if呢?如果去掉,效果其实是和1.2类似的,会出现低效率 { synchronized (SingletonExample5.class){//同步锁,对类加锁,同一时间,只能一个线程进入其后的{}中 if(null==instance) { instance=new SingletonExample5(); } } } return instance; } }饿汉模式代码示例
2.1饿汉,注意和1.1的区别,在于实例化单例类的位置
/** * 饿汉模式 * 单例实例在类装载使用时进行创建 * 要求:该单例对象一定会被使用,以免造成资源浪费 */ @Slf4j @ThreadSafe public class SingletonExample2 { private SingletonExample2(){ } private static SingletonExample2 instance=new SingletonExample2(); public static SingletonExample2 getInstance(){ return instance; } }
2.2饿汉模式,在静态块中实例化单例
** * 饿汉模式--使用静态块实例化 * 单例实例在类装载使用时进行创建 * 要求:该单例对象一定会被被使用,以免造成资源浪费 * 初始化顺序:这个顺序不必记忆,具体可打断点观察顺序 */ @Slf4j @ThreadSafe public class SingletonExample6 { private SingletonExample6(){ } // static {//A // instance=new SingletonExample6(); // } private static SingletonExample6 instance=null; static {//B instance=new SingletonExample6(); } public static SingletonExample6 getInstance(){ return instance; } public static void main(String[] args) { System.out.println(getInstance());//保留A,注释B 输出为null,反之正常 System.out.println(getInstance());//保留A,注释B,输出为null,反之正常 } }
2.3饿汉+枚举
使用枚举来完成单例的实例化,推荐,这是由jvm保证的
/** * 饿汉模式--使用枚举实例化,最安全 * 单例实例在类装载使用时进行创建 * 要求:该单例对象一定会被被使用,以免造成资源浪费 */ @Slf4j @ThreadSafe @Recommend public class SingletonExample7 { private SingletonExample7(){ } private static SingletonExample7 instance=null; public static SingletonExample7 getInstance(){ return Singleton.INSTANCE.getSingleton(); } //枚举是特殊的类,这里将其看成是一个内部类即可 private enum Singleton{ INSTANCE; private SingletonExample7 singleton; // JVM保证这个方法绝对的只被实例化一次 Singleton(){ singleton=new SingletonExample7(); } public SingletonExample7 getSingleton() { return singleton; } } public static void main(String[] args) { System.out.println(getInstance());//输出不为null System.out.println(getInstance());//输出不为为null } }
注:代码中出现的注解
@ThreadSafe
@NotThreadSafe
@Recommend
@NotRecommend
是自定义注解,只是用来方便标识类(方法)是否线程安全,是否推荐等信息
注解类代码如下:
/** * 标识不推荐的写法 */ @Target(ElementType.TYPE) @Retention(RetentionPolicy.SOURCE) public @interface NotRecommend { String value() default ""; }
/** * 用来标记线程【不安全】的类或者方法 */ @Target(ElementType.TYPE) //注解范围 @Retention(RetentionPolicy.SOURCE) public @interface NotThreadSafe { String value() default ""; }
/** * 标识推荐的写法 */ @Target(ElementType.TYPE) @Retention(RetentionPolicy.SOURCE) public @interface Recommend { String value() default ""; }
/** * 用来标记线程安全的类或者方法 */ @Target(ElementType.TYPE) //注解范围 @Retention(RetentionPolicy.SOURCE) public @interface ThreadSafe { String value() default ""; }