目录
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轻量级锁(自旋锁/自适应自旋锁)
1、synchronized 作用于静态方法
总结
- 当synchronized作用于静态方法时,其锁就是当前类的class对象锁。由于静态成员不专属于任何一个实例对象,是类成员,因此通过class对象锁可以控制静态 成员的并发操作。
- synchronized作用于非静态方法时,也可以直接锁类。也可以控制静态 成员的并发操作。
- synchronized作用于非静态方法时,必须是同一个对象才能控制静态 成员的并发操作。
需要注意的是如果一个线程A调用一个实例对象的非static synchronized方法,而线程B需要调用这个实例对象所属类的静态 synchronized方法,是允许的,不会发生互斥现象,因为访问静态 synchronized 方法占用的锁是当前类的class对象,而访问非静态 synchronized 方法占用的锁是当前实例对象锁
案例 静态成员变量 (synchronized锁非静态方法)
synchronized修饰非静态方法时,锁的是当前实例,不同实例就不是同一把锁了,就不是线程安全
public class ThreadRunnable5 implements Runnable {
private static int a = 100;
@SneakyThrows @Override
public 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锁静态方法 或 直接锁类)
2、监视器锁(monitor)
一篇就够,synchronized原理详解_小派师兄的博客-CSDN博客_synchronized原理
2.1 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操作,就需要内核态
3、JDK6 synchronized 的优化
3.1 CAS讲解
跟乐观锁类似,本地内存和主存比较一样即可修改,内存地址和旧的预期值一样 就可以更改 成员变量 。do while用的很好
3.2 synchronized
jdk1.6做的优化,无锁---偏向锁---轻量级锁----重量级锁
3.2.1 java对象的布局
对象头:hash码,锁信息(判断是哪种锁状态,重量级锁的指针 moniter位置),gc标志
实例数据:类中属性数据信息,包括父类的属性信息;
对齐数据:jvm分64位和32位, 64位就是8字节,一个对象要被8整除。不够的补齐。
3.2.2 偏向锁
总结:锁对象结构,一个线程使用的时候,就会是偏向锁,但是有四秒延时,四秒前打印不是偏向锁,四秒后就是偏向锁。
有偏向锁的原因:经过HotSpot作者发现,大多数情况下,锁不仅不存在多线程竞争,而且总是由一个线程多次获取,为了让线程获取锁的代价更低,引入了偏向锁。
偏向锁:会偏向于第一个获取锁的线程,会在对象头存储偏向的线程id。以后该线程进入和退出同步快时只需要检查是否为偏向锁,锁标志位以及ThreadID即可。
偏向锁的基本流程图
步骤二是用CAS保证记录线程id的正确性。
验证打印对象头信息
<!--查看对象内存布局-->
<dependency>
<groupId>org.openjdk.jol</groupId>
<artifactId>jol-core</artifactId>
<version>0.9</version>
</dependency>
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
3.2.3 偏向锁的撤销
场景:两个线程竞争的时候就会去撤销偏向锁。
1偏向锁的撤销动作必须等待全局安全点(所有的线程都停下来了)
2暂停拥有偏向锁的线程,判断锁对象是否处于被锁定的状态。
3撤销偏向锁,恢复到无锁(标志位为01)或轻量级锁(标志位为00)的状态
3.2.4轻量级锁(自旋锁/自适应自旋锁)
场景:适用于多个线程交替去获取锁,而不是大量并发去获取锁。
jdk1.4引入自旋锁,1.6引入自适应自旋锁。 如果线程过多很多线程空耗cpu资源。升级为重量级锁(monitor)
还有很多细节,实在学的难受,暂时放放。
偏向锁、轻量级锁、自旋锁、重量级锁,看这一篇就够了!_51CTO博客_自旋锁和互斥锁