Synchronized 的底层原理——Java全栈知识(40)
Synchronized 的底层原理
1、Synchronized 如何使用?
我们都知道 Synchronized 关键字会给代码上互斥锁,但是上锁的位置分为两种,分别是对象锁和类锁
1.1、对象锁
1、Synchronized 默认锁的对象是 this,也可以手动指定锁对象。
案例一(锁 this 对象):
public class SynchronizedObjectLock implements Runnable { static SynchronizedObjectLock instance = new SynchronizedObjectLock(); @Override public void run() { // 同步代码块形式——锁为this,两个线程使用的锁是一样的,线程1必须要等到线程0释放了该锁后,才能执行 synchronized (this) { System.out.println("我是线程" + Thread.currentThread().getName()); try { Thread.sleep(3000); } catch (InterruptedException e) { e.printStackTrace(); } System.out.println(Thread.currentThread().getName() + "结束"); } } public static void main(String[] args) { Thread t1 = new Thread(instance); Thread t2 = new Thread(instance); t1.start(); t2.start(); }
}
运行结果是:
我是线程Thread-0
Thread-0结束
我是线程Thread-1
Thread-1结束
[!note]
1、我们可以看到 t 1 运行时锁住了 instance 对象
2、t 2 开始运行获取当前对象的锁
3、阻塞等待到 t 1 释放锁,t 2 获取锁执行。
2、Synchronized 指定锁对象
案例二(指定锁对象):
public class SynchronizedObjectLock implements Runnable { static SynchronizedObjectLock instance = new SynchronizedObjectLock(); // 创建2把锁 Object block1 = new Object(); Object block2 = new Object(); @Override public void run() { // 这个代码块使用的是第一把锁,当他释放后,后面的代码块由于使用的是第二把锁,因此可以马上执行 synchronized (block1) { System.out.println("block1锁,我是线程" + Thread.currentThread().getName()); try { Thread.sleep(3000); } catch (InterruptedException e) { e.printStackTrace(); } System.out.println("block1锁,"+Thread.currentThread().getName() + "结束"); } synchronized (block2) { System.out.println("block2锁,我是线程" + Thread.currentThread().getName()); try { Thread.sleep(3000); } catch (InterruptedException e) { e.printStackTrace(); } System.out.println("block2锁,"+Thread.currentThread().getName() + "结束"); } } public static void main(String[] args) { Thread t1 = new Thread(instance); Thread t2 = new Thread(instance); t1.start(); t2.start(); }
}
block1锁,我是线程Thread-0
block1锁,Thread-0结束
block2锁,我是线程Thread-0 // 可以看到当第一个线程在执行完第一段同步代码块之后,第二个同步代码块可以马上得到执行,因为他们使用的锁不是同一把
block1锁,我是线程Thread-1
block2锁,Thread-0结束
block1锁,Thread-1结束
block2锁,我是线程Thread-1
block2锁,Thread-1结束
[!note]
1、t 1 线程获取到 block 1,t 2 线程阻塞等待
2、t 1 线程释放 block 1 获取到 block 2,t 2 获取到 block1
3、t 1 线程释放 block 2 ,t 2 线程获取到 block 2 执行
3、方法锁:synchronized 修饰普通方法,锁对象默认为 this
案例三:
public class SynchronizedObjectLock implements Runnable { static SynchronizedObjectLock instance = new SynchronizedObjectLock(); @Override public void run() { method(); } public synchronized void method() { System.out.println("我是线程" + Thread.currentThread().getName()); try { Thread.sleep(3000); } catch (InterruptedException e) { e.printStackTrace(); } System.out.println(Thread.currentThread().getName() + "结束"); } public static void main(String[] args) { Thread t1 = new Thread(instance); Thread t2 = new Thread(instance); t1.start(); t2.start(); }
}
运行结果是:
我是线程Thread-0
Thread-0结束
我是线程Thread-1
Thread-1结束
1.2、类锁
指 synchronize 修饰静态的方法或指定锁对象为 Class 对象
1、 synchronize 修饰静态方法
示例一(修饰普通方法):
public class SynchronizedObjectLock implements Runnable {static SynchronizedObjectLock instance1 = new SynchronizedObjectLock();static SynchronizedObjectLock instance2 = new SynchronizedObjectLock();@Overridepublic void run() {method();}// synchronized用在普通方法上,默认的锁就是this,当前实例public synchronized void method() {System.out.println("我是线程" + Thread.currentThread().getName());try {Thread.sleep(3000);} catch (InterruptedException e) {e.printStackTrace();}System.out.println(Thread.currentThread().getName() + "结束");}public static void main(String[] args) {// t1和t2对应的this是两个不同的实例,所以代码不会串行Thread t1 = new Thread(instance1);Thread t2 = new Thread(instance2);t1.start();t2.start();}
}
输出结果:
我是线程Thread-0
我是线程Thread-1
Thread-1结束
Thread-0结束
- 示例2(synchronized 修饰静态方法)
public class SynchronizedObjectLock implements Runnable {static SynchronizedObjectLock instance1 = new SynchronizedObjectLock();static SynchronizedObjectLock instance2 = new SynchronizedObjectLock();@Overridepublic void run() {method();}// synchronized用在静态方法上,默认的锁就是当前所在的Class类,所以无论是哪个线程访问它,需要的锁都只有一把public static synchronized void method() {System.out.println("我是线程" + Thread.currentThread().getName());try {Thread.sleep(3000);} catch (InterruptedException e) {e.printStackTrace();}System.out.println(Thread.currentThread().getName() + "结束");}public static void main(String[] args) {Thread t1 = new Thread(instance1);Thread t2 = new Thread(instance2);t1.start();t2.start();}
}
输出结果:
我是线程Thread-0
Thread-0结束
我是线程Thread-1
Thread-1结束
[!note]
synchronized 用在静态方法上,默认的锁就是当前所在的 Class 类,所以无论是哪个线程访问它,需要的锁都只有一把
2 、 synchronized 修饰 Class 对象
public class SynchronizedObjectLock implements Runnable {static SynchronizedObjectLock instance1 = new SynchronizedObjectLock();static SynchronizedObjectLock instance2 = new SynchronizedObjectLock();@Overridepublic void run() {// 所有线程需要的锁都是同一把synchronized(SynchronizedObjectLock.class){System.out.println("我是线程" + Thread.currentThread().getName());try {Thread.sleep(3000);} catch (InterruptedException e) {e.printStackTrace();}System.out.println(Thread.currentThread().getName() + "结束");}}public static void main(String[] args) {Thread t1 = new Thread(instance1);Thread t2 = new Thread(instance2);t1.start();t2.start();}
}
输出结果:
我是线程Thread-0
Thread-0结束
我是线程Thread-1
Thread-1结束
[!note]
synchronized (SynchronizedObjectLock. class)锁的是当前类。
2、 synchronized 的原理
2.1、Monitor
Monitor 被翻译为监视器,是由 jvm 提供,c++语言实现
在代码中想要体现monitor需要借助javap命令查看clsss的字节码,比如以下代码:
public class SyncTest {
static final Object lock = new Object(); static int counter = 0; public static void main(String[] args) { synchronized (lock) { counter++; } }
}
找到这个类的class文件,在class文件目录下执行javap -v SyncTest.class
,反编译效果如下:
monitorenter 上锁开始的地方
monitorexit 解锁的地方
其中被 monitorenter 和 monitorexit 包围住的指令就是上锁的代码
有两个monitorexit的原因,第二个monitorexit是为了防止锁住的代码抛异常后不能及时释放锁
在使用了synchornized代码块时需要指定一个对象,所以synchornized也被称为对象锁
monitor主要就是跟这个对象产生关联,如下图
[!note]
上面我们说了,synchronized 锁的是对象,所以说一个锁对应一个对象对应一个 Monitor
Monitor内部具体的存储结构:
- Owner:存储当前获取锁的线程的,只能有一个线程可以获取
- EntryList:关联没有抢到锁的线程,处于Blocked状态的线程
- WaitSet:关联调用了wait方法的线程,处于Waiting状态的线程
具体的流程:
- 代码进入synchorized代码块,先让lock(对象锁)关联的monitor,然后判断Owner是否有线程持有
- 如果没有线程持有,则让当前线程持有,表示该线程获取锁成功
- 如果有线程持有,则让当前线程进入entryList进行阻塞,如果Owner持有的线程已经释放了锁,在EntryList中的线程去竞争锁的持有权(非公平)
- 如果代码块中调用了wait()方法,则会进去WaitSet中进行等待
总结:
- Synchronized【对象锁】采用互斥的方式让同一时刻至多只有一个线程能持有【对象锁】
- 它的底层由monitor实现的,monitor是jvm级别的对象( C++实现),线程获得锁需要使用对象(锁)关联monitor
- 在monitor内部有三个属性,分别是owner、entrylist、waitset
- 其中owner是关联的获得锁的线程,并且只能关联一个线程;entrylist关联的是处于阻塞状态的线程;waitset关联的是处于Waiting状态的线程