共享内存的原因
在Java内存模型中,分为主内存和线程工作内存,线程使用共享数据时,都是先从主内存中拷贝到工作内存,使用完成之后再写入主内存,可以理解为线程之间通讯是通过共享内存的方式实现的。
线程安全需要满足的特点
- 原子性:对数据的操作不会受其他线程打断,意味着一个线程操作数据过程中不会插入其他线程对数据的操作
- 可见性:当线程修改了数据的状态时,能够立即被其他线程知晓,即数据修改后会立即写入主内存,后续其他线程读取时就能得知数据的变化
线程安全实质上就是保证在任何调度情况下,同一时刻都只能有一个线程去进行数据操作并将结果写入主存。
如何保证线程安全
- 在尽可能的情况下,保证mutable类不会共享数据
- 如果不得不共享数据,最好使用immutable类型
- 如果不得不共享数据并且使用mutable类型,最好使用线程安全的数据类型
- 如果以上条件均不能满足,则对必要的操作加锁,加锁的方式主要有两种:使用关键词synchronized或者使用ReentrantLock
synchronized和ReentrantLock的区别
- 由于synchronized是在JVM层面实现的,因此系统可以监控锁的释放与否;而ReentrantLock是使用代码实现的,系统无法自动释放锁,需要在代码中的finally子句中显式释放锁lock.unlock()
- 在并发量比较小的情况下,使用synchronized是个不错的选择;但是在并发量比较高的情况下,其性能下降会很严重,此时ReentrantLock是个不错的方案
- 在使用synchronized 代码块时,可以与wait()、notify()、notifyAll()一起使用,从而进一步实现线程的通信
- ReentrantLock提供了一些高级功能,如等待可中断,即某线程在等待锁的时候可以放弃等待,转而去处理其他的事情等
- ReentrantLock中多个线程在等待一个锁时,必须按照申请锁的时间顺序来获得锁。synchronized中的锁是非公平的,释放的一瞬间任何线程都可能获得锁。ReentrantLock默认是非公平的,但可以通过带bool值得构造函数要求使用公平锁。