单例模式:保证获取到的对象是同一个对象,对象只实例化一次
特点:单例类的构造方法是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 "";
}