Java中synchronized的锁优化

123 阅读3分钟

持续创作,加速成长!这是我参与「掘金日新计划 · 10 月更文挑战」的第28天,点击查看活动详情

Java中synchronized的锁优化

在并发编程中,对于锁的使用是相当频繁的,所以相信各位朋友们对如何用锁的认识都有一定深度了,那么既然这样,今天我们就来学习另外一个东西——锁是如何优化的,可以有什么优化?

锁优化

下面我们先来看一个常见的例子:

有一个方法,它是需要在并发环境下也保持着同步执行的,所以一般情况下,我们会像下面这样给方法加锁:

具体代码如下:

    synchronized void testMethod() {
        try {
            TimeUnit.SECONDS.sleep(2);
        } catch (InterruptedException e) {
            e.printStackTrace();
        }
        
        count ++;
        
        try {
            TimeUnit.SECONDS.sleep(2);
        } catch (InterruptedException e) {
            e.printStackTrace();
        }
    }
​

可以看到上述代码在方法头加了synchronized,这就可以保证了整个方法都是同步执行的。

但是在实际上,我们可能并不是整个方法都要求同步的,也就是说方法内部有一些代码逻辑是可以并发执行的;而如果是直接在方法头加synchronized的话,就整个方法都是同步的了,那些允许并发的代码也必须同步,这样很显然会影响整个代码的执行效率。

为此,我们提出了锁优化的概念,当然啦,在这种情况下,其实就是将锁的粒度降低(整个方法都上锁导致锁的粒度太大了)

下面我们一起来看看降低锁粒度之后的代码实现:

     void testMethod() {
         
        try {
            TimeUnit.SECONDS.sleep(2);
        } catch (InterruptedException e) {
            e.printStackTrace();
        }
         
         // 这里可能存在部分不需要同步的代码
         
         // 下面是必须保持同步的代码逻辑
        synchronized(o) {
            count ++;
        }
         
        // 这里可能存在部分不需要同步的代码
         
        try {
            TimeUnit.SECONDS.sleep(2);
        } catch (InterruptedException e) {
            e.printStackTrace();
        }
    }

可以看到上述代码中synchronized锁住的就只要同步逻辑 count ++;了,锁粒度降低,其他代码可以并发执行,整体代码执行效率上升。

锁优化思想在单例模式中的体现

我们都知道在单例模式中,有一种双重检查的实现方法,那么你了解的其中蕴含的锁优化的思想么?

下面我们一起来看看双重检查的核心代码:

class Instance {    
    volatile Instance instance;
    void getInstance() {
        ...
        // 第一重检查
        if (instance != null) {
            synchronized (o) {
                // 第二重的检查
                if (instance != null) {
                    instance = new Instance();
                }
            }
        }
        ...
        return instance;
    }
}

在上述代码中,其实synchronized加第二重检查已经可以确保生成的是单例了的,为什么还需要第一重检查呢?第一重检查的作用是什么?

其实我们都知道加锁之后,性能肯定会下降的!

有了第一重检查在,可以拦截掉大部分的请求(在单例模式中,只需要创建一次,其他不为空就可以直接跳过同步创建Instance的代码逻辑了),这样避免多次执行同步代码而引发的性能下降。