synchronized基本原理
锁用来保护数据的安全性,synchronized锁既要保证数据的安全性又要保证性能。
基本使用
synchronized修饰不同的地方表示保护的范围不同。
1、修饰实例方法(实例方法就是依赖实例的方法就是不带static修饰的),对象锁
只会保护当前实例方法的对象,生命周期和对象的生命周期一致,等同于synchronized(this),修饰方法里的代码块比直接在方法名上修饰更加有优势或者说是量级轻,因为锁在代码块上只会保护代码块而不影响方法的调用和其他代码的执行。
2、修饰静态方法 类锁
保护的是当前类的全局实例,生命周期最大,与类的生命周期一致,范围最大,所有该类型的对象都会受此保护,等同于synchronized(class),锁在方法上和在代码块里机制和上面一致。
3、修饰代码块
synchronized锁的存储
1、java对象在内存中的布局
一个对象有三个部分,对象头、实例数据、对齐填充
synchronized(obj)是基于obj对象的生命周期来控制粒度的,所以此时锁是存储与obj对象的对象头 mark word中的
锁升级:jvm实现锁升级 都是jvm底层实现的
无锁、偏向锁、轻量级锁、重量级锁
只有一个线程去访问的时候使用偏向锁,在被锁对象的对象头mark word中标记偏向锁为1 记录当前线程Id
两个线程交叉访问的时候使用轻量级锁 自旋
多个线程访问的时候 只能阻塞
偏向锁:当一个线程访问加了同步锁的代码块时会在对象头中存储当前线程的线程id,如果下次还是这个线程进来就不必再次加锁,而是直接比较锁对象头里面是否存储了指向当前线程的偏向锁。
当一个线程访问一个加了synchronized对象锁的代码块是 会先判断该对象头中是否是否存储了偏量锁状态,如果是可偏量状态则使用cas操作把锁写入到mark word中,如果写入成功那么就表示该线程获得了锁对象的偏量锁,接着执行代码块,如果整形失败说明其他线程已经获得了偏量锁,需要撤销已获得偏量锁的线程并把它升级为轻量锁,然后获得偏量锁的线程继续执行代码块。
如果是已偏向状态,则检查锁对象头mark word中是否存了当前线程的id如果是则直接执行如果不是则需要撤销偏向锁升级到轻量锁
偏向锁的撤销
对原持有偏向锁的线程进行撤销时原偏向锁的线程有两种情况:
1、原获得偏向锁的线程如果已经退出临界区也就是所同步代码块执行完成了,那么这个时候会把
对象头设置为无锁状态,并且抢锁的线程可以基于cas重新偏向当前线程
2、如果原获得偏向锁的同步代码块没有执行完成,处于临界之内,这个时候会把偏向锁升级为清凉所 继续执行代码块,
我们实际中多线程都会存在两个以上的线程竞争,如果开启偏向锁反而会增加资源消耗,所以可以通过jvm参数 UseBiasedLocking 来设置开启或关闭偏向锁。
轻量锁:
锁升级为轻量锁后,对象的markword也会进行相应的变化。升级锁的过程:
1、线程在自己的栈帧中创建锁记录 Lock Record
2、将锁对象的对象头中的markword复制到线程刚创建的锁记录中
3、将锁记录中的owner指针指向锁对象
4、将锁对象的对象头的markword替换为指向锁记录的指针。
自旋锁:
轻量级锁在加锁过程中用到了自旋锁,自旋锁就是当一个线程来竞争锁是发现该锁对象被其他线程持有,那么当前线程就会在原地执行啥也没有的for循环来等待锁被释放。这种情况一般都是在执行代码块很快的情况下就是说锁会被很快释放,因为反复循环也会造成资源浪费。
轻量级锁的释放过程
轻量级锁的释放其实就是获得锁的逆向逻辑,通过cas操作把栈帧中的lockrecord替换到多对象的markword中去,如果成功标示没有竞争。如果失败标示有竞争会升级为重量锁
重量锁:当轻量级锁膨胀到了重量锁后 意味着线程只能被挂起阻塞,等待被唤醒。
重量级锁的monitor:
加了同步代码块之后的字节码中会看到一个monitorenter和monitorexit,每个java对象都会与一个监视器monitor关联,我们可以把它理解为一把锁 当一个线程想要执行一段被synchronized修饰的同步方法或代码块时 该线程需要先获得synchronized修饰对象的monitor
monitorenter标示获取一个对象的监视器,monitorexit标示释放对象的monitor监视器所有权,其他被阻塞线程可以来竞争这个monitor。
monitor依赖操作系统的湖 MutexLock互斥锁来实现,线程被阻塞后便进入内核调度状态,这个会导致系统在用户态和内核态之间的来回切换,严重影响锁的性能
重量级锁的加锁基本流程
任意线程在对受synchronized保护访问时首先要获得锁对象的监视器,如果获取失败线程进入同步队列,线程状态变为 blocked,访问object的线程释放了锁 则该释放操作唤醒阻塞在同步队列中的线程,使其重新尝试对锁对象monitor的竞争。
synchronized结合 java object中的wait、notify、 notifyAll
wait、notify、notifyall基本概念
wait:标示持有对象锁的线程准备释放对象锁权限,cpu资源同事释放掉,进入等待状态
notify:表示当前线程持有对象锁通知jvm唤醒某个执行了所对象wait方法的线程,当前线程执行完同步代码块后 被唤醒的线程直接获取锁对象,而其他对象继续等待。(即使线程 X 同
步完毕,释放对象锁,其他竞争线程仍然等待,直至有新
的 notify ,notifyAll 被调用)
notifyall:和notify的区别在于它会唤醒所有的竞争同一个对象所得线程,当该持有锁线程执行完成后其他被唤醒的锁同时竞选竞争。
需要注意的是三个方法必须在synchronized同步关键字所限定的作用域中调用否则会出现异常,java.lang.IllegalMonitorStateException,意思是因为没有同步所以线程对象锁的状态不确定,不能调用这些方法。
另外通过同步机制来确保线程congowait方法返回时能感知到notify线程对变量做出的修改。
wait、notify基本原理图: