什么是线程安全?下面是我摘自java并发编程书里给的解释:
当多个线程访问某个类时,这个类始终都能表现出正确的行为,那么就称这个类是线程安全的。
这个解释听的是不是云里雾里?我们说的通俗一点:
线程安全就是多线程并发访问的时候,线程之间所共享的资源不会被污染或被破坏。
我们知道线程共享的内存区域主要就是java堆,而java堆主要存的就是对象和数组,所有的全局变量都存储在堆中。也就是说我们想保证线程安全,就需要保证全局变量和堆内的对象不会受其他线程影响。
由此,我们可以从数据共享和不共享两个方面来讨论实现线程安全的方式。
避免共享
通过避免共享,让所有资源都编程线程私有,达到线程安全。主要实现方法有三种:
- 不可变
- 可重入代码
- 线程封闭
不可变
使用final类型
通过使用final类型修饰,发布不可变对象。
String属于不可变对象,可直接使用。
使用volatile类型
通过使用Volatile类型修饰,发布不可变对象。
volatile类型修饰的变量只适用于读多写少的情况,只能确保其可见性。如果要保证线程安全,则需要保证只有单个线程去修改。
可重入代码
若一个程序或子程序可以安全的被并行执行,则称其为可重入(reentrant),即允许多个进程同时访问。
可重入代码:所有变量都是局部变量或者是不可变的全局变量。
线程封闭
通过使用线程本地存储,即使用ThreadLocal类存储对象。
ThreadLocal提供类get()和set()等方法,为每个使用该变量的线程都保存一份独立的副本。
Thread类中有一个成员变量ThreadLocal.ThreadLocalMap,ThreadLocalMap则定义在ThreadLocal类中的静态内部类,它是一个Map,他的key是线程对象,value是线程的变量副本。
必须共享
保证数据共享安全,即保证数据同步。同步的方式主要分两种:互斥同步和非阻塞同步。
互斥同步
为了保证多线程在同一时间访问共享数据的安全性,我们可以通过加互斥锁使共享数据每次只被一个线程访问,这样就能保证线程安全,但是同时线程会被阻塞。
互斥同步的主要实现方式是加锁:synchronized 和 ReentrantLock。
synchronized关键字:java内置锁,可重入。同步代码块的锁就是方法调用所在的对象
,静态的synchronized方法以Class对象作为锁。进入占有锁,退出释放锁(无论是正常还是异常退出)。
ReentrantLock:实现lock接口,默认的是非公平锁 false,必须手动释放锁
ReentrantLock相比synchronized优势:1. 可实现公平锁 (构造参数传true) 2. 可响应中断 3. 获取锁限时等待 4. 结合Condition实现等待通知机制
非阻塞同步
java提供了大量的基于非阻塞同步CAS原子操作的类(java.util.concurrent 里面Atomic开头的类)。
CAS 又称无锁操作,一种乐观锁策略,原理就是多线程环境下各线程访问共享变量不会加锁阻塞排队,线程不会被挂起。通俗来讲就是一直循环对比,如果有访问冲突则重试,直到没有冲突为止。
来源:CSDN
作者:林中君
链接:https://blog.csdn.net/frankyhome/article/details/103596446