目录
1、synchronized 作用于静态方法
总结
编辑
案例 静态成员变量 (synchronized锁非静态方法)
案例 静态成员变量 (synchronized锁静态方法 或 直接锁类)
2、监视器锁(monitor)
2.1 synchronized怎么实现的线程安全呢?
3、JDK6 synchronized 的优化
3.1 CAS讲解
3.2 synchronized
3.2.1 java对象的布局
3.2.2 偏向锁
3.2.3 偏向锁的撤销
3.2.4轻量级锁(自旋锁/自适应自旋锁)
需要注意的是如果一个线程A调用一个实例对象的非static synchronized方法,而线程B需要调用这个实例对象所属类的静态 synchronized方法,是允许的,不会发生互斥现象,因为访问静态 synchronized 方法占用的锁是当前类的class对象,而访问非静态 synchronized 方法占用的锁是当前实例对象锁
synchronized修饰非静态方法时,锁的是当前实例,不同实例就不是同一把锁了,就不是线程安全
public class ThreadRunnable5 implements Runnable {private static int a = 100;@SneakyThrows @Overridepublic void run() {while (true) {if (a > 1) {increase4Obj();Thread.sleep(100);}}}public synchronized void increase4Obj(){System.out.println(Thread.currentThread().getName() + "==" + a);a--;}public static void main(String[] args) {ThreadRunnable5 thread = new ThreadRunnable5();new Thread(thread).start();new Thread(thread).start();}
}
普通方法锁的就是 对象的实例,不同的实例对象时,就是两个锁。无法保证线程安全
一篇就够,synchronized原理详解_小派师兄的博客-CSDN博客_synchronized原理
2.1 我们反编译下面的代码,看看jvm底层是如何帮助我们实现的线程安全的。
代码
public class ThreadRunnable6 {private static Object obj=new Object();public synchronized void test(){System.out.println("test打印");}public static void main(String[] args) {synchronized (obj){System.out.println("同步代码块");}}
}
执行反编译命令
到目标类的文件夹下
cd target/classes/com/example/juc/create/
对目标类执行命令
javap -p -v -c ThreadRunnable6.class
找到main方法那段就行
jvm虚拟机规范文档找到指令作用
Chapter 6. The Java Virtual Machine Instruction Set
monitorenter内部结构
可以参考这个博客的一些理解,画的很容易理解,但是也不是很全面
Java并发编程之Monitor工作原理(有图解)_Juice正在路上..的博客-CSDN博客_java monitor原理
java对象头的总体结构,MarkWord的结构、MarkWord和锁的关系_时间都用来泡馍了的博客-CSDN博客_java对象头结构
获取锁资源的流程:
monitor是重量级锁
jvm底层调用会用到内核函数。需要我们由用户态切换到内核态。
那什么是内核态呢?为什么不能直接使用呢?
我们的应用程序在内存运行的空间叫 用户空间(用户态)。
它不能直接调用系统的硬件资源,会去损坏我们的硬件(随意删除内存数据,造成死机等)。为了安全,
只有内核能够调用硬件,应用程序靠系统调用,cpu由用户态转为内核态,调用完成后,操作系统会重置cpu为用户态并返回系统调用的结果。
用户态与内核态来回切换会有大量的系统资源消耗,所以jdk1.6优化了synchronize 。
例如:IO操作,就需要内核态
跟乐观锁类似,本地内存和主存比较一样即可修改,内存地址和旧的预期值一样 就可以更改 成员变量 。do while用的很好
jdk1.6做的优化,无锁---偏向锁---轻量级锁----重量级锁
对象头:hash码,锁信息(判断是哪种锁状态,重量级锁的指针 moniter位置),gc标志
实例数据:类中属性数据信息,包括父类的属性信息;
对齐数据:jvm分64位和32位, 64位就是8字节,一个对象要被8整除。不够的补齐。
总结:锁对象结构,一个线程使用的时候,就会是偏向锁,但是有四秒延时,四秒前打印不是偏向锁,四秒后就是偏向锁。
有偏向锁的原因:经过HotSpot作者发现,大多数情况下,锁不仅不存在多线程竞争,而且总是由一个线程多次获取,为了让线程获取锁的代价更低,引入了偏向锁。
偏向锁:会偏向于第一个获取锁的线程,会在对象头存储偏向的线程id。以后该线程进入和退出同步快时只需要检查是否为偏向锁,锁标志位以及ThreadID即可。
偏向锁的基本流程图
步骤二是用CAS保证记录线程id的正确性。
验证打印对象头信息
org.openjdk.jol jol-core 0.9
private static Object obj=new Object();public static void main(String[] args) {synchronized (obj){System.out.println(ClassLayout.parseInstance(obj).toPrintable());}}
为什么打印的不是101(偏向锁)?
偏向锁在java1.6是默认启动的,但是要在程序启动后4秒才能激活。可以使用参数关闭延时。如果程序所有锁基本都要处于竞争状态也可以关闭偏向锁。
//关闭延迟开启偏向锁
-XX:BiasedLockingStartupDelay=0
//禁止偏向锁
-XX:-UseBiasedLocking
场景:两个线程竞争的时候就会去撤销偏向锁。
1偏向锁的撤销动作必须等待全局安全点(所有的线程都停下来了)
2暂停拥有偏向锁的线程,判断锁对象是否处于被锁定的状态。
3撤销偏向锁,恢复到无锁(标志位为01)或轻量级锁(标志位为00)的状态
场景:适用于多个线程交替去获取锁,而不是大量并发去获取锁。
jdk1.4引入自旋锁,1.6引入自适应自旋锁。 如果线程过多很多线程空耗cpu资源。升级为重量级锁(monitor)
还有很多细节,实在学的难受,暂时放放。
偏向锁、轻量级锁、自旋锁、重量级锁,看这一篇就够了!_51CTO博客_自旋锁和互斥锁