当前位置: 主页 > JAVA语言

java 线程安全解决方案-java线程中有线程

发布时间:2023-02-08 16:19   浏览次数:次   作者:佚名

本文分享自华为云社区《【高并发】如何安全发布对象(包括各种单例代码解析)-云社区-华为云》,作者:冰河。

今天给大家带来一篇关于如何在高并发环境下安全发布对象实例的技术文章。

发布对象:使对象可用于当前范围之外的代码

对象溢出:是一种假释放,让一个对象在还没有被构造出来的时候对其他线程可见

不安全的帖子示例代码:

package io.binghe.concurrency.example.publish;
import lombok.extern.slf4j.Slf4j;
import java.util.Arrays;
@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()));
    }
}

其中,每个线程都可以获取UnsafePublish类的私有成员变量states,修改states数组的元素值,导致其他线程获取的states元素值不确定。

对象溢出示例代码:

package io.binghe.concurrency.example.publish;
import lombok.extern.slf4j.Slf4j;
@Slf4j
public class Escape {
    private int thisCanBeEscape = 0;
    public Escape(){
        new InnerClass();
    }
    private class InnerClass{
        public InnerClass(){
            log.info("{}", Escape.this.thisCanBeEscape);
        }
    }

java 线程安全解决方案_线程死锁怎么解决_java线程中有线程

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

其中,内部类InnerClass的构造方法中包含了对封装实例Escape的隐式引用(体现在InnerClass的构造方法中引用了Escape.this),在对象正确构造之前会被释放。 可能存在不安全因素。

导致 this 在构造期间溢出的错误:在构造函数中启动线程,无论是隐式还是显式,都会溢出 this 引用(因为新线程总是在构造所属对象之前初始化(已经看到 this 引用)。 因此,如果要在构造函数中创建线程,不要在构造函数中启动线程,可以使用专有的start()方法或者初始化方法统一启动线程,可以使用工厂方法和私有方法constructor 完成Object的创建和监听器的注册,然后统一启动线程,避免溢出。

注意:对象在构造之前,是不能释放的

如何安全地发布对象:

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

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

(3) 在一个正确构造的对象的final类型字段中保存对对象的引用

(4) 将对对象的引用保存在受锁保护的字段中

接下来我们看几个单例对象的示例代码,有的是线程安全的,有的不是。 你需要细细品味。 这些代码也经过了冰河本人在高并发环境下的测试验证。

代码一:SingletonExample1

这个类是惰性模式java 线程安全解决方案,不是线程安全的

package io.binghe.concurrency.example.singleton;
/**
 * @author binghe
 * @version 1.0.0
 * @description 懒汉模式,单例实例在第一次使用的时候进行创建,这个类是线程不安全的
 */
public class SingletonExample1 {
    private SingletonExample1(){}
    private static SingletonExample1 instance = null;
    public static SingletonExample1 getInstance(){
        //多个线程同时调用,可能会创建多个对象
        if (instance == null){
            instance = new SingletonExample1();
        }
        return instance;
    }
}

代码二:SingletonExample2

饿汉模式,类加载时创建单例实例,线程安全

package io.binghe.concurrency.example.singleton;
/**
 * @author binghe

java线程中有线程_java 线程安全解决方案_线程死锁怎么解决

* @version 1.0.0 * @description 饿汉模式,单例实例在类装载的时候进行创建,是线程安全的 */ public class SingletonExample2 { private SingletonExample2(){} private static SingletonExample2 instance = new SingletonExample2(); public static SingletonExample2 getInstance(){ return instance; } }

代码 3:SingletonExample3

Lazy模式,单例实例在第一次使用时创建,该类是线程安全的,但不推荐这种写法

package io.binghe.concurrency.example.singleton;
/**
 * @author binghe
 * @version 1.0.0
 * @description 懒汉模式,单例实例在第一次使用的时候进行创建,这个类是线程安全的,但是这个写法不推荐
 */
public class SingletonExample3 {
    private SingletonExample3(){}
    private static SingletonExample3 instance = null;
    public static synchronized SingletonExample3 getInstance(){
        if (instance == null){
            instance = new SingletonExample3();
        }
        return instance;
    }
}

代码4:SingletonExample4

惰性模式(双锁同步锁单例模式),单例实例在第一次使用时创建,但是这个类不是线程安全的! ! ! ! !

package io.binghe.concurrency.example.singleton;
/**
 * @author binghe
 * @version 1.0.0
 * @description 懒汉模式(双重锁同步锁单例模式)

java 线程安全解决方案_线程死锁怎么解决_java线程中有线程

* 单例实例在第一次使用的时候进行创建,这个类不是线程安全的 */ public class SingletonExample4 { private SingletonExample4(){} private static SingletonExample4 instance = null; //线程不安全 //当执行instance = new SingletonExample4();这行代码时,CPU会执行如下指令: //1.memory = allocate() 分配对象的内存空间 //2.ctorInstance() 初始化对象 //3.instance = memory 设置instance指向刚分配的内存 //单纯执行以上三步没啥问题,但是在多线程情况下,可能会发生指令重排序。 // 指令重排序对单线程没有影响,单线程下CPU可以按照顺序执行以上三个步骤,但是在多线程下,如果发生了指令重排序,则会打乱上面的三个步骤。 //如果发生了JVM和CPU优化,发生重排序时,可能会按照下面的顺序执行: //1.memory = allocate() 分配对象的内存空间 //3.instance = memory 设置instance指向刚分配的内存 //2.ctorInstance() 初始化对象 //假设目前有两个线程A和B同时执行getInstance()方法,A线程执行到instance = new SingletonExample4(); B线程刚执行到第一个 if (instance == null){处, //如果按照1.3.2的顺序,假设线程A执行到3.instance = memory 设置instance指向刚分配的内存,此时,线程B判断instance已经有值,就会直接return instance; //而实际上,线程A还未执行2.ctorInstance() 初始化对象,也就是说线程B拿到的instance对象还未进行初始化,这个未初始化的instance对象一旦被线程B使用,就会出现问题。 public static SingletonExample4 getInstance(){ if (instance == null){ synchronized (SingletonExample4.class){ if(instance == null){ instance = new SingletonExample4(); } } } return instance; } }

线程不安全分析如下:

当执行 instance = new SingletonExample4(); 代码行,CPU将执行以下指令:

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

2. ctorInstance() 初始化对象

3.instance = memory 设置instance指向刚刚分配的内存

简单的执行以上三步没有错,但是在多线程的情况下,可能会出现指令重排序。

指令重新排序对单个线程没有影响。 在单线程下,CPU可以依次执行以上三个步骤。 但是,在多线程下,如果发生指令重排序,上述三个步骤就会被打乱。

如果发生 JVM 和 CPU 优化,当发生重新排序时,可能会按以下顺序执行:

java 线程安全解决方案_线程死锁怎么解决_java线程中有线程

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

2.instance = memory 设置instance指向刚刚分配的内存

3. ctorInstance() 初始化对象

假设当前有两个线程A和B同时执行getInstance()方法,A线程执行到instance = new SingletonExample4(); B线程只是执行到第一个if (instance == null){,如果按照1.3.2 假设线程A执行到3.instance = memory并设置instance指向刚刚分配的内存。 这时线程B判断instance有值,直接返回instance; 实际上线程A还没有执行2.ctorInstance()初始化对象,也就是说线程B获取到的实例对象还没有初始化。 这个未初始化的实例对象一旦被线程B使用java 线程安全解决方案,就会出现问题。

代码五:SingletonExample5

惰性模式(双锁同步锁单例模式)单例实例在第一次使用时创建。 该类是线程安全的,采用volatile+双重检测机制,禁止指令重排,实现线程安全

package io.binghe.concurrency.example.singleton;
/**
 * @author binghe
 * @version 1.0.0
 * @description 懒汉模式(双重锁同步锁单例模式)
 *              单例实例在第一次使用的时候进行创建,这个类是线程安全的
 */
public class SingletonExample5 {
    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;
    }
}

代码六:SingletonExample6

饿汉模式,类加载时创建单例实例(使用静态代码块),线程安全

package io.binghe.concurrency.example.singleton;
/**
 * @author binghe
 * @version 1.0.0
 * @description 饿汉模式,单例实例在类装载的时候进行创建,是线程安全的
 */
public class SingletonExample6 {
    private SingletonExample6(){}

java 线程安全解决方案_java线程中有线程_线程死锁怎么解决

private static SingletonExample6 instance = null; static { instance = new SingletonExample6(); } public static SingletonExample6 getInstance(){ return instance; } }

代码七:SingletonExample7

枚举方法被实例化,是线程安全的,这个方法也是最线程安全的

package io.binghe.concurrency.example.singleton;
/**
 * @author binghe
 * @version 1.0.0
 * @description 枚举方式进行实例化,是线程安全的,此种方式也是线程最安全的
 */
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;
        }
    }
}

戳下方第一时间了解华为云的新鲜技术~

华为云博客_大数据博客_人工智能博客_云计算博客_开发者中心-华为云

#华为云开发者联盟#