Java并发学习之安全发布对象

安全发布对象

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

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

线程不安全的发布对象:

package com.concurrency.example.example.publish;

import com.concurrency.example.annotations.NotThreadSafe;
import lombok.extern.slf4j.Slf4j;

import java.util.Arrays;

/**不安全的发布
 * Created with IDEA
 * author:LuXiaoWei
 * Date:2018/5/5
 * Time:20:23
 */
@NotThreadSafe
@Slf4j
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()));
    }
}

输出结果:

[a,b,c]
[d,b,c]

代码中,声明了一个String类型的数组并通过一个public方法getState()发布了这个数组(域),在这个类的任何外部线程都可以访问这个域。在这个例子中,通过

UnsafePublish unsafePublish = new UnsafePublish();
发布了这个类的一个实例,并使用
unsafePublish.getStates()[0]

得到了这个私有域,并将第一个元素赋为"d",所以第二次输出为[d,b,c],这样是线程不安全的。

对象溢出:

package com.concurrency.example.example.publish;

import com.concurrency.example.annotations.NotRecommend;
import com.concurrency.example.annotations.NotThreadSafe;
import lombok.extern.slf4j.Slf4j;

/**
 * Created with IDEA
 * author:LuXiaoWei
 * Date:2018/5/5
 * Time:20:36
 */
@Slf4j
@NotThreadSafe
@NotRecommend
public class Escape {

    private int thisCanBeScape = 0;

    public Escape(){
        new InnerClass();
    }

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

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

在Escape的构造函数中相当于创建了一个线程,创建了一个内部类对象,内部类里面使用了thisCanBeEscape,有可能在对象还没有发布完成就先使用了,有可能导致this引用在构造过程中溢出。

如果要在构造器中创建线程,应该使用专有的start或初始化的方法来统一启动线程,这里可以使用工厂方法和私有构造函数来完成对象的创建和监听器的创建。

安全发布对象的四种方法:

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

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

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

4、将对象的引用保存到一个由锁保护的域中

懒汉模式:

package com.concurrency.example.example.singleton;

import com.concurrency.example.annotations.NotThreadSafe;

/**
 * 懒汉模式
 * 单例的实例在第一次使用时进行创建
 * Created with IDEA
 * author:LuXiaoWei
 * Date:2018/5/6
 * Time:10:14
 */
@NotThreadSafe
public class SingletonExample1 {

    //将默认构造方法设为私有,保证类只能初始化一次,不能通过new创建类对象
    private SingletonExample1(){

    }

    //定义单例对象,每次返回同一个对象
    private static SingletonExample1 instance = null;

    //提供静态的工厂方法返回当前实例
    public static SingletonExample1 getInstance(){

        //多线程环境下有可能会初始化多次
        if (instance == null){
            instance = new SingletonExample1();
        }
        return instance;
    }


}

饿汉模式:

package com.concurrency.example.example.singleton;

import com.concurrency.example.annotations.NotThreadSafe;
import com.concurrency.example.annotations.ThreadSafe;
import lombok.extern.slf4j.Slf4j;

/**
 * 饿汉模式
 * 单例的实例在类装载时进行创建(线程安全)
 * 使用饿汉模式时,私有构造方法中代码过多或类实例没有被使用会降低性能,
 * 因此应该保证私有构造方法中没有过多处理且类实例肯定会被使用,避免线程浪费
 * Created with IDEA
 * author:LuXiaoWei
 * Date:2018/5/6
 * Time:10:14
 */
@Slf4j
@ThreadSafe
public class SingletonExample2 {

    //将默认构造方法设为私有,保证类只能初始化一次,不能通过new创建类对象
    private SingletonExample2(){

    }

    //定义单例对象,每次返回同一个对象
    private static SingletonExample2 instance = new SingletonExample2();

    //提供静态的工厂方法返回当前实例
    public static SingletonExample2 getInstance(){
        return instance;
    }


}

双重同步锁实现懒汉模式(线程不安全):

package com.concurrency.example.example.singleton;

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

/**
 * 懒汉模式 -> 双重同步锁单例模式(线程不安全)
 * 单例的实例在第一次使用时进行创建
 * Created with IDEA
 * author:LuXiaoWei
 * Date:2018/5/6
 * Time:10:14
 */
@NotThreadSafe
public class SingletonExample4 {

    //将默认构造方法设为私有,保证类只能初始化一次,不能通过new创建类对象
    private SingletonExample4(){

    }

    //定义单例对象,每次返回同一个对象
    private static SingletonExample4 instance = null;

    //提供静态的工厂方法返回当前实例
    public static  SingletonExample4 getInstance(){
        if (instance == null){
            //双重检测机制
            synchronized (SingletonExample4.class) {//同步锁
                if (instance == null) {
                    instance = new SingletonExample4();
                }
            }
        }
        return instance;
    }


}

为什么线程不安全:

创建类实例的步骤:

1、memory = allocate()分配对象内存空间

2、ctorInstance()初始化对象

3、instance = memory 指针重定向

但由于JVM和CPU优化,多线程环境下可能会发生指令重排序,顺序变为1、3、2。这样一来可能就会出现一个线程执行完

instance = new SingletonExample4();
一行代码之后,另一个线程进行到第一次检测instance是否为null,检测到不为null便直接return instance

,但是这时还没有初始化,使用返回的instance调用其中方法便可能会报空指针异常。

因此只要使用volatile关键字限制指令重排序即可保证线程安全:
package com.concurrency.example.example.singleton;

import com.concurrency.example.annotations.NotThreadSafe;
import com.concurrency.example.annotations.ThreadSafe;

/**
 * 懒汉模式 -> 双重同步锁+volatile实现单例模式(线程安全)
 * 单例的实例在第一次使用时进行创建
 * Created with IDEA
 * author:LuXiaoWei
 * Date:2018/5/6
 * Time:10:14
 */
@ThreadSafe
public class SingletonExample5 {

    //将默认构造方法设为私有,保证类只能初始化一次,不能通过new创建类对象
    private SingletonExample5(){

    }

    //定义单例对象,每次返回同一个对象。使用volatile+双重检测机制限制指令重排实现线程安全
    private volatile static SingletonExample5 instance = null;

    //提供静态的工厂方法返回当前实例
    public static SingletonExample5 getInstance(){
        if (instance == null){
            //双重检测机制
            synchronized (SingletonExample5.class) {//同步锁
                if (instance == null) {
                    instance = new SingletonExample5();
                }
            }
        }
        return instance;
    }


}

使用static静态代码块实现线程安全的饿汉模式:

package com.concurrency.example.example.singleton;

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

/**
 * 饿汉模式
 * 单例的实例在类装载时(使用static静态代码块)进行创建(线程安全)
 * 使用饿汉模式时,私有构造方法中代码过多或类实例没有被使用会降低性能,
 * 因此应该保证私有构造方法中没有过多处理且类实例肯定会被使用,避免线程浪费
 * Created with IDEA
 * author:LuXiaoWei
 * Date:2018/5/6
 * Time:10:14
 */
@Slf4j
@ThreadSafe
public class SingletonExample6 {

    //将默认构造方法设为私有,保证类只能初始化一次,不能通过new创建类对象
    private SingletonExample6(){

    }

    //定义单例对象,每次返回同一个对象,这段声明代码必须在下面的静态代码块上面,否则会报空指针异常
    private static SingletonExample6 instance = null;


    //使用静态块初始化
    static {
        instance = new SingletonExample6();
    }

    //提供静态的工厂方法返回当前实例
    public static SingletonExample6 getInstance(){
        return instance;
    }

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

}

使用枚举创建(推荐):

package com.concurrency.example.example.singleton;

import com.concurrency.example.annotations.Recommend;
import com.concurrency.example.annotations.ThreadSafe;

/**
 * 枚举模式:最安全
 * Created with IDEA
 * author:LuXiaoWei
 * Date:2018/5/6
 * Time:10:46
 */
@ThreadSafe
@Recommend
public class SingletonExample7 {
    //私有构造函数
    private SingletonExample7(){

    }

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

    private enum Singleton{
        INSTANCE;

        private SingletonExample7 singleton;

        //JVM保证绝对只被调用一次
        Singleton(){
            singleton = new SingletonExample7();
        }

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

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值