Synchronized:
三种方式加锁:
1:修饰实例方法,作用于当前实例,进入同步代码块之前要获取当前实例的锁
2:静态方式,作用域当前类对象,进入同步代码看前要获取当前类对象的锁
3:修饰代码块,指定加锁对象,对给定对象加锁,进入同步代码块前要获取给定对象的锁
对象在内存中的布局:
分为三个区域:对象头[对象标记(Mark Word) 类元信息],实例数据,对齐填充
为什么任何对象都可以实现锁?
1:java中任何对象都继承自Object,而每个java Object在JVM内部都有一个oop/oopDesc进行对应
2:线程在获取锁的时候实际上就是获取一个监视器对象(monitor),所有java对象都携带monitor,多个线程访问同步代码块时候相当于去争抢对象监视器修改对象中的锁表示
Synchronized锁升级过程:
锁存在四种状态:无锁,偏向锁,轻量级锁,重量级锁
偏向锁:当一个线程访问同步代码块时候会在对象头中存储当前线程的ID,后续这个线程进入和退出这段加了锁的代码块时候,不需要再次加锁和释放锁,而是直接比较线程ID
偏向锁的撤销并不是将对象恢复到无锁状态,而是直接将锁升级到轻量级锁的状态,对原持有偏向锁的线程进行撤销时候有两种情况:
1:原获取偏向锁的线程如果已经退出了临界区(同步代码块执行完成),这个时候会将对象头设置成无锁状态并且争抢锁的线程可以基于CAS重新偏向当前线程
2:原获取偏向锁的线程的同步代码块未执行完,这个时候会将偏向锁升级成轻量级锁后继续执行同步代码块内容
轻量级锁:
1:线程在自己的栈帧中创建锁记录LockRecord
2:将锁对象的对象头中的MarkWord复制到线程刚刚创建的锁记录中
3:将锁记录中的Owner指针指向锁对象
4:将锁对象的对象头的MarkWord替换为指向锁记录的指针
自旋锁:
当另外一个线程来竞争锁时候,这个线程会原地循环等待,而不是将该线程阻塞,知道获取锁的线程释放锁之后,该线程可以获取锁(自旋等待时候相当于for()循环,消耗CPU),适合同步代码块执行很快的场景
轻量级锁的解锁:
就是获取锁的逆向过程,通过CAS将线程栈帧中的LockRecord替换到锁对象的MarkWord中,如果成功标识没有竞争,如果失败则表示当前锁存在竞争,升级为重量级锁
重量级锁:
线程只能被挂起,阻塞等待被唤醒
每一个Java对象和一个监视器(monitor)关联,执行同步代码块时候先获取对应的monitor然后才能执行代码块,monitor依赖操作系统的互斥锁实现,线程被阻塞后进入内核调度状态,这样会导致系统在用户状态和内核状态来回切换,影响性能
Wait,Notify,NotifyAll
使用synchronized关键字时候,获取monitor失败时候进入了同步队列,在其他线程执行完同步代码块时候会唤醒同步队列中的阻塞线程再次去竞争锁
wait:表示持有对象锁的线程A准备释放对象锁权限,释放CPU资源并进去等待状态
notify:表示持有对象锁的线程A准备释放对象锁权限,通知JVM唤醒某个竞争该独享锁的线程X
notifyAll:通notify。只是notifyAll是唤醒锁有线程,notify是随机唤醒一个
上面三个方法都必须在synchronized关键字内部使用,否则会提示IllegalMonitorStateException
wait/notify原理
waitThread和NotifyThread争抢同一把锁的时候,当waitThread获取到锁时候,进入同步代码块,此时如果执行wait()方法,将会释放锁,并且进入到等待队列中,NotifyThread此时会获取到锁,当执行notify、notifyAll时候,会唤醒等待队列中的waitThread线程,waitThread会从等待队列移动到同步队列中,当NotifyThread执行完代码块时候会释放锁,此时waitThread会去竞争锁