一篇文章总结了JVM线程安全

版权声明:本文为博主思考总结而得,只做抛砖引玉,欢迎大家转载指导 https://blog.csdn.net/hayre/article/details/81013085

继上一篇文章的知识点,继续总结JVM里面的线程安全

一、什么是线程安全

一个老生常谈的问题,如果一个对象可以安全的被多个线程同时使用,那它就是线程安全的。
当多个线程访问一个对象时,如果不用考虑这些线程在运行时环境下的调度和交替执行,也不需要进行额外的同步,或者在调用方进行任何的其他的协调操作,调用这个对象的行为都可以获得正确的结果,那这个对象是线程安全的。

二、线程安全代码的特征

代码本身封装了所有必要的正确性保障(如互斥同步等),令调用者无须关心多线程的问题,更无须自己采取任何措施来保证多线程的正确调用。

三、JAVA语言中所谓的线程安全是什么?分为什么程度?

1 不可变对象(绝对安全的)

不可变的对象一定是线程安全的。
保证对象行为不影响自己状态的途径有很多种,其中最简单的就是把对象中带有状态的变量都声明为final。
JDK中的不可变对象

  • String
  • Number部分子类
  • Long
  • Double
  • BigInteger
  • BigDecimal

2 绝对线程安全

指的是不管运行时环境如何,调用者都不需要任何额外的同步措施。
JAVA API 中标注自己是线程安全的类,大多数都不是绝对的线程安全,因为只是限制在使用某些方法时是原子性操作,但是如果调用多个方法,那么便不是原子性操作了。比如并发去操作Vector安全类,结果却是错的,所以不是绝对线程安全的。

3 相对线程安全

就是我们通常意义上所讲的线程安全,需要保证对这个对象的单独操作是线程安全的,但是对于一些特定顺序的连续调用,就可能需要在调用端使用额外的同步手段来保证调用的正确性。

JDK中的相对线程安全类

  • Vector
  • HashTable
  • Collections的synchronizedCollection()方法包装的集合

4 线程兼容

是指对象本身并不是线程安全的,但是通过调用端正确的使用同步手段来保证对象在并发环境中可以安全的使用。

5 线程对立

是指无论调用端是否采取了同步措施,都无法在多线程中并发使用的代码。
由于Java语言天生就具备多线程特性,线程对立这种排斥多线程的代码是很少出现的,而且通常都是有害的,应当尽量避免。

四、JVM线程安全的实现方法

虚拟机提供的同步和锁机制对线程安全起到非常重要的作用,只要了解虚拟机线程安全手段的运作过程,自己去思考代码如何编写并不是一件困难的事情。

主要方法如下:

扫描二维码关注公众号,回复: 5183134 查看本文章

1 互斥同步(阻塞同步,悲观策略)

同步是指在多个线程并发访问共享数据时,保证共享数据在同一时刻只被一个或者一些线程使用。

互斥是实现同步的一种手段。以下是实现互斥的手段:

  • 临界区
  • 互斥量
  • 信号量

互斥是因,同步是果,互斥是方法,同步是目的。

互斥在代码中是如何体现的?

synchronized
经过编译之后,会在同步块的前后分别形成monitorenter和monitorexit两个字节码指令,这两个字节码都需要一个reference类型的参数来指明要锁定和解锁的对象。如果Java程序中的synchronized明确指定了对象参数,那就是这个对象的reference:如果没有明确指定,那就根据synchronized修饰的是实例还是类方法,去取对应的对象实例或Class对象来作为锁对象。

主要表现为以下特征:

  • 对同一线程是可重入的
  • 同步块在已进入的线程执行完之前,会阻塞后面其他线程的进入
  • synchronized是重量级操作,会执行比较长的时间,阻塞或唤醒一个线程,都需要操作系统来帮忙完成,这需要从用户态转换到核心态中。
  • 虚拟机会对synchronized进行一些优化

ReentrantLock
与synchronized功能类似,主要表现以下特征:

  • 等待可中断。当持有锁的线程长期不释放锁,正在等待的线程可以选择放弃等待,改为处理其他事情
  • 公平锁。多个线程在等待同一个锁时,必须按照申请锁的时间顺序来依次获得锁;synchronized的锁是非公平的
  • 锁定绑定多个条件。一个ReentrantLock对象可以同时绑定多个Condition对象,而synchronized中只能有一个条件

JDK1.6之前synchronized的性能比较差,之后的版本的性能都做了优化,所以JDK鼓励用synchronized

2 非阻塞同步(非阻塞同步,乐观策略)

主要是为了解决、克服线程阻塞和唤醒所带来的性能问题。
这是基于冲突检测的乐观并发策略。
先进行操作,如果没有其他线程争用共享数据,那操作就成功了;如果共享数据有争用产生了冲突,那就再采取其他补偿措施(最常见的补偿措施就是不断地重试,直到成功为止)。

主要的体现:

CAS指令(这是一条硬件指令,因为能保证从语义行看起来需要多次操作的行为只通过一条处理器指令就能完成)

需要有三个操作数,分别是内存位置(在Java中可以简单理解为变量的内存地址,用V表示)、旧的预期值(用A表示)和新值(用B表示)。CAS指令执行时,当且当V符合旧预期值A时,处理器用新值B更新V的值,否则它就不执行更新,但是无论是否更新了V的值,都会返回V的旧值,上述的处理过程是一个原子操作。

JDK1.5之后,JAVA程序中才可以使用CAS操作,该操作由sun.misc.Unsafe里面的几个方法包装提供,虚拟机在内部对这些方法做了特殊处理,即时编译出来的结果就是一条平台相关的处理器CAS指令,没有方法调用过程,或者可以认为是无条件内联进去。只有启动类加载器加载的类才能访问。

存在ABA问题,但是一般不会影响程序结果的正确性。

3 无同步方案

3.1 如果一个方法本来就不涉及共享数据,那它自然就无须任何同步措施去保证正确性。

3.2 可重入代码
在代码执行的任何时刻中断它,转而去执行另一段代码,在控制权返回后,原来的程序不会出现任何错误

  • 不依赖存储在堆上的数据和公用的系统资源,用到的状态量都由参数传入、不调用非可重入方法等
  • 如果一个方法,它的返回结果是可以预测的,只要输入了相同的数据,就都可能返回相同的结果

3.3 线程本地存储

  • 把共享数据的可见范围控制在同一个线程之内,这样无须同步也能保证线程之间不出现数据争用问题
  • 消费队列的架构模式
  • ThreadLocal类

猜你喜欢

转载自blog.csdn.net/hayre/article/details/81013085