什么是线程安全问题?如何确保线程安全?进来看看就明白了!!

🌈🌈🌈今天给大家分享的是:什么是线程安全,在程序中多线程并发执行的时候,是否会产生线程不安全问题,以及如何解决线程不安全问题。

清风的CSDN博客

🛩️🛩️🛩️希望我的文章能对你有所帮助,有不足的地方还请各位看官多多指教,大家一起学习交流!🛩️🛩️🛩️

✈️✈️✈️动动你们发财的小手,点点关注点点赞!在此谢过啦!哈哈哈!✈️✈️✈️

目录

 一、线程安全的概念

二、观察线程不安全 

三、解决线程不安全问题 

 3.1 线程安全问题的原因

3.2 解决方案 


 一、线程安全的概念

想给出一个线程安全的确切定义是复杂的,但我们可以这样认为: 如果多线程环境下代码运行的结果是符合我们预期的,即在单线程环境应该的结果,则说这个程序是线程安全的。

二、观察线程不安全 

首先,我们写一个多线程代码,一个线程负责把一个数加50000次,另一个线程也负责把这个数加50000次。(从0开始加)

class Counter{
    private int count = 0;
    public void increase(){
        count++;
    }

    public int getCount() {
        return count;
    }
}
public class Test4 {
    public static void main(String[] args) throws InterruptedException {
        Counter counter = new Counter();
        Thread t1 = new Thread(()->{
            for (int i = 0; i < 50000; i++) {
                counter.increase();
            }
        });

        Thread t2 = new Thread(()->{
            for (int i = 0; i < 50000; i++) {
                counter.increase();
            }
        });
        t1.start();
        t2.start();
        t1.join();
        t2.join();
        System.out.println(counter.getCount());
    }
}

程序运行结果如下:

按照我们的逻辑,希望最后得到的结果是100000,为什么会出现这么大的偏差呢?其实,这样的运行结果是由于线程不安全导致的。

事实上:上面的count++操作,在CPU指令角度看,本质上是三个操作

  • 把内存中的数据加载到CPU的寄存器中。(LOAD)
  • 把寄存器中的寄存器进行加一运行算。(ADD)
  • 把寄存器中的数据写回到内存中。

如果是单个线程执行,没有问题。但是如果是多个线程并发执行,就可能会出现错误。由于CPU调度线程的顺序是不确定的,因此这两个线程并发执行的过程中,线程1的操作可能会和线程2的操作相互影响,也就是说,这两个线程的命令的排列方式可能有很多种:

还有很多种情况,我就不一一列举了。

如果是按照下面的情况,那么结果是没有问题的:

执行完线程1后,继续执行线程2,结果为2,是正确结果。 

那如果是按照下面的顺序,就会出现错误:

正常结果应该是2,但是这里的最终结果是1,发生了错误,虽然是自增两次,但是由于两个线程并发执行,就可能在一定的执行顺序下,导致运算的中间结果被覆盖了。也就是发生了线程不安全。 导致结果出现问题,而且得到的这个错误值,一定小于10w。

那么,如何解决线程不安全问题呢?

三、解决线程不安全问题 

 3.1 线程安全问题的原因

  • 多个线程之间的调度顺序是随机的,操作系统使用抢占式执行的策略来调度线程。【根本原因】
  •  多个线程同时修改同一个变量,容易产生线程安全问题。
  • 进行的修改,不是原子性的。如果修改操作,能够按照原子的方式来完成,就不会出现线程安全问题。 
  • 内存可见性引起的线程安全问题。
  • 指令重排序,引起的线程安全问题。

3.2 解决方案 

 我们可以把修改操作改成“原子性”的操作,即可以进行加锁操作。相当于是把一组操作,打包成一个整体的操作。此处这里的原子,是通过锁进行“互斥”,当前线程执行的时候,其他线程无法执行。对于加锁,Java引入了一个synchornized关键字。

通过对increase方法加锁,进入方法,就会加锁;出了方法,就会解锁。 当t1加锁之后,t2也尝试进行加锁,此时t2就会阻塞等待。这样也就保证了原子性。

class Counter{
    private int count = 0;
    synchronized public void increase(){
        count++;
    }

     public int getCount() {
        return count;
    }
}
public class Test4 {
    public static void main(String[] args) throws InterruptedException {
        Counter counter = new Counter();
        Thread t1 = new Thread(()->{
            for (int i = 0; i < 50000; i++) {
                counter.increase();
            }
        });

        Thread t2 = new Thread(()->{
            for (int i = 0; i < 50000; i++) {
                counter.increase();
            }
        });
        t1.start();
        t2.start();
        t1.join();
        t2.join();
        System.out.println(counter.getCount());
    }
}

运行结果如下:


🌈🌈🌈好啦,今天的分享就到这里!

🛩️🛩️🛩️希望各位看官读完文章后,能够有所提升。

🎉🎉🎉创作不易,还希望各位大佬支持一下!

✈️✈️✈️点赞,你的认可是我创作的动力!

⭐⭐⭐收藏,你的青睐是我努力的方向!

✏️✏️✏️评论:你的意见是我进步的财富!

  • 29
    点赞
  • 26
    收藏
    觉得还不错? 一键收藏
  • 打赏
    打赏
  • 5
    评论

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

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

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

打赏作者

..清风

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

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

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

打赏作者

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

抵扣说明:

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

余额充值