线程之安全发布对象--单例模式

单例模式:保证获取到的对象是同一个对象,对象只实例化一次

特点:单例类的构造方法是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 "";
   }


猜你喜欢

转载自blog.csdn.net/qq_36922927/article/details/80672043