线程安全
线程安全是以多个线程之间存在共享数据访问为前提的,java语言中各种操作共享的数据分为以下五类:
1. 不可变
只要一个不可变的共享对象被正确地创建,那其外部的可见状态永远不会改变,永远不会出现在其他线程中数据不一致的状态。不可变对象天生是线程安全的。
比如java.lang.String、枚举类型、以及java.lang.Number的部分子类比如Long,Double等、BigInteger,BigDecimal等大数据类型都是不可变的。
2. 绝对线程安全
不管运行时环境如何,调用者都不需要任何额外的同步措施保证数据线程安全。Java API中标注自己是线程安全的类大多数都不是绝对的线程安全。
3. 相对线程安全
保证对象单个读或写操作是线程安全的,大多数类不能保证读写复合操作的线程安全,需要调用额外的同步手段来保证。
4. 线程兼容
对象本身不是线程安全的,但是可以通过在调用端使用同步手段来保证对象在并发环境中安全地使用。
5. 线程对立
互斥的线程,无论是否调用同步手段都不可能并发执行。
线程安全的实现方式
1. 互斥同步
同步是指多个线程并发访问共享数据时,保证共享数据在同一时刻只被一个或一些(当使用信号量时)线程使用,
互斥是实现同步的手段,临界区(Critical Section)、互斥量(Mutex)和信号量(Semaphore)都是常见的互斥方式。
Java中最基本的实现互斥的手段就是sychronized关键字,这是一种块结构的同步语法,需要一个reference类型的参数来指定锁定和解锁的对象。sychronized关键字经Javac编译器编译后,会在同步代码块前后生成monitorenter和monitorexit两个字节码指令,控制持有锁的线程进代码块和出代码块释放锁,获取锁失败的线程则会阻塞等待。
JDK 5 之后,java.util.concurrent包下的lock接口提供一种新的互斥同步手段。
互斥同步是一种悲观策略,无论是否有其他线程竞争资源,互斥同步都直接加锁,无条件地阻塞其他线程,这就涉及到线程调度线程模型、线程状态、线程调度,不可避免地陷入用户态到内核态的转换中,尤其是简单的读写(getter和setter)方法的加锁,上下文切换耗时比线程执行时间还要长。
2. 非阻塞同步
互斥同步面临的主要问题是线程阻塞唤醒时存在的线程上下文切换带来的性能开销。随着硬件指令集的发展,基于冲突检测的乐观并发策略成了新的同步手段。线程先执行操作,如何没有其他线程竞争资源,则直接操作成功,如果存在竞争,则进行其他的补偿措施,比如典型的CAS的补偿措施就是不断重试。由于乐观并发策略不会将线程阻塞挂起,所以也成为无锁编程。