多线程--06--多线程安全问题解决--01--总述--01--如何解决、在解决什么?

一、多线程安全问题的解决

1.1 为了避免多线程安全问题发生,有多种手段可以达到目的:

(1)只读变量

 类变量、成员变量或局部变量只读不写

(2)不共享变量

(3)不可变类---->参考:多线程--06--多线程安全问题解决--02--不可变类方式

 (4)非阻塞式的解决方案(乐观锁):原子变量---->参考:多线程--06--多线程安全问题解决--03--无锁方式--原子类

(5)阻塞式的解决方案(悲观锁):synchronized,Lock

二、多线程安全问题解决的本质

解决多线程安全问题的本质,实际是解决并发的三个问题:原子性、可见性、有序性。

要想并发程序正确地执行,必须要保证原子性、可见性以及有序性。只要有一个没有被保证,就有可能会导致程序运行不正确。

三、 原子性

原子性的一般定义是:在一次或多次操作中,要么所有的操作都成功执行并且不会受其他因素干扰而中断,要么所有的操作都不执行或全部执行失败,不会出现中间状态,即所谓的“要么都成功、要么都失败、要么都不执行”。

在多线程中原子性可以理解为:同一同步代码块,同一时刻,只能有一个线程运行(即同步代码块中,不会出现指令交错,就是原子性的)。(注意:原子操作 + 原子操作 != 原子操作)

3.1 举例

举例1:i++不是原子操作

举例2:Object类提供的wait()方法,会在线程拿到锁之后释放锁,会不会破坏原子性?

答案是不会。因为wait虽然释放了锁,但其它线程执行时,也只有获得锁的一个线程执行,依旧是同一时刻只有一个线程执行。

这也是为什么wait 需要和 synchronized 一起用 的原因。

四、可见性

可见性是指一个线程对共享变量进行修改,对其它线程应可见。

4.1  不可见性举例--退不出的循环

对于共享变量,如果不特殊处理,则在多线程执行时,一个线程的修改操作,会对其它线程不可见,导致多线程执行结果不正确。例如如下代码中的共享变量run。

五、有序性:是指程序中代码的执行顺序。

  • java为了提高程序运行效率,在不影响单线程程序执行结果的前提下,会对代码进行重排序优化,以尽可能地提高并行度。
  • 编译器、处理器都遵循这样一个目标。
  • 单线程时,指令重排不会出现问题,但在多线程环境下,指令重排会影响程序正确性,因此在多线程环境下,需要禁止指令重排。

5.1 指令重排导致多线程结果不正确举例

        int i = 0;
        boolean flag = false;
        i = 1;                //语句1  
        flag = true;          //语句2

           上面代码定义了一个int型变量,定义了一个boolean类型变量,然后分别对两个变量进行赋值操作。从代码顺序上看,语句1是在语句2前面的,那么JVM在真正执行这段代码的时候会保证语句1一定会在语句2前面执行吗?不一定,为什么呢?这里可能会发生指令重排序(Instruction Reorder)。

           一般来说,处理器为了提高程序运行效率,可能会对输入代码进行优化,它不保证程序中各个语句的执行先后顺序同代码中的顺序一致,但是它会保证单线程中程序最终执行结果和代码顺序执行的结果是一致的。比如上面的代码中,语句1和语句2谁先执行对最终的程序结果并没有影响,那么就有可能在执行过程中,语句2先执行而语句1后执行。但是要注意,虽然处理器会对指令进行重排序,但是它会保证程序最终结果会和代码顺序执行结果相同,再看下面一个例子:

        int a = 10;    //语句1
        int r = 2;    //语句2
        a = a + 3;    //语句3
        r = a*a;     //语句4

            这段代码有4个语句,那么可能的一个执行顺序是:语句2   语句1    语句3   语句4,但不可能的顺序是: 语句2   语句1    语句4   语句3。因为处理器在进行重排序时是会考虑指令之间的数据依赖性,如果一个指令Instruction 2必须用到Instruction 1的结果,那么处理器会保证Instruction 1会在Instruction 2之前执行。

           虽然重排序不会影响单个线程内程序执行的结果,但是多线程呢?下面看一个例子:

        //线程1:
        context = loadContext();   //语句1
        inited = true;             //语句2

        //线程2:
        while(!inited ){
            sleep()
        }
        doSomethingwithconfig(context);

           上面代码中,由于语句1和语句2没有数据依赖性,因此可能会被重排序。假如发生了重排序,在线程1执行过程中先执行语句2,而此时线程2会以为初始化工作已经完成,那么就会跳出while循环,去执行doSomethingwithconfig(context)方法,而此时context并没有被初始化,就会导致程序抛出空指针异常。因此,指令重排序不会影响单个线程的执行,但是会影响到线程并发执行的正确性。

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

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

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

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值