synchronized关键字
Java并发编程时,遇到多线程抢占共享资源的问题,都知道要给共享资源加锁。Java目前提供的加锁的方式主要是两种:synchronized的阻塞方式和基于AQS(AbstractQueuedSynchronizer)的相关实现(ReentrantLock等靠维护队列实现)。本文介绍synchronized的实现及原理。
synchronized关键字解决的是多个线程之间访问资源的同步性,synchronized关键字可以保证被它修饰的方法或者代码块在任意时刻只能有一个线程执行。
另外,在 Java 早期版本中,synchronized属于重量级锁,效率低下,因为监视器锁(monitor)是依赖于底层的操作系统的 Mutex Lock 来实现的,Java 的线程是映射到操作系统的原生线程之上的。如果要挂起或者唤醒一个线程,都需要操作系统帮忙完成,而操作系统实现线程之间的切换时需要从用户态转换到内核态,这个状态之间的转换需要相对比较长的时间,时间成本相对较高,这也是为什么早期的 synchronized 效率低的原因。庆幸的是在 Java 6 之后 Java 官方对从 JVM 层面对synchronized 较大优化,所以现在的 synchronized 锁效率也优化得很不错了。JDK1.6对锁的实现引入了大量的优化,如自旋锁、适应性自旋锁、锁消除、锁粗化、偏向锁、轻量级锁等技术来减少锁操作的开销。
synchronized关键字用法
总体来说synchronized关键字有两种用法,用在方法上和用在代码块,但是方法又又静态和非静态方法,所有也可以说有三种用法。
synchronized关键字最主要的三种使用方式:
1.修饰实例方法: 作用于当前对象实例加锁,进入同步代码前要获得当前对象实例的锁。
2.修饰静态方法: 也就是给当前类加锁,其实是用类对象加锁,会作用于类的所有对象实例 ,因为静态成员不属于任何一个实例对象,是类成员( static 表明这是该类的一个静态资源,不管new了多少个对象,只有一份)。所以如果一个线程A调用一个实例对象的非静态 synchronized 方法,而线程B需要调用这个实例对象所属类的静态 synchronized 方法,是允许的,不会发生互斥现象,因为访问静态 synchronized 方法占用的锁是当前类的锁,而访问非静态 synchronized 方法占用的锁是当前实例对象锁。
3.修饰代码块: 指定加锁对象,对给定对象加锁,进入同步代码库前要获得给定对象的锁。
看下面代码:
public class TestDemo {
//synchronized 代码块
public void synchronizedTest(){
synchronized (this){
System.out.println("test synchronized code");
}
}
//synchronized 方法
public synchronized void synchronizedTest2(){
System.out.println("test synchronized method");
}
}
上面可以看到这就是synchronized两种使用方式:同步代码块和同步方法,静态方法时只是锁定的对象是class对象其他与非静态方法一致。上面代码进行javap命令反汇编后看的更加清晰:
javap -c -s -v -l TestDemo.class
public com.zenmesir.TestDemo();
public void synchronizedTest();
Signature: ()V
flags: ACC_PUBLIC
Code:
stack=2, locals=3, args_size=1
0: aload_0
1: dup
2: astore_1
3: monitorenter
4: getstatic #2 // Field java/lang/System.out:Ljava/io/PrintStream;
7: ldc #3 // String test synchronized code
9: invokevirtual #4 // Method java/io/PrintStream.println:(Ljava/lang/String;)V
12: aload_1
13: monitorexit
14: goto 22
17: astore_2
18: aload_1
19: monitorexit
20: aload_2
21: athrow
22: return
Exception table:
from to target type
4 14 17 any
17 20 17 any
StackMapTable: number_of_entries = 2
frame_type = 255 /* full_frame */
offset_delta = 17
locals = [ class com/zenmesir/TestDemo, class java/lang/Object ]
stack = [ class java/lang/Throwable ]
frame_type = 250 /* chop */
offset_delta = 4
public synchronized void synchronizedTest2();
Signature: ()V
flags: ACC_PUBLIC, ACC_SYNCHRONIZED
LineNumberTable:
line 12: 0
line 13: 8
Code:
stack=2, locals=1, args_size=1
0: getstatic #2 // Field java/lang/System.out:Ljava/io/PrintStream;
3: ldc #5 // String test synchronized method
5: invokevirtual #4 // Method java/io/PrintStream.println:(Ljava/lang/String;)V
8: return
先看同步代码块,同步代码块的同步实现方式是通过monitorenter和monitorexit指令实现的。(未完待续)