转:阿里面试题深度解析:Synchronized(obj){...}是锁对象还是代码块?Why?_哔哩哔哩_bilibili
马士兵说:【一小时】直插Java高并发最深处-AQS_哔哩哔哩_bilibili
{2021最新}java并发编程中最难的AQS框架源码解析-【视频教程全集】_哔哩哔哩_bilibili
CAS是乐观锁的实现,synchronized就是悲观锁了
为什么需要内存对齐呢?寻址最优
问题来了,那么锁的具体是什么?
1. synchronized用在普通类方法上,相当于锁的是调用这个方法的对象
2. synchronized用在static类方法上,相当于锁的是A.class
3. 优化
public class A { int num = 0; AtomicInteger atomicInteger = new AtomicInteger(); public long getNum(){ return atomicInteger.get(); } public void incrementNum(){ // synchronized (this){ // num++; // } atomicInteger.incrementAndGet(); } }
atomicInteger既可以保证多线程数据正确,又可以提升效率
同理:LongAdder也可以实现类似效果,同时也会有效率的提升
public class A { int num = 0; AtomicInteger atomicInteger = new AtomicInteger(); LongAdder longAdder = new LongAdder(); public long getNum(){ return longAdder.longValue(); //return atomicInteger.get(); } public void incrementNum(){ // synchronized (this){ // num++; // } // atomicInteger.incrementAndGet(); longAdder.increment(); } }
LongAdder内部实现了分段CAS
0. JDK实现版本
1.6之前【重量级锁:操作系统线程调度,内核态和用户态切换,阻塞、唤醒、抢锁】
那么我们继续来看看,在1.6之前,加锁的对象是什么样子的?
有两个队列:
一个是EntryList 阻塞队列另一个调用wait 是同步等待队列
来看看Synchronized的底层实现
可以看到有两个字节码:montiorenter、 monitorexit 是JVM指令码
Java虚拟机执行到这行代码会调用底层具体C++的实现,也就是所有的锁升级都是在HotSpot源码里面实现的,底层有大量的实现
执行monitorenter的时候,会解释成上述C++实现的代码
UseBiasedLocking:偏向锁是否开启
LongAdder:
其中,在1.6之后,对synchronized做了很多很多优化?
1. 引入了锁的状态
2. 基于原则:对某一块代码加锁(如:++),可能很多时候在线上运行的时候,只有一个线程来运行加锁的代码块
3. 偏向锁【在对象内部写入线程id 】,尤为轻量,不需要若干复杂操作
1. 了解下Java对象的布局具体是什么样子的?
引入pom依赖
<dependency> <groupId>org.openjdk.jol</groupId> <artifactId>jol-core</artifactId> <version>0.16</version> </dependency>
L l = new L(); System.out.println(ClassLayout.parseInstance(l).toPrintable());
尝试打印对下在内存里面的布局[里面会有hash码,但是hash是地址经过计算的结果,如果没有计算,那么对象内存布局中也不会有的]
如上:object header是对象头。 alignment gap 是填充内容, 还有一部分是实例数据
所以:可以指导java对象头是12字节【64位机器上】,所以总共96bit
java对象是8的倍数对齐
其中对象头里面包含若干信息:gc对象的年龄、哈希码、同步状态synchronized
64位虚拟机下:mark word下占用bit情况(64bit)
klass pointer:32bit【另外需要注意:如果这里没有开启指针压缩,那么这里是64bit】
如下是:锁的升级过程
@Data public class User { private int age; private String name; }
public class LockUpGrade { public static void main(String[] args) throws InterruptedException { User userTmp = new User(); System.out.println("无状态(001):" + ClassLayout.parseInstance(userTmp).toPrintable()); //jvm默认延时4s开启偏向锁,可通过:-XX:BiasedLockingStartupDelay=0, 取消延时 //如果不需要偏向锁,可通过-XX:UseBiasedLocking=false 来设置 Thread.sleep(5000); User user = new User(); System.out.println("启用偏向锁(101):" + ClassLayout.parseInstance(user).toPrintable()); for (int i=0; i<2; i++){ synchronized (user){ System.out.println("偏向锁(101),带id:" + ClassLayout.parseInstance(user).toPrintable()); } //偏向锁释放,对象头里面不会做任何修改。偏向锁是偏向特定的线程【认为下次还会来进行加锁 】 System.out.println("释放偏向锁(101),带id:" + ClassLayout.parseInstance(user).toPrintable()); } new Thread(() -> { synchronized (user) { System.out.println("轻量级锁(00):" + ClassLayout.parseInstance(user).toPrintable()); try { System.out.println("睡眠3s钟"); Thread.sleep(3000); } catch (InterruptedException e) { e.printStackTrace(); } System.out.println("轻量-》重量(10): " + ClassLayout.parseInstance(user).toPrintable()); } }).start(); Thread.sleep(1000); new Thread(() -> { synchronized (user) { System.out.println("重量级锁(10):" + ClassLayout.parseInstance(user).toPrintable()); } }).start(); } }
使用synchronized关键字的时候,对象有几种状态呢?
new一个对象、偏向锁、轻量、重量锁、gc标记
2. synchronized现在还重吗?
1.5之前,需要来回进行用户态和kernel来进行切换,所以是重量级锁
那么在1.5之后,对其进行了优化,引入了无锁、偏向锁、轻量级锁、重量级锁
同时:1.5之后,java增加了很多concurrentlock的内容,只用在java虚拟机内部就可以实现锁,也就是说不需要跟Operator System来打交到了,所以效率会提高