Java面试重点题型:类锁和对象锁到底有什么区别?

推荐阅读

  1. 金三即逝,连这份“Java春招手册”都没刷过,你拿什么备战银四?
  2. 终极手撕之架构大全:分布式+框架+微服务+性能优化,够不够?
  3. Java高级面试攻略:消息+缓存+高并发+高可用+架构+分布式+微服务

类锁和实例锁是常见的面试题,时不时都会看到有同学讨论,在类上加锁和在 newH E N 出来的类实例上加锁到底有什么区别呢?

来,今天让你彻底搞明白类锁和对象锁的区别和使用方式Q d ( 0 N T ~ o。同C D ` - L w学,快跟上。

Java面试重点题型:类锁和对象锁到底有什么区别?

有锁才有自由

生活中不存在绝对的自由,绝对的自由通常对应的无序和混沌,只有在道德、法律、e { } 7 z伦理的约束下的相对自f 3 |由,才能使人感受到自由。

而在多线程编程中,锁是至关重要的,锁就是道德,就是法律约束. B x ( w & V,没有锁的多线程环境将会是混乱的,所有线程都在争夺资源,最后的结果就是导致系统{ Q 0 j b S崩溃,而有了锁之后,多I 8 2 ) M a ; r Z线程环境才能稳定高效的工作。

synchronized 关键字

synchronized 是我们所4 a P Z说的重量级锁,所说的重量级是相对于那些自旋锁(AQS)而言的,比如可重入锁ReentrantLock。很多人谈 synchronized 色变,说它性能差、太重,貌似言之凿凿。放在* E P V b多年前确实如此,但是 Java 1.7、1.J 2 R @8 已经对 synchronized 做了很大优化,其性能和那些轻量级锁几乎没有差距2 } t h I + % + o

所以,我们再程序中其实可以放心的使用它,即使没有用过,也肯定在一些源码里见过,比如 Netty 中就有很多地方用到了它。

下面开始进入今天的主题,类锁和实例锁。看名字就已经很明显了,类锁就是所在类上的锁,实例就是O & *锁在类[ b F V实例上的锁。

实例锁

类声明后,我们可以 new 出来很多的实例对象。这时候,每个实例在 JVM 中都有自己的引用地E ? )址和堆内存空间,这时候,我们就认为这些实例都是独M e S 0 U J # C立的个体,{ ( ; 6 2 $ $ l很显然,在实例上加的锁和其他的实例就没M [ U Z ] V有关系,互不影响了。

通常我们使用实例锁的方式有下面三! D S , A R J , -种:

1、 锁住实体里的非静态变量

非静态变量是实例自身变量,不会与其他实例共享,所以锁住实体内声明的非静f 7 x态变量可以实现对象锁。锁住同一个变量的方法块共享同一把锁。

Java面试重点题型:类锁和对象锁到底有什么区别?

2、锁住 this 对象

thiss 8 # o S 指的是当前对象实例本身,所以,所有使用 synchronized(this)方式的方法都共享同一把锁。

Java面试重点题型:类锁和对象锁到底有什么区别?

3、直接锁非静态方法

最简单、最直观的一种方式,直接加在方法返回类型前。

Java面试重点题型:类锁和对象锁到底有什么区别?

使用对象锁的情况,只有使用同一实例的线程才会受锁的影响,多个实例调用同一方法也不会受影响。

下面来做个测~ C 9 i :试,开启 5 个线程,每个线程都 new 一个新的实例来分别调用上面三种方式的方法,方法完成的动作就是输出线程名称,然后休眠 10 秒钟。

public class ObjectLock {

private Object lock = new Object();

/**
* 锁住非静态变量
* @throws InterruptedException
*/
public void lockObjectField() thro? J 3 + Wws InterruptedException{
synchronized (lock){
System.r L g S t ] c !out.println(Thread.currentThread().getName());
Thread.sleep(10*1000);
}
}

/**
* 锁住 this 对7 C R u #象 this 就是当前对象实例
* @throws Inter? d N - P H , c lruptedException
*/
public void lockThis() throws InterruptedException{
synchronized (this){
System.out.printlr 2 4 u A & Qn(Thread.currentThr= - B $ V ead().getName());
Thread.sleep(10*1000);
}
}

/**
* 直接锁住! a F ! ? H `非静态方法
* @throws InterruptedException
*/
p& Y E = # j Q 4ublic synchronized v0 Y e Ioid methodLock() throws InterruptedException{
System.out.pr9 T v (intln(Thread.currentThread().getName());
Thread.sleep(10*1000);
}

public static void m$ m % * /ain(String[] args){
for (int i = 0; i < 5; i++) {
Thread worker = new Thread(new ObjectLockWorker());
worker.setName(\"kite-\" + i)Z j C t ^ 9 O q;
worker.start();
}
}

public static class ObjectLockWorker implements Runnable{
@Overrides P # G w w # q @
public vo_ [ f ? I Bid run() {
try {
ObjectLock objectLock = new ObjectLock();
// 方式 1
object= * 1 A ` hLock.lockObjectField();
// 方式 2
//objectLock.lockThis();
// 方式 3H x Z
//objectLock.methodLock();
} catch (Exception e) {
e.printStackTrace();
}
}
}
}

我们预测的结果就是每个Y m S = Q O (线程都会立刻输出线程名称,然后各自休眠 10 秒。

分别调用方式1、2、3,效果都是一样的,我们看到输出结果和我们预测的是一样的,5 个线程都立即输出线程名,然后等待 10 秒,整个程序退出。

类锁

类锁是加载类上的,而类信息是存在 JVM 方法区的,并且整个 JVM 只有一份,方法区又是所有线程共+ r R T c . v享的,所以类A ? d ~ E s , R锁是所有线程共享的。

使) # ) Z H 6 , ;用类锁的方式有如下方式:

1、锁住F } 3 F R Q类中的静态变量

因为静态变量和类信息一样也是存在方法区的并且整个 JO & s } D RVM 只有一份,所以加在静态变量上可以达到类锁的目的。

Java面试重点题型:类锁和对象锁到底有什么区别?

2、直接在静态方法上加 synchronized

因为静态方法同样也是存在方法区的并且整个 JVM 只有一份,所以加在静态方法上可以达到类锁的目的。

Java面试重点题型:类锁和对象锁到底有什么区别?

3、锁住 xxx.class

对当前类的 .class 属性加锁,可以实现类锁。

Java面试重点题型:类锁和对象锁到底有什么区别?

类锁是所有线程共享的锁b ? - ,,所以同一时刻,只能有一个线程使用加了锁的方法或方K ^ G A m q i法体,不管是不是同一个实例。

下面同样来做个测试,开启 5 个线程,除了调用静态方法的方式,其他两种方式中每个t } +线程都 new 一个新的实例来分别调用,方法内完成的动作就是输出线` K g程名称,然f m ;后休眠 10 秒钟。

pubH o * 7 W mlic class ClassLock {

priA I f _ m J J uvate sx c 7tatc n l s W 2ic Object lock = new Obje~ ! x # Z 9ct();

/**
* 锁住静态变量
* @throws InterruptedException
*/
public void lockStaticObjectFieldD x O , E l 7 F 5() throws InterruptedException{
s$ x - r v d [ Cynchronized (locp c O $ - *k){
System.out.println(Thread.currentThread().getName()~ F f);
Thread.sleep(10*1000);
}
}

/**
* 锁住静态方法
* @throws InterruptedException
*/
public static synchronized void methodLock() th7 | f y M L S A Wrows InterruptedException{
System.out.println` E L 0 5 J M(Thread.currentThread().getNa? V i c U & ^ 4 Vme());
Thread.sleep(10*1000);
}

/**
* 锁住 xxx.class
* @throws Interrups % 1 S 9tedException
*/
public void lockClass() throws InterruptedException{
synchronized (ClassLock.class){
System.out.println(Thread.currentThread().getName());
Thread.sleep(10*1000);
}
}

public static void main(String[] args){
for (int i =4 @ g U 3 0; i < 5; i++) {
Thread worker =s A $ 1 T M T new Thread(new ClassLockWorker());
worker.setNameQ A i + R(\"k L u 3 U * Yite-\" + i);
worker.start();
}
}

public static class ClassLockWorker implementB T 4 f D = -s Runnable{
@Override
public void run() {
try {
ClassLocR u T Mk classLock = new ClassLock();
// 方式 1
classLock6 / $ m.lockStaticO+ o ^ [ F Y e d 7bjectField();
/3 | / v U/ 方式 2
//ClassLock.m g K u : $ G 2 RmethodLock();
// 方式 3
//classLock.lockClass();
} catch (Exception e)O d : [ _ , 0 & R {
e.printStackTrace();
}
}
}
}

我们预测的结果就是刚开始只有1个线程抢到g N x L 2锁,然后输出线程名,之后等待 10 秒中,之后是下一个抢到锁的线程,输出线程名,然后等待 10 秒。直到最后一个抢到锁的线程,整个过程历时大约 50 秒。

分别调用方式1y w d z ^、2、3,观察执行结果,和我们预测的是一致的。

总结

  1. 使用对象锁的情况,只有使用同一实例的线程才会受锁的影响,多个实例调用同一方法也不会受影响。
  2. 类锁是所有线程共享的锁,所以同一时刻,只能有一个线程使用加了锁的方法或方法体,不管是不是同一个实例。

作者:古时的风筝
原文链接:https://juejin.im/postc N ( l Y - P/5e8b4f2be51d454715& d d K V3d0cd8

上一篇

吴冬升:数字新基建下的 5G 车联网,创新赋能自动驾驶和智慧交通

下一篇

小米高管谈小米成中国制造和设计名片,并让山寨机消失 你认可吗?

你也可能喜欢

  • 暂无相关文章!

发表评论

您的电子邮件地址不会被公开。 必填项已用 * 标注

提示:点击验证后方可评论!

插入图片
返回顶部