为了解决CPU运算速度和物理内存读写速度之间的巨大差异,Java引入了高速缓存的概念,缓存的读写速度和CPU速度差不多。即主内存和工作内存,主内存对应实际的物理内存,每一个线程都有自己的工作内存,所有的数据都存储在主内存上,线程需要用到的数据都来自于主内存的数据拷贝,等到数据处理完成再回写到主内存。
我们知道线程操作的数据都是来自主线程的拷贝,当多个线程访问同一数据的时候,就可能出现一个线程读数据的时候另一个线程还没有来得及会写到主内存,造成读到的是脏数据,这就是我理解的线程安全问题
Java中自定义了8种先行发生原则
一个操作如果不直接满足这些原则,而且不能由这些原则推导出来,那么它就是线程不安全的
Volatile修饰的变量具有2个特性
要保证线程安全,就是保证操作满足三大特性
原子性:一个操作那么全部完成,那么都不执行,不会出现中断(即一些执行了一些没有执行)操作,基本数据类型的读取和赋值都是原子操作
可见性:在工作内存的操作会立即同步到主内存,就是说一个线程的操作对其他所有线程立即可见
有序性:JVM可以对指令进行重新排序,但是不管怎么排序,要保证程序执行的结果相同
通过前面可以知道,Volatile关键字可以保证可见性和有序性, 如果对Volatile关键字修饰的变量操作也具备原子性的话
,那么就是线程安全的。所以它是一个轻量级的线程安全机制
Synchronized关键字可以保证操作具备三大特性
它会在编译后代码的前后增加Monitorenter和Monitorexit两条指令,这个指令都有一个reference属性,用来制定需要加锁和解锁的对象,除了明确指定锁对象,否则Synchronized修饰一般方法时锁对象是对象实例,修饰静态方法时锁对象是类的class对象
//这里需要理解清楚再改
线程执行Monitorexit指令的时候,会尝试获取对象的锁,如果获取失败,说明对象被锁定,线程阻塞。否则把对象加锁,所计数器加1
并发包下的可重入锁也可以用来实现线程同步,使用时需要显式的执行lock和unloc操作,并用try/finally来保证任何情况下都能执行解锁操作,而且它提供了更多的高级特性