重量级锁synchronized------->房屋锁(个人类比理解)
synchronized,中文意思为同步,顾名思义在同一时间内,一个资源只能被一个人利用,后续的所有人得留下来等待。
一、为什么要存在该重量级锁。
因为在大部分应用场景中,存在很多重要的资源变量,例如某人银行中的账户余额。该变量在同一时间内,只能进行存钱或者取钱中的一个操作,且只能一个人单独进行,不能存在任何的同时进行,否则损失将会不可估计。
二、要完成这个锁需要一些什么‘角色’
对一个资源进行加锁就必定离不开以下几个角色
- 类,被加锁的那个类,精确点来说是那个类的对象头
- 类里的实例对象,即为类中某个方法对象
- 锁,首先要明确一件事,一个类只有一把锁,只不过可以根据把锁用在的地方不同,分为三种不同的应用方式,分别为:synchronized作用于实例方法,synchronized作用于静态方法,synchronized同步代码块。实际在JVM中,锁的功能是由对象加载内存后的对象头唯一对应的**管程(monitor)**所实现的,关于管程的细节,小编后续研究明白再补充。。
三、要如何理解上面角色之间的关系
我个人的理解是把重量级锁与日常中房屋锁一一对应起来,然后对应不同的应用方式进行逐一理解。
类 -------> 门
类里的实例对象----------> “屋子里面的遥控器”
锁(管程monitor)---------->一把锁,随便加在哪里
- synchronized作用于实例方法
代码实例如下:
public class Main implements Runnable {
private static int i = 0;
public synchronized void add2() {
i++;
}
@Override
public void run() {
for (int i1 = 0; i1 < 10000; i1++) {
add2();
}
}
public static void main(String[] args) throws InterruptedException {
Main m = new Main();
Thread t1 = new Thread(m);
Thread t2 = new Thread(m);
t1.start();t2.start();
t1.join(); t2.join();
System.out.println(i);
}
}
测试结果为20000
此时我们是对一个Main对象中的add2方法进行的加锁,然后两个线程去执行,结果表
表名两个线程之间是同步进行的。
这就相当于两个人在一个房间里面去争抢遥控器,遥控器相当于上了锁的资源,所以同时只能有一个人拿到,两人同步进行。
但是如果其他条件不变,一个房间改成两个房间呢?
代码如下
public class Main implements Runnable {
private static int i = 0;
public synchronized void add2() {
i++;
}
@Override
public void run() {
for (int i1 = 0; i1 < 10000; i1++) {
add2();
}
}
public static void main(String[] args) throws InterruptedException {
Main m = new Main();
Thread t1 = new Thread(new Main());
Thread t2 = new Thread(new Main());
t1.start();t2.start();
t1.join(); t2.join();
System.out.println(i);
}
}
此时的运行结果为18974,肯定会是一个小于20000的数字。
因为在这个过程中,类不再是一个类,而是两个不同的类,就相当于另外一个人从门口去到了另外一个同样的房间,
里面同样有一个遥控器,两者可以同时操作。
- synchronized作用于静态方法
那要怎么样避免上面那种情况呢?答案就是把遥控器放在门把上然后加锁,那么在同一时间内,不管有多少个人要用遥控器,那也只能有一个人通过这个门,来拿到遥控器。
代码如下:
public class Main implements Runnable {
private static int i = 0;
public static synchronized void add() {
i++;
}
@Override
public void run() {
for (int i1 = 0; i1 < 10000; i1++) {
add();
}
}
public static void main(String[] args) throws InterruptedException {
Main m = new Main();
Thread t1 = new Thread(new Main());
Thread t2 = new Thread(new Main());
t1.start();
t2.start();
t1.join();
t2.join();
System.out.println(i);
}
}
此时的结果就是20000,
这种情况就是将锁用于静态方法,那么该方法便属于类的一部分,跟着类跑。
3.synchronized同步代码块
除了使用关键字修饰实例方法和静态方法外,还可以使用同步代码块,在某些情况下,我们编写的方法体可能比较大,同时存在一些比较耗时的操作,而需要同步的代码又只有一小部分,如果直接对整个方法进行同步操作,可能会得不偿失,此时我们可以使用同步代码块的方式对需要同步的代码进行包裹,这样就无需对整个方法进行同步操作了,同步代码块的使用示例如下:
public class Main implements Runnable {
private static int i = 0;
@Override
public void run() {
synchronized (this) {
for (int i1 = 0; i1 < 10000; i1++) {
i++;
}
}
}
public static void main(String[] args) throws InterruptedException {
Main m = new Main();
Thread t1 = new Thread(new Main());
Thread t2 = new Thread(new Main());
t1.start();
t2.start();
t1.join();
t2.join();
System.out.println(i);
}
}
同步代码块的性质与synchronized作用于实例方法类似,不同的只是前者作用的可能只是按遥控的动作,而后者是拿遥控的整个过程。
四.总结
总的来说,synchronized重量级锁实现的原理不难,有点类似于操作系统中的PV操作。其就是用一个唯一对应的管程monitor去操作,在Java虚拟机中,monitor是由ObjectMonitor实现的。
ObjectMonitor中有两个队列,_WaitSet 和 _EntryList,用来保存ObjectWaiter对象列表( 每个等待锁的线程都会被封装成ObjectWaiter对象),_owner指向持有ObjectMonitor对象的线程,当多个线程同时访问一段同步代码时,首先会进入 _EntryList 集合,当线程获取到对象的monitor 后进入 _Owner 区域并把monitor中的owner变量设置为当前线程同时monitor中的计数器count加1,若线程调用 wait() 方法,将释放当前持有的monitor,owner变量恢复为null,count自减1,同时该线程进入 WaitSe t集合中等待被唤醒。若当前线程执行完毕也将释放monitor(锁)并复位变量的值,以便其他线程进入获取monitor(锁)。