【4】安全发布对象-发布与溢出 | 安全发布对象-四种方法

本文探讨了在并发编程环境下安全发布对象的重要性,通过实例分析了不安全发布的风险,并介绍了四种安全发布对象的方法,包括使用单例模式确保线程安全。深入解析了懒汉和饿汉模式下单例模式的实现细节,以及如何避免指令重排等问题。

摘要生成于 C知道 ,由 DeepSeek-R1 满血版支持, 前往体验 >

简介 总结

1. 安全发布对象-发布与溢出

2. 安全发布对象-四种方法

在这里插入图片描述

不安全发布代码示例

package com.mmall.concurrency.publishDemo;
import lombok.extern.slf4j.Slf4j;
import java.util.Arrays;

/**
 * 定义一个数组,发布get方法,其他示例线程修改数组
 */
@Slf4j
public class UnSafePub {

    private String[] states = {"a","b","c","d"};

    // 发布出去一个
    public String[] getStates(){
        return states;
    }

    public static void main(String[] args){
        UnSafePub unSafePub = new UnSafePub();
        // log.info("init array: {}", unSafePub.getStates());
        log.info("Init array is: {}", Arrays.toString(unSafePub.getStates()));
        
        unSafePub.getStates()[0] = "FUCK!";
        log.info("After modify.the  array is: {}", Arrays.toString(unSafePub.getStates()));
    }
}

对象溢出

package com.mmall.concurrency.publishDemo;

import lombok.extern.slf4j.Slf4j;

/**
 * 线程溢出
 */
@Slf4j
public class Escape {
    private int thisCanBeEscape = 0;

    public Escape(){
        new InnerClass();
    }
    //内部类
    public class InnerClass {
        public InnerClass(){
            log.info("{}", Escape.this.thisCanBeEscape);
        }
    }

    //主方法测试
    public static void main(String[] args){
        new Escape();
    }
}

案例分析

内部类包含对分装实例的隐涵应用,这样在对象没有被正确构造完成之前它就会被发布,有可能有不安全的因素,一个导致(Escape.this.thisCanBeEscape) 中,this引用在构造期间导致的错误他是在函数构造过程中(public Escape(){) 相当于启动了一个线程,无论是显式的还是隐式的,都会造成this引用的逸出错误,新线程总会在所属对象构造完成之前就看到他了,所以如果要在构造函数中创建线程,那么,不要启动它,而是应该用一个专有start方法或者一个统一启动的方法来启动,这里可以采用工厂方法和私有构造函数来完成对象创建和监听器的注册等操作,这样才可以避免不正确的创建,

注意, 我们这里的目的是:在对象没有完成构造之前,不可以将其发布,不安全,不推荐

不正确的发布可变对象,导致两种错误。

  • 发布线程以外的任何线程都可以看到被发布对象的过期的值,
  • 线程看到的被发布对象的引用是最新的,然而被发布对象的状态是过期的,如果一个对象是可变对象,
    那么它要被安全发布才可以。

接下来讲安全的发布对象的四种方法

如何保证一个实例只被初始化一次,且线程安全呢? 单例代码

在这里插入图片描述

1. 要安全 = 不能随便new一个对象出来。构造方法私有, 构造单例对象,静态工厂方法获取单例对象(不安全)

第一种实现

package com.mmall.concurrency.singleton;
import com.mmall.concurrency.annotations.NotThreadSafe;
import lombok.extern.slf4j.Slf4j;
/**
 * 懒汉模式
 * 单例实例在第一次使用时候创建
 */
@Slf4j
@NotThreadSafe
public class Singleton1 {
    // 私有构造函数
    private Singleton1(){

    }

    // 单例对象
    private static Singleton1 instance = null;

    // 静态工厂类获取实例 --  19行两个线程同时执行时候不安全
    public static Singleton1 getInstance(){
        if (instance == null){
            instance = new Singleton1();
        }
        return instance;
    }
}
    public static synchronized Singleton1 getInstance(){

这样就是安全的,只是性能不好

第二种实现

package com.mmall.concurrency.singleton;

import com.mmall.concurrency.annotations.ThreadSafe;
import lombok.extern.slf4j.Slf4j;

/**
 * 饿汉模式
 * 单例实例在装载的时候进行创建
 */
@ThreadSafe
@Slf4j
public class Single2 {
    //私有构造
    private Single2(){
        
    }
    // 单例
    private static Single2 instance = new Single2();
    // 工厂类
    public static Single2 getInstance(){
        return instance;
    }
    
}

不足

如果构造方法存在过多的处理,导致类加载特别慢,性能问题,只加载,不使用,资源浪费
因此饿汉模式考虑两个问题,

  • 1.私构造函数没有太多处理
    1. 这个类肯定被使用

第三种实现

// CPU new 操作的步骤问题
// 1. memory = allocate() 分配对象的内存空间
// 2. ctorInstance() 初始化对象
// 3. instance = memory 设置instance指向刚分配的内存
// * JVM 和cpu 优化 指令重排
// 1. memory = allocate() 分配对象的内存空间
// 3. instance = memory 设置instance指向刚分配的内存
// 2. ctorInstance() 初始化对象
// 调用没有初始化的对象 --> 解决办法 --> 禁止指令重排

// 单例对象
/**
 * 懒汉模式
 * 单例实例在第一次使用时候创建
 */

/**synchronized,主要的性能消耗
 *
 */
@Slf4j
@NotThreadSafe
@NotRecommend
public class Singleton4 {
    // 私有构造函数
    private Singleton4(){

    }
  
    private static Singleton4 instance = null;

    // 静态工厂类获取实例 --  19行两个线程同时执行时候不安全
    public static  Singleton4 getInstance(){
        if (instance == null){
            // 改变性能消耗 -- 双重检测机制     // B
            synchronized(Singleton4.class){  // 同步锁
                if (instance == null){
                    instance = new Singleton4();   // A
                }
            }
        }
        return instance;
    }
}

饿汉第四种实现

/**
 * 懒汉模式
 * 单例实例在第一次使用时候创建
 */

/**synchronized,主要的性能消耗
 *
 */
@Slf4j
@ThreadSafe
@NotRecommend
public class Singleton5 {
    // 私有构造函数
    private Singleton5(){
    }
    // 调用没有初始化的对象  -->  解决办法 --> 禁止指令重排 --> volatile
    //  volatile + double check 禁止指令重拍
    private volatile static Singleton5 instance = null;

    // 静态工厂类获取实例 --  19行两个线程同时执行时候不安全
    public static Singleton5 getInstance(){
        if (instance == null){
            // 改变性能消耗 -- 双重检测机制     // B
            synchronized(Singleton5.class){  // 同步锁
                if (instance == null){
                    instance = new Singleton5();   // A
                }
            }
        }
        return instance;
    }
}

懒汉完了 – 将饿汉的优化办法

除了静态域还用静态块 – 写静态域和静态方法时候一定要注意顺序

package com.mmall.concurrency.singleton;

import com.mmall.concurrency.annotations.NotRecommend;
import com.mmall.concurrency.annotations.ThreadSafe;
import lombok.extern.slf4j.Slf4j;

/**
 * 懒汉模式
 * 单例实例在第一次使用时候创建
 */
@Slf4j
@ThreadSafe
@NotRecommend
public class Singleton6 {
    // 私有构造函数
    private Singleton6(){

    }

    // 单例对象
    private static Singleton6 instance = null;
    // 静态域
    static {
        instance = new Singleton6();
    }

    // 静态工厂类获取实例 --  19行两个线程同时执行时候不安全
    public static synchronized Singleton6 getInstance(){
        return instance;
    }

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

关于单例实例的初始化介绍了懒汉 , 饿汉模式,还有枚举模式

package com.mmall.concurrency.singleton;

import com.mmall.concurrency.annotations.Recommend;
import lombok.extern.slf4j.Slf4j;
import org.apache.http.annotation.ThreadSafe;

/**
 * m枚举模式最安全
 */
@Slf4j
@ThreadSafe
@Recommend
public class Singleton7 {

    private Singleton7(){

    }

    public static Singleton7 getInstance(){
        return Singleton.INSTANCE.getInstance();
    }

    private enum Singleton{
        INSTANCE;

        private Singleton7 singleton;

        //jvm 保证绝对值调用一次
        Singleton(){
            singleton = new Singleton7();
        }

        public Singleton7 getInstance(){
            return singleton;
        }
    }
}

单例场景的分析

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值