synchronized的作用:被synchronized修饰的方法或者代码块,同一时刻最多只有一个线程执行这段代码。
public class SynchronizedDemo {
public static final Object LOCK = new Object();
public static void fn1(){
synchronized (LOCK){
System.out.println(Thread.currentThread().getName()+"运行");
try {
TimeUnit.SECONDS.sleep(1);
} catch (InterruptedException e) {
e.printStackTrace();
}
}
}
/**
* synchronized在静态方法上,锁是类对象SynchronizedDemo.class
*/
public static synchronized void fn2(){
System.out.println(Thread.currentThread().getName()+"运行");
}
/**
* synchronized在实例方法上,锁是类实例this
*/
public synchronized void fn3(){
System.out.println(Thread.currentThread().getName()+"运行");
}
public static void main(String[] args) throws Exception{
for (int i = 0; i < 3; i++) {
new Thread(() -> fn1()).start();
}
}
}
synchronized同步代码块内抛出异常,JVM会释放当前线程的锁
public class SynchronizedDemo {
public static final Object LOCK = new Object();
public static void main(String[] args) throws Exception {
Thread thread1 = new Thread(() -> {
synchronized (LOCK) {
System.out.println(Thread.currentThread().getName() + "运行");
int a = 0;
if (a == 0) {
/**
* synchronized同步代码块内抛出异常,JVM会释放当前线程的锁
*/
throw new RuntimeException("抛出异常");
}
try {
TimeUnit.SECONDS.sleep(3L);
} catch (InterruptedException e) {
e.printStackTrace();
}
}
});
Thread thread2 = new Thread(() -> {
synchronized (LOCK) {
System.out.println(Thread.currentThread().getName() + "运行");
}
});
thread1.start();
TimeUnit.MILLISECONDS.sleep(10);
thread2.start();
}
}
synchronized具备可重入性,同一个线程在外层函数获得锁之后,内层函数还可以再次直接获取该锁。
synchronized可重入性原理:每个对象拥有一个锁计数器,JVM负责跟踪对象被加锁的次数,线程第一次进入同步代码块,计数器变为1,相同线程在此对象上再次获得锁时,计数器递增。离开同步代码块,计数器递减,当计数为0时,锁被释放。
public class SynchronizedDemo {
public static final Object LOCK = new Object();
public static void fn1() {
synchronized (LOCK) {
System.out.println(Thread.currentThread().getName() + "运行fn1");
fn2();
}
}
public static void fn2() {
synchronized (LOCK) {
System.out.println(Thread.currentThread().getName() + "运行fn2");
}
}
public static void main(String[] args) {
new Thread(() -> fn1()).start();
}
}
线程等待锁的时候处于BLOCKED阻塞状态,无法响应中断。一旦锁被其他线程获取了,当前线程只能阻塞,直到持有锁的线程释放锁。如果持锁线程不释放锁,BLOCKED线程只能永远等待下去。
public class SynchronizedDemo {
public static final Object LOCK = new Object();
public static void main(String[] args) throws Exception {
Thread thread1 = new Thread(() -> {
synchronized (LOCK) {
System.out.println(Thread.currentThread().getName() + " run");
try {
TimeUnit.SECONDS.sleep(5L);
} catch (InterruptedException e) {
e.printStackTrace();
}
}
});
Thread thread2 = new Thread(() -> {
synchronized (LOCK) {
System.out.println(Thread.currentThread().getName() + " run");
}
});
thread1.start();
TimeUnit.MILLISECONDS.sleep(10L);
thread2.start();
TimeUnit.MILLISECONDS.sleep(10L);
/**
* 此时thread2等待其他线程释放锁,处于BLOCKED阻塞状态,无法响应中断
*/
thread2.interrupt();
thread1.join();
thread2.join();
}
}
在字节码层面理解synchronized
1、每个Java对象自带一个监视器锁(monitor),monitor的计数器为0,表示没有线程持有锁。
2、线程进入synchronized同步代码块,monitor的owner变量设置为该线程,表示锁被该线程占有,同时锁计数器加1。若重入,锁计数器递增。
3、在monitor被线程占有期间有其他线程尝试获取锁,这些线程会进入BLOCKED状态,并且这些线程会被封装为ObjectWaiter对象,放在一个等待队列中。
4、线程离开synchronized同步代码块,monitor锁计数器减1。当锁计数器减为0,monitor的owner变量设置为null,锁被线程释放,其他线程可以竞争锁。
public class SyncBlock {
public void syncsTask(){
synchronized (this){
System.out.println("synchronized code");
}
}
}
编译代码: javac SyncBlock.java
使用javap查看SyncBlock.class: javap -verbose SyncBlock.class 。得到的部分代码如下:
public void syncsTask();
descriptor: ()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 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
monitorenter是获取锁指令,monitorexit是释放锁指令。为了保证锁能释放,编译器会自动产生一个异常处理器,这个异常处理器声明可处理所有的异常,在异常处理器中执行monitorexit。这是为什么会有两条monitorexit的原因。
public class SyncMethod {
public synchronized void syncTask(){
System.out.println("synchronized code");
}
}
synchronized用在方法上,使用javap得到的字节码如下
public synchronized void syncTask();
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 synchronized code
5: invokevirtual #4 // Method java/io/PrintStream.println:(Ljava/lang/String;)V
8: return
LineNumberTable:
line 5: 0
line 6: 8
字节码中并没有monitorenter,monitorexit,而是多了一个ACC_SYNCHRONIZED标记。方法被ACC_SYNCHRONIZED,线程必须先持有monitor才能进入方法,方法执行完毕,线程释放monitor。