专栏首页JavaEE深入理解synchronized

深入理解synchronized

面试官:请谈谈你对synchronized的理解。

小白:这是一个java的关键字,用来控制并发的,被它锁住的代码同一时刻只能有一个线程访问。

面试官:还有吗?

小白:没有了……

面试官:那你先回去等通知吧!


synchronized,相信学过java的都知道它,但是面试一被问到这个,又总是答不出多少东西来。下面我就将synchronized的知识点列举出来,深入理解(要深入它,才能征服它)。


1. 用来干嘛的?

这是一个同步关键字,保证同一时刻只能有一个线程执行被其修饰的方法或代码块,可以保证线程安全。

2. 怎么用呢?

这是一个关键字,可以用来修饰静态方法、实例方法、代码块。注意这里的代码块不是类中的静态代码块和构造代码块,而是方法中的代码块。

  • 修饰静态方法: 介绍它修饰静态方法之前,先来回忆一下静态方法的特点。静态是该类所有实例共享的,JVM加载该类时就会对其进行初始化,因为不属于任何一个实例,所以静态方法里面不能用this关键字。如果synchronized修饰静态方法,那么锁对象是啥呢?首先排除this,因为调用静态方法的时候可能该类都还没有实例。所以修饰静态方法的时候,锁对象其实是当前class。
// 静态方法
public synchronized static void staticFun(){
    System.out.println("synchronized修饰静态方法,锁对象是当前class");
    // 业务代码……
}
  • 修饰实例方法: 既然都说了是实例方法,那么锁对象就是当前类的实例。
// 实例方法
public synchronized void instanceFun(){
    System.out.println("synchronized修饰实例方法,锁对象是类实例");
    // 业务代码……
}
  • 修饰代码块: 修饰代码块,锁对象可以是class,也可以是给定的对象。如果是class,那就是不管该类new几个实例,都是属于这个类的,都会被锁住;如果是对象,那么不同对象去访问时是可以获取到锁的,所以class作为锁其实粒度更粗。
public void fun(){
    synchronized (TestSync.class){ // 锁对象是当前class
//    synchronized (this){ // 锁对象是实例
        System.out.println("synchronized修饰代码块,锁对象可以是实例,可以是类");
    }
}

3. 线程A调用类的同步实例方法,线程B可以同时调用类的同步静态方法吗?为什么?

我们先用代码看结果,再解释为什么。

 // 静态方法
 public synchronized static void staticFun(){
     System.out.println("synchronized修饰静态方法,锁对象是当前class");
     System.out.println(Thread.currentThread().getName() + "进入同步静态方法");
     System.out.println(Thread.currentThread().getName() + "执行结束");
 }

 // 实例方法
 public synchronized void instanceFun(){
     System.out.println("synchronized修饰实例方法,锁对象是类实例");
     System.out.println(Thread.currentThread().getName() + "进入同步实例方法");
     try { TimeUnit.SECONDS.sleep(5); } catch (InterruptedException e) { e.printStackTrace(); }
     System.out.println(Thread.currentThread().getName() + "执行结束");
 }

 public static void main(String[] args){
     TestSync testSync = new TestSync();
     new Thread(() -> {
         testSync.instanceFun();
     }, "线程A").start();

     new Thread(() -> {
         staticFun();
     }, "线程B").start();

     new Thread(() -> {
         testSync.instanceFun();
     }, "线程C").start();
 }

运行结果:

运行结果

上面的代码,线程A调用实例方法,并且进入方法后线程睡了5秒钟;线程B调用静态方法,还没等线程A结束,线程B已经执行结束了,线程B不需要等线程A释放锁也可以执行。而线程C,因为是同一个对象去调用的同步实例方法,所以得等线程A释放了锁,线程C才能拿到执行权。假如线程C是另外再new一个对象去调用的,那么也不需要等待线程A释放锁。

从结果可以得出答案:线程A调用类的同步实例方法,线程B可以同时调用类的同步静态方法。原因就是同步实例方法的锁是对象锁,而同步静态方法的锁是类锁,锁对象不同,所以可以同时调用

4. 可以用String字符串来做锁对象吗?

可以,但没必要。代码块的锁对象其实可以是任意对象,不过一般都用class或者this,并不建议用string做锁对象,因为用string很容易造成死锁。为什么容易造成死锁呢?因为JVM中有个常量池,比如你定义两个字符串:

String str1 = "haha";
String str2 = "haha";

这里明明是两个字符串,但其实是同一个对象,因为这样赋值的String,首先会看常量池中有没有,没有就往常量池中添加一个,并指向它,有的话,就直接指向。所以str1和str2都是指向常量池中同一个对象。

5. synchronized可以修饰构造方法吗?为什么?

不能修饰构造方法,构造方法只能有权限修饰符,比如public、private之类的,它本身就是线程安全的。

6. jdk1.6开始对synchronized做了哪些优化?

jdk1.6之前,synchronized是很重的锁,jdk1.6开始,做了大量的优化,比如用偏向锁、轻量级锁、自旋锁、适应性自旋锁、锁消除、锁粗化等技术来减少锁操作的开销(当你这么回答的时候,估计面试官紧接着就问什么是偏向锁、自旋锁……有什么区别,这些后续再详细地说)。

7. 你知道synchronized的底层原理吗?

  • 同步代码块:方法里面的同步代码块,synchronized底层是通过监视器monitor来实现的。通过指令
javap -c -s -v -l Xxx.class

可以发现monitorenter指令指向同步代码块开始的位置,同时会尝试获取锁,锁的计数器为0表示可以获取锁,获取后计数器变为1;monitorexit指令指向同步代码块结束的位置,同时释放锁,将锁的计数器置为0。所以获取锁就是获取Monitor的执行权。Monitor是基于C++,由ObjectMonitor实现的,每个对象都内置了ObjectMonitor。另外,wait/notify方法也是基于monitor来实现的。

  • 同步方法:执行上述的javap指令查看同步方法,可以发现并没有monitorenter和monitorexit指令,但是在方法开头有个名为ACC_SYNCHRONIZED的flag标识,同步方法就是通过这个标识来控制同步操作的。

8. synchronized和ReentrantLock有何异同?

相同点:

  • 两者都是可重入锁;
  • 都可实现选择性通知;

不同点:

  • synchronized是JVM层面的,ReentrantLock是API层面的;
  • synchronized是非公平锁,ReentrantLock可以指定为公平锁或者非公平锁;
  • synchronized无需手动释放锁,ReentrantLock需要手动释放锁;
  • synchronized等待不能中断,ReentrantLock等待可通过lock.lockInterruptibly()中断等待;

本文参与腾讯云自媒体分享计划,欢迎正在阅读的你也加入,一起分享。

我来说两句

0 条评论
登录 后参与评论

相关文章

  • 深入理解 Synchronized

    java404
  • 深入理解synchronized工作原理与锁的优化

    导读:synchronized 是 java 中最常用的保证线程安全的方式,synchronized 的作用主要有三方面:

    码农架构
  • 深入理解synchronized底层原理,一篇文章就够了!

    如果某一个资源被多个线程共享,为了避免因为资源抢占导致资源数据错乱,我们需要对线程进行同步,那么synchronized就是实现线程同步的关键字,可以说在并发控...

    beifengtz
  • 深入理解synchronized底层原理,一篇文章就够了!

    如果某一个资源被多个线程共享,为了避免因为资源抢占导致资源数据错乱,我们需要对线程进行同步,那么synchronized就是实现线程同步的关键字,可以说在并发控...

    挨踢小子部落阁
  • 深入理解 Java 并发之 synchronized 实现原理

    线程安全是并发编程中的重要关注点,应该注意到的是,造成线程安全问题的主要诱因有两点,一是存在共享数据(也称临界资源),二是存在多条线程共同操作共享数据。因此为了...

    用户1263954
  • 由浅入深逐步了解 Synchronized

    synchronized关键字是Java并发编程中线程同步的常用手段之一,其作用有三个:

    sowhat1412
  • Synchronized深入分析

    Synchronized是Java中解决并发问题的一种最常用的方法,也是最简单的一种方法。

    Anymarvel
  • synchronized—深入总结

    传统的锁(也就是下文要说的重量级锁)依赖于系统的同步函数,在linux上使用mutex互斥锁,这些同步函数都涉及到用户态和内核态的切换、进程的上下文切换,成本较...

    用户5325874
  • 高并发编程-synchronized深入解析深挖

    Java虚拟机的运行时数据区中的堆和方法区是所有线程共享的区域,如果多个线程需要同时使用共享的对象或类变量,则必须要正确协调它们对数据的访问。否则,程序将具有不...

    JavaQ

扫码关注云+社区

领取腾讯云代金券