Java 并发编程之核心理论

一、共享性

  数据共享性是线程安全的主要原因之一。如果所有的数据只是在线程内有效,那就不存在线程安全性问题,这也是我们在编程的时候经常不需要考虑线程安全的主要原因之一。但是,在多线程编程中,数据共享是不可避免的。最典型的场景是数据库中的数据,为了保证数据的一致性,我们通常需要共享同一个数据库中数据,即使是在主从的情况下,访问的也同一份数据,主从只是为了访问的效率和数据安全,而对同一份数据做的副本。我们现在,通过一个简单的示例来演示多线程下共享数据导致的问题:

 

            上述代码通过10个线程同时对次数进行加一操作,每个线程执行100次,正常情况下,应该输出1000。不过,如果你运行上面的程序,你会发现结果却不是这样,因为对共享变量操作,在多线程环境下很容易出现各种意想不到的的结果。下面是某次的执行结果(每次运行的结果不一定相同,有时候也可能获取到正确的结果):

 

二、互斥性

    互斥性是指同时只允许一个访问者访问某个资源。我们通常允许多线程同时对数据进行读操作,但是同一时间内只允许一个线程对数据进行写操作,这就是锁中的共享锁和互斥锁(排他锁),也叫读锁和写锁。在并发编程中,我们很多时候都必须要考虑线程安全问题,涉及到共享数据的写操作时,就必须要保证互斥性,java中提供了多种机制来保证互斥性,最常用的就是使用synchronized关键字或者lock锁,此时我们使用synchronized关键字对方法加锁后,无论你怎么执行,最终结果都会是1000:

 

三、原子性

  原子性就是指对数据的操作是一个独立的、不可分割的整体。通俗点说,就是一次操作、是一个连续不可中断的过程,数据不会执行的一半的时候被其他线程所修改。保证原子性的最简单方式是操作系统指令,就是说如果一次操作对应一条操作系统指令,这样肯定可以能保证原子性。但是很多操作不能通过一条指令就完成。例如,对long类型的运算,很多系统就需要分成多条指令分别对高位和低位进行操作才能完成。

       还比如,我们经常使用的整数 i++ 的操作,其实需要分成三个步骤:(1)读取整数 i 的值;(2)对 i 进行加一操作;(3)将结果写回内存。

       对于这种组合操作,要保证原子性,最常见的方式是加锁,如Java中的Synchronized或Lock都可以实现。除了锁以外,还有一种方式就是CAS算法(Compare And Swap),即修改数据之前先比较与之前读取到的值是否一致,如果一致,则进行修改,如果不一致则重新执行,这也是乐观锁的实现原理。

 

四、可见性

  

从这个图中我们可以看出,每个线程都有一个自己的工作内存,对于共享变量,线程每次读取的是工作内存中共享变量的副本,写入的时候也直接修改工作内存中副本的值,然后在某个时间点上再将工作内存与主内存中的值进行同步。这样导致的问题是,如果线程1对某个变量进行了修改,线程2却有可能看不到线程1对共享变量所做的修改。

 

五、有序性

    为了提高性能,编译器和处理器可能会对指令做重排序。重排序可以分为三种:

  (1)编译器优化的重排序。编译器在不改变单线程程序语义的前提下,可以重新安排语句的执行顺序。

        (2)指令级并行的重排序。在数据不存在依赖性的前提下,处理器可以改变语句对应机器指令的执行顺序。

        (3)内存系统的重排序。内存系统的重排序指的是:处理器使用缓存和读/写缓冲区造成的加载和存储可能是乱序执行。

    Java 语言提供了 volatile 和 synchronized 两个关键字来保证线程之间操作的有序性,volatile 是因为其本身包含“禁止指令重排序”的语义,synchronized 是由“一个变量在同一个时刻只允许一条线程对其进行 lock 操作”这条规则获得的,此规则决定了持有同一个对象锁的两个同步块只能串行执行。

 

 

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

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

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

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值