java 多线程:原子性与可视性

来源:互联网 发布:九次方大数据 上市 编辑:程序博客网 时间:2024/05/16 15:10

一、定义

 原子性

      原子是发生化学中的最小单位,即原子具有不可再拆分、不可分割的特点。顾名思义,原子操作具有不可中断性,即某个线程进入该原子操作后,就不会被中断,直到其执行完成(如:赋值

   1)对于读写除longdouble之外的基本类型变量的简单操作,可以保证它们的原子性来操作内存,但JVM可将longdouble这样的64位的变量拆分成两个分离的32位来操作,这样很可能在一个读取和写入操作之间切换到其它线程,从而导致错误的结果。

     2) 类似a+=2的操作不具备原子性,因为在JVM中这个操作需要三个步骤:

             1 取出a

             2计算a+2

             3 将计算结果写入内存

       在上述步骤之间很可能线程调度器中断,转向另一个任务,这个任务可能修改这个域,造成结果错误,所以这个操作不是原子性的。

       同样a++(即a=a+1)也不具备原子性。(注:在C++中以上这两种操作都是原子性的)

  2 可视性

    1)在多核处理器中,如果多个线程同时对一个变量进行操作,这些线程可能被分配到不同的CPU中运行。由于编译器会对代码进行优化,当某个线程要对这个变量进行操作时,为了提高操作速度,这个线程所在的CPU会从主存中复制这个变量到自己的缓存中,等操作完成后再存储到主存中。因此不同的线程对应的这个变量就有不同的状态。(注:在单核处理器中也存在可视性问题)

    2 变量的可视性:假设有两个线程T1T2分别被安排到了两个不同的CPU(cpu1cpu2),则T1T2对变量a的修改互不可视,如T1a进行修改之后,只是对cpu1缓存中的a运行修改,没有立即被写入到主存中,因此当线程T2再对a进行操作时,操作的并不是被线程T1修改后的新值。此时线程T1和线程T2对于变量a是互不可视的。

    3)不可视性产生的问题:多个线程对某个变量的操作互不可视,可能造成某些操作被覆盖,产生错误的结果。如对于变量a,线程T1和线程T2都对其进行a++操作,T1操作a++后并没有及时地将结果写入到主存中去,而是继续执行其它对a的操作,当T2再执行a++操作后,它并没有发现a的值已被线程T1修改,这样就由于a的值没有被及时更新而产生错误。

二  volatilesynchronized关键字在原子性和可视性中的应用

   1)当对longdouble类型的变量用关键字volatile修饰时,就能获得简单操作(赋值和return)的原子性。但除对longdouble简单类型的简单操作外,volatile并不能提供原子性,即使对一个变量用volatile修饰,对这个变量的操作也不是原子性的。

   2volatile与可视性:volatile赋予变量可视性,它修饰的变量每次被线程访问时都要从主存中重新读取该变量的值,并且当变量的值发生变化时会强迫线程刷新到主存中去这样在任何时刻,从不同的线程看某一变量都是相同的值,从而保证了变量的可视性。

       使用场合:如果多个线程同时访问某个变量,那个这个变量就应该用volatile修饰,如果这个变量已在由synchronized修饰的方法或代码块中,则不必再用volatile修饰。(注:优先选择使用synchronized关键字,因为这是最安全的方式)

       原理:为了获得最佳速度,允许线程保存共享成员变量的私有拷贝,而且只当线程进入或者离开同步代码块时才与共享成员变量的原始值对比。这样当多个线程同时与某个对象交互时,就必须要注意到要让线程及时的得到共享成员变量的变化。而volatile关键字就是提示JVM:对于这个成员变量不能保存它的私有拷贝,而应直接与共享成员变量交互。 因此当要访问的变量已在synchronized代码块中(因为线程进入或离开同步代码块时会更新共享变量的值),或者为常量时,不必再使用volatile。由于使用volatile屏蔽掉了JVM中必要的代码优化,所以在效率上比较低。

    3synchronized关键字与操作原子性:synchronized为一段操作或内存加锁,具有互斥性。当线程要操作被synchronized修饰的方法或代码块时,必须先获得锁。但是同一时刻只能有一个线程持有同一把锁(对象监示器),所以只允许一个线程对被synchronized修饰的方法或代码块进行操作。

      synchronized关键字可以看作是解决多线程原子性操作的方法,因为它拒绝同一时刻多个线程对同一资源的访问。



 参考资料:《Java 编程思想》 第四版

0 0
原创粉丝点击