初识并发编程(三) 发布对象

1.基础

发布:使对象能够在当前作用域之外的代码中使用
对象逸出:一种错误的发布,当一个对象还没构建完成时,就被其他线程所见。

不安全发布:

        这个代码通过public访问级别发布了类的域,在类的任何外部的线程都可以访问这些域
        我们无法保证其他线程会不会修改这个域,从而使私有域内的值错误(上述代码中就对私有域进行了修改)

package com.mmall.concurrency.example.publish;

import com.mmall.concurrency.annoations.NotThreadSafe;
import lombok.extern.slf4j.Slf4j;
import java.util.Arrays;

/**
 * 发布对象
 * <p></p>
 * 线程不安全,
 */
@NotThreadSafe
@Slf4j
public class UsafePublish {

    //此处创建一个数组
    private String[] states = {"a", "b"};

    /**
     * 通过 public访问级别 发布class的域。class的任何外部线程都可以访问这个域,发布对象不安全。其他线程会不会修改这个域(如下:usafePublish.getStates()[0] = "cc";)
     * ,从而造成class状态的错误
     */

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

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

        //当前线程修改,导致其他线程得到的不是 states最原始的值
        usafePublish.getStates()[0] = "cc";
        log.info("ts main2 {}", Arrays.toString(usafePublish.getStates()));

    }
}

对象溢出:

        这个内部类的实例里面包含了对封装实例的私有域对象的引用,在对象没有被正确构造完成之前就会被发布,有可能有不安全的因素在里面,会导致this引用在构造期间溢出的错误。
        上述代码在函数构造过程中启动了一个线程。无论是隐式的启动还是显式的启动,都会造成这个this引用的溢出。新线程总会在所属对象构造完毕之前就已经看到它了。
        因此要在构造函数中创建线程,那么不要启动它,而是应该采用一个专有的start或者初始化的方法统一启动线程
        这里其实我们可以采用工厂方法和私有构造函数来完成对象创建和监听器的注册等等,这样才可以避免错误 

public class Escape {

    private int thisCannBeEscape = 0;

    public Escape(){
        new InnerClass();
    }
    private class  InnerClass{
        public InnerClass(){
            log.info("{}",Escape.this.thisCannBeEscape);
        }
    }

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

2.安全发布对象

2.1在静态初始化函数中初始化一个对象引用

        一个类只初始化一次,这个类不能随便new新的对象出来。他的默认构造方法是私有的

        有懒汉模式(多线程下不安全)、饿汉模式(初始化就加载,后面可能不用,容易资源浪费)、枚举模式(推荐)

懒汉模式Demo:

/**
 * 懒汉模式
 * 单利实例在第一次使用时候创建
 * <p>
 * 不安全线程
 */
@Slf4j
@NotThreadSafe
public class SingletonExampleLazy {
    //私有化构造函数
    private SingletonExampleLazy() {
    }
    //单例对象
    private static SingletonExampleLazy instance = null;

    //静态的工厂方法
    public static SingletonExampleLazy getInstance() {
        //单线程没有问题。如果是多线程 ,
        //如果两个线程同时判断是否为空,会同时运行两次(new SingletonExampleLazy)
        if (instance == null) {
            instance = new SingletonExampleLazy();
        }
        return instance;

    }
}

饿汉模式Demo:

/**
 * 饿汉模式
 * 单例实例在类加载的时候初始化
 * <p>
 * 安全线程
 * <p>
 * 1:存在过多的处理,导致加载class过慢。
 * 2:如果加载而不使用,则会导致浪费。
 */
@Slf4j
@ThreadSafe
public class SingletonExampleHungry {
    //私有化构建函数
    private SingletonExampleHungry() {
    }

    //单例对象
    private static SingletonExampleHungry instance = new SingletonExampleHungry();

    //静态工厂方法
    public static SingletonExampleHungry getInstance() {
        return instance;
    }
}

枚举模式:

/**
 * 枚举模式,最安全。
 * <p></p>
 * 比懒汉 更加安全
 * 比饿汉  实际开始才会初始化。没有资源浪费
 */
@ThreadSafe
@Recommend
public class SingletonExampleEnum {
    //私有构造函数
    private SingletonExampleEnum() {
    }
    private static SingletonExampleEnum getInstance(){
        return Singleton.INSTANCE.getInstance();
    }

    private enum Singleton{
        INSTANCE;
        private SingletonExampleEnum singleton;
        //JVM保证这个方法只被调用一次
        Singleton(){
            singleton=new SingletonExampleEnum();
        }
        public
        SingletonExampleEnum getInstance(){
            return singleton;
        }
    }
}

2.2将对象的引用保存到 volatile 类型域或者 AtomicReference 对象中

/**
 * 懒汉模式+双重锁
 * 线程安全
 */
@Slf4j
public class SingletonExampleLazyDoubleLockSafe {

    //私有化构建函数
    private SingletonExampleLazyDoubleLockSafe() {

    }
// 1、memory = allocate() 分配对象的内存空间
    // 2、ctorInstance() 初始化对象
    // 3、instance = memory 设置instance指向刚分配的内存

    // 单例对象 volatile + 双重检测机制 -> 禁止指令重排
    private volatile static SingletonExampleLazyDoubleLockSafe instance = null;

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

 2.3将对象的引用保存到某个正确构造对象的 final 类型域中

 2.4将对象的引用保存到一个由锁保护的域中(synchronized)

/**
 * 懒汉模式+双重同步锁单例模式
 * 线程不安全
 */
@NotThreadSafe
@Slf4j
public class SingletonExampleLazyDoubleLock {
    //私有化构建函数
    private SingletonExampleLazyDoubleLock() {

    }

    /**
     * 正常流程
     * 1、memory = allocate() 分配对象的内存空间
     * 2、ctorInstance() 初始化对象
     * 3、instance = memory 设置instance指向刚分配的内存
     */

    /**
     * 多线程可能发生    JVM和cpu优化,发生了指令重排
     * 1、memory = allocate() 分配对象的内存空间
     * 3、instance = memory 设置instance指向刚分配的内存
     * 2、ctorInstance() 初始化对象
     */

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

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

 另外:静态域或者静态方法一定要注意顺序,否则执行代码结果可能不同

  • 0
    点赞
  • 1
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值