线程安全问题及其解决方案

一:线程安全问题的原因

1.线程是抢占式执行的,即具有随机性

2.多线程的情况下,线程同时去修改同一个数据或者有的线程在修改数据,有的线程在读取数据

3.一条语句对应多个指令,这些指令操作并不是原子的。指令有可能会相互穿插,从而数据的安全性就无法得到可靠保证

4.内存可见性问题:当我们有两个线程t1,t2在执行的时候,假如线程t2对数据进行了修改,然而线程t1读到的数据却是修改之前的数据。

5.指令重排序:主要也是由于编译器优化带来的问题

二:解决方案:

1.对于原因1,这是由操作系统内核实现的,我们一般情况下是无能为力的;

2.对于原因2,由于这是我们的具体的场景下的要求,我们要达到目的不能随意的更改需求,所以这里也没办法做什么文章;

3.对于原因3,这些操作指令不是原子的,那么我们可以想办法将这些指令1打包成一个原子的操作,即加锁。在java中,每一个类对象都会有一把锁,只有当线程持有这把锁的时候才可以继续向下执行,否则线程将会处于阻塞状态。因此我们可以用加锁的方式来保证线程安全,当线程持有对象锁之后,继续向下执行,直到代码块执行完才会释放锁。其他线程将会继续竞争这把锁,同1所述,最终谁能拿到锁,继续向下执行,这是由操作系统内核实现的,或者说具有随机性的,我们无法确定。

4.对于原因4,本质上是编译器在对我们的代码进行优化的时候,将我们的数据看作是不变的数据,因此只会读取一次就不再读取,所以在我们后续对数据进行更改以后,程序也读不到。解决方法就是通过volatile关键字来告诉编译器,在这里不需要对代码进行优化即可。

5.对于原因5,请看如下的场景:

class SingletonLazy {
    //懒汉模式
    private static SingletonLazy instance = null;
    public static SingletonLazy getInstance() {
        if(instance == null) {
            instance = new SingletonLazy();
        }
        return instance;
    }
    //将构造方法私有化,防止在类外被new
    private SingletonLazy() {}
}

对于if里面的new操作实际上也是可以分为许多步骤的,这里我们简单分为三个步骤:

1.申请内存,得到内存的首地址;

2.调用构造方法,来初始化实例;

3.把内存地址赋值给实例引用;

那么此时编译器可能就会对代码进行"指令重排序"的优化。

假设在这里的执行顺序是132,那么当一个线程执行到3时,他得到的就是一个内存地址(上面的数据时无效的),这个时候线程2调用了getInstance()方法的时候,他就会认为对象传创建好了,就会将他直接返回,我们在外边也有可能会对这个不完全的对象进行解引用操作(调用里面的方法,使用属性),这就造成了线程的不安全。

解决办法同样也是告诉编译器在这里不需要进行优化即可

我们一般使用volatile或者synchronized关键字

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

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

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

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包

打赏作者

假若爱有天意

你的鼓励将是我创作的最大动力

¥1 ¥2 ¥4 ¥6 ¥10 ¥20
扫码支付:¥1
获取中
扫码支付

您的余额不足,请更换扫码支付或充值

打赏作者

实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

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

余额充值