codeER.tec

深入理解synchronized

2021-06-17  本文已影响0人  贪挽懒月

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

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

面试官:还有吗?

小白:没有了……

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


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


1. 用来干嘛的?

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

2. 怎么用呢?

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

// 静态方法
public synchronized static void staticFun(){
    System.out.println("synchronized修饰静态方法,锁对象是当前class");
    // 业务代码……
}
// 实例方法
public synchronized void instanceFun(){
    System.out.println("synchronized修饰实例方法,锁对象是类实例");
    // 业务代码……
}
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的底层原理吗?

javap -c -s -v -l Xxx.class

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

8. synchronized和ReentrantLock有何异同?

相同点:

不同点:

上一篇下一篇

猜你喜欢

热点阅读