一、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加锁
public classNoSynchronizedDemo {public voidmethod() {
System.out.println("Method 1 start");
}
}
View Code
查看核心字节码
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");
}
}
}
查看字节码
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");
}
}
查看字节码
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");
}
}
查看字节码
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就是根据该标示符来实现方