【JAVAEE】多线程带来的风险---线程不安全的原因

目录

1.线程不安全的现象

1.1用自增操作演示线程不安全

1.2线程安全的概念

2.线程不安全的原因

2.1修改共享数据

2.2线程是抢占式执行的

2.3原子性

2.4内存可见性

Java内存模型------JMM

 2.5有序性


1.线程不安全的现象

1.1用自增操作演示线程不安全

举例:用两个线程对同一个共享变量做五万次自增操作

package lesson03;

public class Demo19_Synchronized {
    //定义自增操作的对象
    private static Counter19 counter=new Counter19();

    public static void main(String[] args) throws InterruptedException {
        //定义两个线程,分别自增5万次
        Thread t1=new Thread(()->{
            for (int i = 0; i < 50000; i++) {
                counter.increment();
            }
        });
        Thread t2=new Thread(()->{
            for (int i = 0; i < 50000; i++) {
                counter.increment();
            }
        });

        t1.start();
        t2.start();

        t1.join();
        t2.join();

        System.out.println("count="+counter.count);
    }
}
class Counter19{
    public int count=0;
    public void increment(){
        count++;
    }}

各五万次自增操作之后,count的值应该为10000。

来看看结果吧:

多运行几次,结果各不相同。

 

这是为什么呢?

1.2线程安全的概念

想给出线程安全的确切定义是复杂的,但是我们可以这样认为:

如果多线程环境下代码运行的结果是符合我们预期的,即在单线程环境应该的结果,则说明这个程序是线程安全的。

2.线程不安全的原因

2.1修改共享数据

多个线程修改了同一个共享变量。上述代码中就是多个线程修改了同一个共享变量counter。

补充:

多个线程修改不同的变量,不会出现线程安全问题

多个线程读取同一个变量,也不会出现线程安全问题

单线程环境下,也不会出现线程安全问题

2.2线程是抢占式执行的

⭐多个线程在CPU上的调用时随机的,顺序是不可预知的

2.3原子性

数据库中原子性的定义:指事务的不可分割性,一个事务的所有操作要么不间断地全部被执行,要么一个也没有执行。

那我们上面的代码,只有一条count++语句,那还需要原子性吗?其实不是的,这一条语句只是我们所看到的,而每一个代码最终都会编译成CPU可以执行的指令。

比如上面的count++语句,其实是由三步操作组成:

1.LOAD 从内存把数据读到CPU

2.ADD 进行数据更新

3.STORE 把数据写回到CPU

这里的原子性就是指上面这三个操作要么全部执行,要么全部不执行。

我们画图来看看:

正常串行执行是这样的:t1先执行完count++,t2再执行;或者t2先执行count++,t1再执行

由于线程是抢占式执行的,执行多少条指令也是不确定的,所以就会发送很多不正常的执行

情况①:t1先LOAD进内存中,这时CPU把这个线程调度走了,t2开始执行指令,t2执行完之后,又继续t1的执行

 情况②:

情况③:

情况④:

 以情况④为例我们来探究一下:

第一步:t2 LOAD

第二步:t1 LOAD

第三步:t1 ADD

第四步:t2 ADD

第五步:t1 STORE

第六步:t2 STORE

至此,t1,t2分别进行了一次自增运算,count最后应该为2.

 但是真正的运行过程和结果却不是这样的:

由于没有保证指令执行的原子性导致多个线程之间做了自增运算之后,互相不可见,从而覆盖掉了上一个线程修改过后的值 。

2.4内存可见性

可见性指,一个线程对共享变量值的修改,能够及时的被其它线程看到。

而在多线程环境下,某一个线程修改了共享变量的值,另一个线程没有感知到最新的值就造成了线程不安全现象。

这里需要引入一个Java内存模型的概念-------JMM。

Java内存模型------JMM

 JMM 是Java内存模型( Java Memory Model),简称JMM。它本身只是一个抽象的概念,并不真实存在,它描述的是一种规则或规范,是和多线程相关的一组规范。通过这组规范,定义了程序中对各个变量(包括实例字段,静态字段和构成数组对象的元素)的访问方式。需要每个JVM 的实现都要遵守这样的规范,有了JMM规范的保障,并发程序运行在不同的虚拟机上时,得到的程序结果才是安全可靠可信赖的。如果没有JMM 内存模型来规范,就可能会出现,经过不同 JVM 翻译之后,运行的结果不相同也不正确的情况。

JMM中最重要的概念就是主内存和工作内存。

⭐主内存:指的是硬件的内存条,进程在启动的时候会申请一些资源,包括内存资源,用来保存一些变量。

⭐工作内存:指的是线程独有的内存空间,它们之间不能够相互访问,起到了线程之间内存隔离的作用。

⭐JMM规定,一个线程在修改某个变量的值时,必须要把这个变量从主内存中加载到自己的工作内存,修改完成后再刷新回主内存。

⭐每个工作内存之间是相互隔离的。

为什么要用JMM?
因为Java是一个跨平台的语言,把不同的计算设备和操作系统对内存管理做了一个统一的封装。

 2.5有序性

一段代码是这样的:
1. 去前台取下 U
2. 去教室写 10 分钟作业
3. 去前台取下快递
如果是在单线程情况下, JVM CPU 指令集会对其进行优化,比如,按 1->3->2 的方式执行,也是没问题,可以少跑一次前台。这种叫做指令重排序
在编译过程中,JVM调用本地接口,CPU执行指令过程中,会有 指令的有序性的产生。
即:指令在特殊情况下会打乱顺序,如上述的例子,并不是按程序员的预期去执行的。
把不相关的代码或指令,做指令重排序,从而提高程序的效率。在单线程中重排序之后的结果必须是100%正确的,但在多线程环境中就未必是正确的。
  • 2
    点赞
  • 1
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

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

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

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值