什么是线程安全?
多个线程不管以何种方式访问某个类,并且在主调代码中不需要同步,都能表现正确的行为。
线程安全如何实现?
Java中线程安全主要是通过同步互斥、非阻塞同步和无同步方案这三种手段实现的:
-
同步互斥:悲观并发策略,指多个线程并发访问共享数据时,保证共享数据段在同一时刻只能被一个线程访问。
- synchronized关键字,synchronized关键字可以给代码块、普通方法和静态方法加锁
- ReentrantLock(重入锁),相比synchronized增加了一些高级功能:
- 可实现公平锁,按照申请锁的顺序来依次获取锁;
- 可绑定多个条件condition,实现精确唤醒;
- 等待可中断,在等待的线程可以选择放弃等待,改为处理其他事情。
同步互斥最主要的问题就是线程的唤醒和阻塞带来的性能问题,因为Java的线程映射到操作系统的线程之上,如果要阻塞或者唤醒一个线程,都需要操作系统帮忙完成,从用户态转换到内核态,这种状态转换需要耗费很多处理器的时间。
-
非阻塞同步:乐观并发策略,基本思想是先进行操作,如果没有其他线程争用共享数据,那么操作成功;如果共享数据被争用,那么久采用其他的补偿措施,比如一直不断的重试,直到成功为止。这种操作主要由硬件指令集来实现,在Java中主要有以下几种实现:
-
CAS指令(比较与交换),主要包括三个操作数,分别为内存地址,期望值,旧值。当指令执行时,将内存地址的值和旧值进行比较,如果相同,则将值修改为期望值;否则就不执行。这个过程是一个原子操作。
CAS可能会出现ABA问题,当一个变量在初次读取时值为A,在准备赋值的过程中,这个值被改为了B,后来又被改为A,那CAS操作就会认为这个没有发生改变。
ABA问题解决办法:控制变量值的版本来保证,JUC提供了原子引用类来解决这个问题。
该操作由Unsafe类中的几个方法包装提供,不是提供给用户程序调用的类,因此不能采用反射的手段,只能通过其他的API来间接使用,如整数原子类。
-
原子类AtomicInteger,调用了Unsafe类的CAS操作,保证自增操作的原子性。
-
-
无同步方案:要保证线程安全,不一定就要进行同步。如果一个方法本来就不涉及共享数据,那么自然也不需要同步来保证正确性。
- 线程本地存储,通过TreadLocal类来实现线程本地变量副本存储。每个Thread对象有一个TreadLocal.TreadLocalMap对象,这个对象保存了以TreadLocal为键,本地线程变量为值的K-V键值对。
参考资料:周志明. 深入理解 Java 虚拟机 [M]. 机械工业出版社, 2011.