Java-线程安全

版权声明:欢迎转载,请注明作者和出处 https://blog.csdn.net/baichoufei90/article/details/85019179

Java-线程安全

0x01 什么是线程安全

线程安全是针对某个对象来说,如果当多线程访问此对象时,不用再用额外方式如同步锁等,总能运行获得正确结果,那就可以说这个对象代码线程安全。

0x02 Java中的线程安全

Java中线程安全强度由强到弱是:
不可变 -> 绝对线程安全 -> 相对线程安全 -> 线程兼容 -> 线程对立

2.1 不可变

就是指那些一旦初始化就不可改变的对象,这当然是线程安全的:

  • final修饰的基本数据类型
  • final修饰的不可变对象如String, 基本类型的封装类型(Long,Integer等)(注意this指针不可逃逸)

这类对象永远不会改变,多线程状态下也永远处于一致的状态。

这种方式实现不可变最简洁,但使用场景较少。

2.2 绝对线程安全

不用考虑运行时环境,调用着不用加任何同步措施,就能保证多线程情况下拥有正确结果。这很难达到,Java中很多所谓线程安全类都不是绝对线程安全。

比如Vector,他每个方法都是synchronized的,但如果多线程同时读写,可能会刚刚某线程remove了某个序号上的元素,另一个线程去访问这个序号的元素就可能导致报错。解决方案就是读写方法都加入synchronized这个vector实例。

2.3 相对线程安全

我们上面例子中的Vector就是相对线程安全,就是在单独考虑是是线程安全,但在某些特定的组合顺序执行时需要额外手段处理。

2.4 线程兼容

就是说本身并不线程安全,需要额外的如同步锁之类的手段确保线程安全。

常用的容器都是此类,如HashMap, HashSet等。

2.5 线程对立

指那些无法通过手段达到线程安全目的的代码。

suspend的时候加同步锁,那他会一直持有锁直到resume。而当对这个线程调用resume之前也申请同步锁,那就肯定发生死锁。

0x03 线程安全实现

3.1 互斥同步(悲观同步)

  • 互斥是实现同步的手段,主要互斥方法有临界区, Mutex, Semaphore
  • 同步指多线程访问共享数据时,只能被一个线程使用(如果用Semaphore就是若干个)

常见实现方法如下:

  • synchronized
  • java.util.concurrentReentrantLock

本方式无论如何都是先加锁,所以也称为悲观同步。

3.2 非阻塞同步(乐观同步)

3.2.1 概念

上述互斥同步方式最大的开销是线程阻塞和唤醒,而且属于是悲观策略。

乐观同步常见的就是CAS(offset, expectVal, newVal)

该方法思想是,如果当前内存offset的值还是为expectVal,那就设为newVal;如果值不是我们期望的,就不改变。而且在这个过程是原子的

3.2.2 实现

  • java.util.concurrent.atomic包内的一些类就有如compareAndSet这样封装好了的cas方法。

  • Unsafe类也有许多cas的方法,并在jdk源码中大量采用,但最好不要直接去用这个类。

3.2.3 问题

CAS有个问题就是ABA问题,详见Java-并发-CAS

3.3 无同步实现线程安全

主要是一些不会访问共享变量的情况,他们是线程安全的。

3.3.1 可重入代码

一个方法的结果只依赖参数,也就是说结果完全可以依据传入参数预测。

3.3.2 线程本地存储

ThreadLocal, 即线程独有变量。详见Java-多线程-ThreadLocal全解析

0x04 关于锁

关于锁的内容,可以参考另一篇文章Java-并发-LockLike

参考文档

《深入理解Java虚拟机-第二版》 作者周志明

猜你喜欢

转载自blog.csdn.net/baichoufei90/article/details/85019179
今日推荐