加锁java_002-多线程-锁-同步锁-synchronized几种加锁方式、Java对象头和Monitor、Mutex Lock、JDK1.6对synchronized锁的优化实现...

一、synchronized概述基本使用

为确保共享变量不会出现并发问题,通常会对修改共享变量的代码块用synchronized加锁,确保同一时刻只有一个线程在修改共享变量,从而避免并发问题。

synchronized结论:

1、java5.0之前,协调线程间对共享对象的访问的机制只有synchronized和volatile,但是内置锁在功能上存在一些局限性,jdk5增加了Lock以及ReentrantLock。

2、java5.0,增加了一种新的机制:显式锁ReentrantLock,注意它并不是替代内置锁synchronized的机制,而是当内置锁不适用时,作为一种可选的高级功能。

3、jdk6之后,synchronized与java.util.concurrent包中的ReentrantLock相比,由于JDK1.6中加入了针对锁的优化措施,使得synchronized与ReentrantLock的性能基本持平。ReentrantLock只是提供了synchronized更丰富的功能,而不一定有更优的性能,所以在synchronized能实现需求的情况下,优先考虑使用synchronized来进行同步。

synchronized在JDK5之前一直被称为重量级锁,底层是使用操作系统的mutex lock实现的,是一个较为鸡肋的设计,而在JDK6对synchronized内在机制进行了大量显著的优化,加入了CAS,轻量级锁和偏向锁的功能,性能上已经跟ReentrantLock相差无几,而且synchronized在使用上更加简单,不易出错(避免哲学家就餐问题造成的死锁),因此如果仅仅是为了实现互斥,而不需要使用基于Lock的附加属性(中断、条件等),推荐优先使用synchronized。

1、synchronized的几种加锁方式以及基础说明

修饰内容锁类型示例

没加锁

没加锁

示例1

修饰代码块

任意对象锁

示例2

修饰普通方法

this锁

示例3

修饰静态方法

类锁

示例4

1.1、示例以及说明

示例1、没有synchronized加锁

8f900a89c6347c561fdf2122f13be562.png

961ddebeb323a10fe0623af514929fc1.png

public classNoSynchronizedDemo {public voidmethod() {

System.out.println("Method 1 start");

}

}

View Code

查看核心字节码

8f900a89c6347c561fdf2122f13be562.png

961ddebeb323a10fe0623af514929fc1.png

public voidmethod();

descriptor: ()V

flags: ACC_PUBLIC

Code:

stack=2, locals=1, args_size=1

0: getstatic #2 //Field java/lang/System.out:Ljava/io/PrintStream;

3: ldc #3 //String Method 1 start

5: invokevirtual #4 //Method java/io/PrintStream.println:(Ljava/lang/String;)V

8: returnLineNumberTable:

line5: 0line6: 8LocalVariableTable:

Start Length Slot Name Signature0 9 0 this Lcom/lhx/cloud/javathread/NoSynchronizedDemo;

View Code

示例2、同步方法块,锁是括号里面的对象

public classSynchronizedDemo {public voidmethod() {synchronized (this) {

System.out.println("Method 1 start");

}

}

}

查看字节码

8f900a89c6347c561fdf2122f13be562.png

961ddebeb323a10fe0623af514929fc1.png

public voidmethod();

descriptor: ()V

flags: ACC_PUBLIC

Code:

stack=2, locals=3, args_size=1

0: aload_01: dup2: astore_13: monitorenter4: getstatic #2 //Field java/lang/System.out:Ljava/io/PrintStream;

7: ldc #3 //String Method 1 start

9: invokevirtual #4 //Method java/io/PrintStream.println:(Ljava/lang/String;)V

12: aload_113: monitorexit14: goto 22

17: astore_218: aload_119: monitorexit20: aload_221: athrow22: return

View Code

可以看在加锁的代码块, 多了个 monitorenter , monitorexit

monitorenter

每个对象有一个监视器锁(monitor)。当monitor被占用时就会处于锁定状态,线程执行monitorenter指令时尝试获取monitor的所有权,过程如下:

如果monitor的进入数为0,则该线程进入monitor,然后将进入数设置为1,该线程即为monitor的所有者。

如果线程已经占有该monitor,只是重新进入,则进入monitor的进入数加1.

如果其他线程已经占用了monitor,则该线程进入阻塞状态,直到monitor的进入数为0,再重新尝试获取monitor的所有权

小结:

Synchronize 可重入锁,即如果当前线程以获得锁对象,可再次获取该锁对象

即:该锁对象的监视器锁 monitor 具有可重入性,每进入一次,进入次数+1

从 synchronized 使用的语法上,如果修饰代码块,synchronize (object ) {} object 即为锁对象

如果修饰方法,普通方法可认为是 this 锁,即当前对象锁;静态方法可认为是 类锁

monitorexit

执行monitorexit的线程必须是objectref所对应的monitor的所有者。

指令执行时,monitor的进入数减1

如果减1后进入数为0,那线程退出monitor,不再是这个monitor的所有者

其他被这个monitor阻塞的线程可以尝试去获取这个 monitor 的所有权

小结:

总结:通过以上描述,应该能很清楚的看出Synchronized的实现原理,Synchronized的语义底层是通过一个monitor的对象来完成,其实wait/notify等方法也依赖于monitor对象,这就是为什么只有在同步的块或者方法中才能调用wait/notify等方法,否则会抛出java.lang.IllegalMonitorStateException的异常的原因

示例3、普通同步方法,锁是当前实例对象

public classSynchronizedDemo2 {public synchronized voidmethod() {

System.out.println("Method 1 start");

}

}

查看字节码

8f900a89c6347c561fdf2122f13be562.png

961ddebeb323a10fe0623af514929fc1.png

public synchronized voidmethod();

descriptor: ()V

flags: ACC_PUBLIC, ACC_SYNCHRONIZED

Code:

stack=2, locals=1, args_size=1

0: getstatic #2 //Field java/lang/System.out:Ljava/io/PrintStream;

3: ldc #3 //String Method 1 start

5: invokevirtual #4 //Method java/io/PrintStream.println:(Ljava/lang/String;)V

8: return

View Code

注意在flags上增加了ACC_SYNCHRONIZED

示例4、静态同步方法,锁是当前类的class对象

public classSynchronizedDemoStatic {public static synchronized voidmethod() {

System.out.println("Method 1 start");

}

}

查看字节码

8f900a89c6347c561fdf2122f13be562.png

961ddebeb323a10fe0623af514929fc1.png

public static synchronized voidmethod();

descriptor: ()V

flags: ACC_PUBLIC, ACC_STATIC, ACC_SYNCHRONIZED

Code:

stack=2, locals=0, args_size=0

0: getstatic #2 //Field java/lang/System.out:Ljava/io/PrintStream;

3: ldc #3 //String Method 1 start

5: invokevirtual #4 //Method java/io/PrintStream.println:(Ljava/lang/String;)V

8: return

View Code

注意:在flags上增加了ACC_STATIC, ACC_SYNCHRONIZED

针对示例3、示例四,在flags上均增加了 ACC_SYNCHRONIZED

从反编译的结果来看,方法的同步并没有通过指令monitorenter和monitorexit来完成(理论上其实也可以通过这两条指令来实现),不过相对于普通方法【没加synchronized的】,其常量池中多了ACC_SYNCHRONIZED标示符。JVM就是根据该标示符来实现方

  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值