线程同步机制可以保障线程安全
java平台提供了的线程同步机制包括:锁,volatile关键字,final关键字,static关键字,以及相关的API,如object.wait() object.notify等
锁
线程安全问题产生的前提是多个线程并发访问共享数据
将多个线程对共享数据的并发访问转换为串行访问,即一个共享数据一次只能被一个线程访问,锁就是复用这种思路来保障线程安全的
锁(Lock)可以理解为对共享数据进行保护的一个许可证,对于同一个许可证保护的共享数据来说,任何线程想要访问这些共享数据必须持有该许可证,一个线程只有在持许可证的情况下才能对这些共享数据进行访问;并且一个许可证一次只能被一个线程持有;许可证线程在结束对共享数据的访问后必须释放其持有的许可证。
一线程在访问共享数据前必须先获得锁;获得锁的线程称为锁的持有线程;一个锁一次只能被一个线程持有锁的持有线程在获得锁之后,和释放锁之前这段时间所执行的代码称为临界区.
锁具有排它性,即一个锁一次只能被一个线程持有。在这种锁称为排他锁或互斥锁
JVM把锁分为内部锁和显示锁两种,内部锁通过synchronized关键字实现;显示锁通过java.concurrent.locks.Lock接口实现类
锁可以实现对共享数据的安全访问,保障了线程的原子性,可见性与有序性。
锁是通过互斥保障原子性,一个锁只能被一个线程持有,这就保障了临界区的代码一次只能被一个线程执行,使得临界区代码所执行的操作自然而然的具有不可分割的特性,即具备了原子性。
可见性的保障是通过写线程冲刷处理器的缓存和读线程刷新处理器缓存这两个动作实现的,在java平台中,锁的获得隐含着刷新处理器缓存的动作,释放锁隐含着冲刷处理器缓存的动作。
锁能够保障有序性,写线程在临界区所执行的在读线程所执行的临界区看来像是完全按照源码顺序执行的
注意:
使用锁保障线程的安全性,必须满足以下条件 :
这些线程在访问共享数据时必须使用同一个锁
即使是读取共享数据的线程也需要使用同步锁。
锁的相关概念
- 可重入性
- 一个线程持有该锁的时候能否再次申请该锁。
有两个方法:一个方法申请锁然后调用另一个方法 另一个方法也是申请锁
- 锁的争用和调度
java平台中内部锁属于非公平锁,显示Lock锁及支持公平锁和非公平锁
- 锁的粒度
一个锁可以保护的共享数据的数量大小称为锁的粒度。锁的保护共享数据量大,称该锁的粒度粗,否则就称该锁的粒度细, 锁的粒度过粗会导致线程在申请锁时3会进行不必要的等待,过细会增加锁调度的开销