这里写目录标题
1. Java内存模型回顾:
- Java所有变量都存储在主内存中
- 每个线程都有自己独立的工作内存,里面保存该线程的使用到的变量副本(该副本就是主内存中该变量的一份拷贝)
- 线程对共享变量的所有操作都必须在自己的工作内存中进行,不能直接在主内存中读写
- 不同线程之间无法直接访问其他线程工作内存中的变量,线程间变量值的传递需要通过主内存来完成。
线程1对共享变量的修改,要想被线程2及时看到,必须经过如下2个过程:
①把工作内存1中更新过的共享变量刷新到主内存中
②将主内存中最新的共享变量的值更新到工作内存2中
2. synchronized同步锁:
2.1 引入:
Synchronized引入是为了解决线程安全问题,主要思想是以阻塞的方式让资源在同一时刻只由一个线程去访问,即悲观锁思想;
2.2 作用:
- Synchronized同步锁主要以阻塞的方式保证数据、同步代码块内多个步骤的原子性;
- 也可以保证可见性:线程在加锁时,先清空工作内存的变量→在主内存中拷贝最新变量的副本到工作内存→执行完代码→将更改后的共享变量的值刷新到主内存中→释放互斥锁。
可见性:是指当某个线程修改了共享变量的值,其他线程能否立刻知晓;
而普通的共享变量不能保证可见性,因为普通共享变量被修改之后,什么时候被写入主存是不确定的,
2.3 synchronized 和 volatile 的区别:
- volatile关键字是线程同步的轻量级实现,所以volatile性能肯定⽐synchronized关键字要好。但是volatile关键字只能⽤于变量⽽synchronized关键字可以修饰⽅法以及代码块。synchronized关键字在JavaSE1.6之后进⾏了主要包括为了减少获得锁和释放锁带来的性能消耗⽽引⼊的偏向锁和轻量级锁以及其它各种优化之后执⾏效率有了显著提升,实际开发中使⽤synchronized 关键字的场景还是更多⼀些。
- 多线程访问volatile关键字不会发⽣阻塞,而synchronized关键字可能会发⽣阻塞
- volatile关键字能保证数据的可见性,但不能保证数据的原子性。synchronized关键字保证了共享变量的原子性和可见性,还有线程访问资源的同步性。
2.4 原理:
- 每个Java对象都关联了一个
Monitor
对象,加synchronized和关键字后 Monitor对象会生效,通过进入与退出Monitor对象实现互斥、同步功能; - Monitor中划分为
Owner
表示监视器的所有者,同一时刻Monitor中只能由一个线程担任Owner
; - 刚开始 Monitor 中 Owner 为 null,当线程 t1访问对象时发现Owner为空,则会执行
Monitor.Enter
指令,线程会占据这个对象的Owner; - 当线程t2来访问对象发现Owner已经被锁住了,则t2会进入
EntryList
排队等待(阻塞状态),当t1执行完毕会执行Monitor.Exit
命令,线程释放对象的Owner,通知EntryList的线程去争抢Owner,所以synchronized是非公平的锁;
Monitor中还有WaitSet,用于存储执行了objectX.wait() / wait(long)的线程,
锁重入:
对同一个都对象多次执行Monitor.Enter--------锁重入
为了避免锁重入增大开销,可以让synchronized锁住的粒度大一些,减少了Monitor.Enter和Monitor.Exit的次数
注意:
- synchronized( )内要放引用类型的对象,不能放基本类型,
- 不加 synchronized 的对象不会关联Moniter监视器;
2.5 synchronized写法总结(加到什么位置)
(1) 修饰同步代码块
synchronized(线程共享对象){
同步代码块;
} //较为灵活
(2) 在实例方法的声明上使用synchronized
表示线程共享对象一定是 this
,并且同步代码块是整个方法体 ;
(3) 在静态方法上使用synchronized表示共享 (保证静态变量的安全)
表示找类锁,类锁永远只有一把!就算创建了100个对象,类锁也只有一把!
而对象锁则是一个对象一把锁,100个对象100把锁;