对象锁与类锁
什么是sycnchronized
synchronized是Java中的关键字,是一种同步锁。在JDK1.6以前,使用synchronized就只有一种方式即重量级锁,而在JDK1.6以后,引入了偏向锁,轻量级锁,重量级锁,来减少竞争带来的上下文切换。它修饰的对象有以下几种:
- 代码块,被修饰的代码块称为同步语句块,其作用的范围是大括号{}括起来的代码,作用的对象是调用这个代码块的对象;
public class Test { private final Object lock = new Object(); { synchronized(lock){ // doSomething } } }
- 方法,被修饰的方法称为同步方法,其作用的范围是整个方法,作用的对象是调用这个方法的对象;
public class Test { public synchronized void test(){ // doSomething } }
- 静态的方法,其作用的范围是整个静态方法,作用的对象是这个类的所有对象;
public class Test { public static synchronized void test(){ // doSomething } }
- 类,其作用的范围是synchronized后面括号括起来的部分,作用主的对象是这个类的所有对象。
public class Test { { synchronized(Test.class){ // doSomething } } }
【对象锁与类锁】首先我来举几个例子
- 一个类中有两个被synchronized修饰的普通方法,生成一个对象延时调用结果会如何
public class Test1 { public static void main(String[] args) { Things things = new Things(); new Thread(() -> things.doThing1(), "A").start(); SleepUtils.sleep(1); new Thread(() -> things.doThing2(), "B").start(); } static class Things { public synchronized void doThing1(){ SleepUtils.sleep(5); System.out.println("I'm doing thing one"); }public synchronized void doThing2(){ SleepUtils.sleep(1); System.out.println("I'm doing thing two"); } } }// 输出结果 // I'm doing thing one // I'm doing thing two
- 一个类中有两个被synchronized修饰的普通方法,生成两个个对象延时调用结果会如何
public class Test2 { public static void main(String[] args) { Things things1 = new Things(); Things things2 = new Things(); new Thread(() -> things1.doThing1(), "A").start(); SleepUtils.sleep(1); new Thread(() -> things2.doThing2(), "B").start(); } static class Things { public synchronized void doThing1(){ SleepUtils.sleep(5); System.out.println("I'm doing thing one"); }public synchronized void doThing2(){ SleepUtils.sleep(1); System.out.println("I'm doing thing two"); } } }// 输出结果 // I'm doing thing two // I'm doing thing one
- 一个类中有两个被synchronized修饰的静态方法,生成两个对象延时调用结果会如何
public class Test3 { public static void main(String[] args) { Things things1 = new Things(); Things things2 = new Things(); new Thread(() -> things1.doThing1(), "A").start(); SleepUtils.sleep(1); new Thread(() -> things2.doThing2(), "B").start(); } static class Things { public static synchronized void doThing1(){ SleepUtils.sleep(5); System.out.println("I'm doing thing one"); }public static synchronized void doThing2(){ SleepUtils.sleep(1); System.out.println("I'm doing thing two"); } } }// 输出结果 // I'm doing thing one // I'm doing thing two
- 一个对象有被synchronized修饰的一个静态方法和一个普通方法,生成两个对象延时调用结果会如何
public class Test4 { public static void main(String[] args) { Things things1 = new Things(); Things things2 = new Things(); new Thread(() -> things1.doThing1(), "A").start(); SleepUtils.sleep(1); new Thread(() -> things2.doThing2(), "B").start(); } static class Things { public static synchronized void doThing1(){ SleepUtils.sleep(5); System.out.println("I'm doing thing one"); }public synchronized void doThing2(){ SleepUtils.sleep(1); System.out.println("I'm doing thing two"); } } }// 输出结果 // I'm doing thing two // I'm doing thing one
对象组成 对象由对象头、实例数据和补齐数据组成。
对象头由MarkWord、指向类的指针和数组长度组成。
其中MarkWord记录了对象和锁有关的关系,在此只介绍MarkWord。
MarkWord内部划分如下图所示(32位):
文章图片
由此可以看见每个对象中记录了锁的信息,其中包括了锁的状态和当前锁的指针。
所以对于一个被synchronized修饰的方法被访问时该线程会将该对象的无锁转态转化为偏向锁(轻量级锁或重量级锁),后面的线程想要访问该方法时,会不断判断该对象是否有锁,当锁不存在了,就抢占这个对象的所有权。
解析上述现象
- 对于例一,有一个对象things,首先线程A抢占了对象A的锁(所有权),然后休眠了五秒(睡眠不释放锁,即一直保持things的所有权),主线程休眠了一秒后,线程B执行,发现对象things已经被占领了,于是它就不断的尝试获取对象things锁(所有权),知道线程A释放了锁,线程B才执行dothing2。
- 对于例二,由于有两个对象,所以线程A即使抢占了对象things1的锁(所有权),并不会干扰线程B抢占对象things2的锁(所有权),所以线程B在休眠了两秒之后(主线程一秒,线程B一秒)就直接打印出来了。
- 对于例三,在对象实例化时,会先在JVM中寻找该类是否被加载到虚拟机中,不存在的话会先进行类加载...等一系列操作,然后会生成一个类对象,保存了该类的信息,同时生成对象时是通过该类对象进行生成的,比如对于Thing这个类,他会有一个生成Thing这个类的类对象,所以访问被sycnchronized修饰的静态方法的线程会将抢占该类对象的锁(所有权),所以线程A抢占了该类的类对象的锁,线程B只能等待线程A释放,然后继续运行。
- 对于例四,就很明了了,线程A抢占的是类对象的锁,线程B抢占的是things2对象的锁,两者之间没有交集,所以不会阻塞等待。
推荐阅读
- JAVA(抽象类与接口的区别&重载与重写&内存泄漏)
- Docker应用:容器间通信与Mariadb数据库主从复制
- 《真与假的困惑》???|《真与假的困惑》??? ——致良知是一种伟大的力量
- 第326天
- Shell-Bash变量与运算符
- 逻辑回归的理解与python示例
- Guava|Guava RateLimiter与限流算法
- 我和你之前距离
- CGI,FastCGI,PHP-CGI与PHP-FPM
- 数组常用方法一