安全发布对象,详解各种单例模式

admin
2022-04-21 / 0 评论 / 142 阅读 / 正在检测是否收录...
温馨提示:
本文最后更新于2022年04月24日,已超过740天没有更新,若内容或图片失效,请留言反馈。

安全发布对象

什么是安全发布?

发布对象:使一个对象能够被当前范围之外的代码所使用

代码示例

package com.yanxizhu.demo.concurrency.unSafePublish;

import com.yanxizhu.demo.concurrency.annotation.UnThreadSafety;
import lombok.extern.slf4j.Slf4j;

import java.util.Arrays;

/**
 * @description: 安全发布,这个例子是线程不安全的
 * @author: <a href="mailto:batis@foxmail.com">清风</a>
 * @date: 2022/4/21 21:45
 * @version: 1.0
 */
@Slf4j
@UnThreadSafety
public class UnSafePublish {
    private String[] states = {"a", "b", "c"};

    public String[] getStates() {
        return states;
    }

    public static void main(String[] args) {
        UnSafePublish unSafePublish = new UnSafePublish();
        log.info("{}", Arrays.toString(unSafePublish.getStates()));

        unSafePublish.getStates()[0] = "d";
        log.info("{}", Arrays.toString(unSafePublish.getStates()));

    }
}

运行结果:

21:48:00.633 [main] INFO com.yanxizhu.demo.concurrency.unSafePublish.UnSafePublish - [a, b, c]
21:48:00.636 [main] INFO com.yanxizhu.demo.concurrency.unSafePublish.UnSafePublish - [d, b, c]

说明:

上面代码,可以通过对象,调用getStates()方法获取数据,并可以外部直接修改里面的值。这样states的值是不安全的。

对象溢出

对象逸出:一种错误的发布。当一个对象还没有构造完成时,就使它被其他线程所见

代码示例

package com.yanxizhu.demo.concurrency.unSafePublish;

import lombok.extern.slf4j.Slf4j;

/**
 * @description: 对象溢出
 * @author: <a href="mailto:batis@foxmail.com">清风</a>
 * @date: 2022/4/21 21:53
 * @version: 1.0
 */
@Slf4j
public class Escape {

    private int thisCanBeEascpe = 0;

    public Escape() {
        new InnerClass();
    }

    private class InnerClass {

        public InnerClass() {
            log.info("{}", Escape.this.thisCanBeEascpe);
        }
    }

    public static void main(String[] args) {
        new Escape();
    }
}

输出结果:

21:55:01.416 [main] INFO com.yanxizhu.demo.concurrency.unSafePublish.Escape - 0

说明:Escape对象还没构造完,就被new InnerClass()线程使用Escape.this.thisCanBeEascpe。


安全发布对象4种方法

  1. 在静态初始化函数中初始化一个对象引用
  2. 将对象的引用保存到volatile类型域或者AtomicReference对象中
  3. 将对象的引用保存到某个正确构造对象的final类型域中
  4. 将对象的引用保存到一个由锁保护的域中

单例-懒汉模式

代码实例,线程不按钮

package com.yanxizhu.demo.concurrency.singleton;

import com.yanxizhu.demo.concurrency.annotation.UnThreadSafety;

/**
 * @description: 单例-懒汉模式
 * 在第一次使用时创建
 * 单线程下没问题,多线程下可能出现问题
 * @author: <a href="mailto:batis@foxmail.com">清风</a>
 * @date: 2022/4/21 22:05
 * @version: 1.0
 */
@UnThreadSafety
public class SingLetonLazyMode {

    //私有构造方法
    private SingLetonLazyMode() {}
    //实例对象
    private static SingLetonLazyMode instance = null;

    //静态工厂方法
    public static SingLetonLazyMode getInstance() {
        //注意:多线程下问题,当2个线程同时判断到不为null,是都会调用私有构造方法,创建2个示例对象。
        // 如果在构造方法中有对环境一些设置,2次执行会出现一些错误
        if (null == instance) {
            instance = new SingLetonLazyMode();
        }
        return instance;
    }
}

说明:

上面的单例-懒汉模式是线程不安全的。多线程下问题,当2个线程同时判断到不为null,是都会调用私有构造方法,创建2个示例对象。

如果在构造方法中有对环境一些设置,2次执行会出现一些错误。

单例-饿汉模式

代码实例,线程按钮

package com.yanxizhu.demo.concurrency.singleton;

import com.yanxizhu.demo.concurrency.annotation.ThreadSafety;

/**
 * @description: 单例-饿汉模式
 * 类装载时创建,线程安全的
 * 不足: 1、如果私有构造方法里面有过多的设置,会加载很慢
 *       2、每次都会创建示例对象,造成资源浪费
 * @author: <a href="mailto:batis@foxmail.com">清风</a>
 * @date: 2022/4/21 22:20
 * @version: 1.0
 */
@ThreadSafety
public class SingLetonHungryManMode {

    private SingLetonHungryManMode() {}

    private static SingLetonHungryManMode instance = new SingLetonHungryManMode();

    public static SingLetonHungryManMode getInstance() {
        return instance;
    }
}

说明:

单例-饿汉模式,类装载时创建,线程安全的。

不足地方:

1、如果私有构造方法里面有过多的设置,会加载很慢。

2、每次都会创建示例对象,造成资源浪费。

使用需要考虑:私有构造方法里面是否有过多设置,创建对象是否被使用。

单例-懒汉模式改进(synchronized)

代码实例,线程安全,但是不推荐使用,性能不太好

package com.yanxizhu.demo.concurrency.singleton;

import com.yanxizhu.demo.concurrency.annotation.ThreadSafety;
import com.yanxizhu.demo.concurrency.annotation.UnRecommend;

/**
 * @description: 单例-懒汉模式(synchronized保证线程保证线程安全)
 * 在第一次使用时创建
 * 单线程下没问题,多线程下通过synchronized保证线程只被执行一次,线程安全
 * @author: <a href="mailto:batis@foxmail.com">清风</a>
 * @date: 2022/4/21 22:05
 * @version: 1.0
 */
@ThreadSafety
@UnRecommend
public class SingLetonLazyModeSynchronized {

    //私有构造方法
    private SingLetonLazyModeSynchronized() {}
    //实例对象
    private static SingLetonLazyModeSynchronized instance = null;

    //静态工厂方法
    public static synchronized SingLetonLazyModeSynchronized getInstance() {
        //注意:多线程下问题,当2个线程同时判断到不为null,是都会调用私有构造方法,创建2个示例对象。
        // 如果在构造方法中有对环境一些设置,2次执行会出现一些错误
        if (null == instance) {
            instance = new SingLetonLazyModeSynchronized();
        }
        return instance;
    }
}

说明:

synchronized保证线程只能同时被执行一次,但是带来了性能上的开销。因此是线程安全但不推荐的写法。

单例-懒汉模式改进(双重同步锁单例)

代码实例,线程不安全的

package com.yanxizhu.demo.concurrency.singleton;

import com.yanxizhu.demo.concurrency.annotation.UnThreadSafety;

/**
 * @description: 单例-懒汉模式(双重同步锁单例)
 * 线程不安全
 * @author: <a href="mailto:batis@foxmail.com">清风</a>
 * @date: 2022/4/21 22:05
 * @version: 1.0
 */
@UnThreadSafety
public class SingLetonLazyModeSynchronized {

    //私有构造方法
    private SingLetonLazyModeSynchronized() {}
    //实例对象
    private static SingLetonLazyModeSynchronized instance = null;

    //静态工厂方法
    public static SingLetonLazyModeSynchronized getInstance() {

        if (null == instance) { //双重检测机制
            //当第一个线程执行后,已经实例化一个对象了,第二个线程再次判断兑现是否实例。
            //线程不安全问题所在:
            //
            synchronized(SingLetonLazyModeSynchronized.class) { //同步锁
                if(null == instance) {
                    instance = new SingLetonLazyModeSynchronized();
                }
            }
        }
        return instance;
    }
}

说明:

instance = new SingLetonLazyModeSynchronized();分为3步骤。

1、分配对象的内存空间。

2、初始化对象。

3、设置instance指向刚分配的内存。

问题分析:

多线程情况下,上面3步,可能发生指令重排。

JVM和CPU优化,发生了指令重排。

因为指令2和3没有直接关联,所以上面3步指令可能优化重排成:

1、分配对象的内存空间。

3、设置instance指向刚分配的内存。

2、初始化对象。

当第一个线程执行instance = new SingLetonLazyModeSynchronized();设置对象指向刚分配的内存地址,还没执行2初始化对象时,第二个线程判断对象的执行步为空,直接返回对象,此时由于2初始化对象还没执行,导致调用对象报错。

单列-懒汉模式(volatile+双重检测机制)最终版

代码实例,线程安全,懒汉模式最终版

package com.yanxizhu.demo.concurrency.singleton;

import com.yanxizhu.demo.concurrency.annotation.ThreadSafety;
import com.yanxizhu.demo.concurrency.annotation.UnRecommend;

/**
 * @description: 单例-懒汉模式(volatile+双重检测机制单列) 线程安全
 * @author: <a href="mailto:batis@foxmail.com">清风</a>
 * @date: 2022/4/21 22:05
 * @version: 1.0
 */
@ThreadSafety
public class SingLetonLazyModeSynchronizedDoubleSyncVolatile {

    //私有构造方法
    private SingLetonLazyModeSynchronizedDoubleSyncVolatile() {}

    //实例对象
    //volatile禁止指令重排
    private volatile static SingLetonLazyModeSynchronizedDoubleSyncVolatile instance = null;

    //静态工厂方法
    public static SingLetonLazyModeSynchronizedDoubleSyncVolatile getInstance() {

        if (null == instance) { //双重检测机制
            synchronized (SingLetonLazyModeSynchronizedDoubleSyncVolatile.class) { //同步锁
                if (null == instance) {
                    instance = new SingLetonLazyModeSynchronizedDoubleSyncVolatile();
                }
            }
        }
        return instance;
    }
}

说明:

volatile+双重检测机制,volatile禁止指令重排,双重检测,加同步锁,保证多线程下线程安全。

单例-饿汉模式(静态代码块写法)

代码实例,错误的,会发生控制在异常

package com.yanxizhu.demo.concurrency.singleton;

import com.yanxizhu.demo.concurrency.annotation.ThreadSafety;

/**
 * @description: 单例-饿汉模式,静态代码写法
 * 注意:静态代码和静态域的顺序,不然会导致空指针
 * @author: <a href="mailto:batis@foxmail.com">清风</a>
 * @date: 2022/4/21 22:20
 * @version: 1.0
 */
@ThreadSafety
public class SingLetonHungryManModeStatic {

    private SingLetonHungryManModeStatic() {}

    //静态代码块
    static{
        instance = new SingLetonHungryManModeStatic();
    }

    //静态作用域
    private static SingLetonHungryManModeStatic instance = null;

    public static SingLetonHungryManModeStatic getInstance() {
        return instance;
    }

    public static void main(String[] args) {
        System.out.println(getInstance().hashCode());
        System.out.println(getInstance().hashCode());
    }
}

输出结果:

Exception in thread "main" java.lang.NullPointerException
    at com.yanxizhu.demo.concurrency.singleton.SingLetonHungryManModeStatic.main(SingLetonHungryManModeStatic.java:30)

错误原因:静态代码和静态域的顺序,导致空指针

代码调整后,正确代码

package com.yanxizhu.demo.concurrency.singleton;

import com.yanxizhu.demo.concurrency.annotation.ThreadSafety;

/**
 * @description: 单例-饿汉模式,静态代码写法
 * 注意:静态代码和静态域的顺序,不然会导致空指针
 * @author: <a href="mailto:batis@foxmail.com">清风</a>
 * @date: 2022/4/21 22:20
 * @version: 1.0
 */
@ThreadSafety
public class SingLetonHungryManModeStatic {

    private SingLetonHungryManModeStatic() {}

    //静态作用域
    private static SingLetonHungryManModeStatic instance = null;

    //静态代码块
    static{
        instance = new SingLetonHungryManModeStatic();
    }

    public static SingLetonHungryManModeStatic getInstance() {
        return instance;
    }

    public static void main(String[] args) {
        System.out.println(getInstance().hashCode());
        System.out.println(getInstance().hashCode());
    }
}

输出结果:

75457651
75457651

单例-枚举模式线程安全

最终推荐版本时枚举模式的单例。

示例代码:

package com.yanxizhu.demo.concurrency.singleton;

import com.yanxizhu.demo.concurrency.annotation.Recommend;
import com.yanxizhu.demo.concurrency.annotation.ThreadSafety;

/**
 * @description: 单例-枚举模式,线程最安全的,推荐使用的
 * @author: <a href="mailto:batis@foxmail.com">清风</a>
 * @date: 2022/4/21 23:11
 * @version: 1.0
 */
@ThreadSafety
@Recommend
public class SingLetonEnum {
    //私有构造方法
    private SingLetonEnum() {};

    public static SingLetonEnum getInstance() {
        return SingLeton.INSTANCE.getSingLetonEnum();
    };

    private enum SingLeton {
        INSTANCE;

        private SingLetonEnum singLetonEnum;

        //JVM保证这个方法绝对只会被执行一次
        SingLeton(){
            singLetonEnum = new SingLetonEnum();
        }

        public SingLetonEnum getSingLetonEnum() {
            return singLetonEnum;
        }
    }
}

说明:枚举的构造方法,JVM保证这个方法绝对只会被执行一次。

线程安全,推荐使用的单例模式-枚举模式。

比饿汉模式更能保证安全,比懒汉模式更能保证性能。

枚举模式只有使用时调用一次,不会造成资源浪费。


总结:

饿汉模式:有2种写法,最后这种饿汉模式注意静态代码块和作用域顺序。

懒汉模式:只有最终版(volatile+双重检测机制)正确,其它都存在线程安全或性能问题。

单例模式,线程安全,最终推荐枚举模式。

3

评论 (0)

取消