前往小程序,Get更优阅读体验!
立即前往
首页
学习
活动
专区
工具
TVP
发布
社区首页 >专栏 >JUC并发编程01——谈谈锁机制:轻量级锁、重量级锁、偏向锁、锁消除与锁优化

JUC并发编程01——谈谈锁机制:轻量级锁、重量级锁、偏向锁、锁消除与锁优化

作者头像
用户10127530
发布2022-10-26 17:57:48
3320
发布2022-10-26 17:57:48
举报
文章被收录于专栏:半旧的技术栈

1.为什么要有并发编程

计算机的cpu与I/O的效率并不是完全一致的,CPU的处理速度快时,在进行I/O操作时,可能会导致CPU空闲的状态,为了最打程度的利用cpu的资源,开发人员创造了并发编程,进程通过轮换可以最大程度的利用cpu的资源,同时给用户进程在同步执行的错觉。但是进程之间并不会共享数据,同时上下文的切换也比较耗时,线程横空出世,同一个进程中的不同线程之间内存共享一片内存区域,线程上下文切换也很轻量级。juc是java官方提供的线程操作的jar包,可以尽可能的降低我们并发编程的难度。

2.锁机制

代码语言:javascript
复制
public class Demo2 {

    public static void main(String[] args) {
        synchronized (Demo2.class) {
            
        }
    }
}

使用idea中的插件jclasslib将其class文件反编译下。

代码语言:javascript
复制
 0 ldc #2 <com/wangzhou/Demo2>
 2 dup
 3 astore_1
 4 monitorenter
 5 aload_1
 6 monitorexit
 7 goto 15 (+8)
10 astore_2
11 aload_1
12 monitorexit
13 aload_2
14 athrow
15 return

其执行逻辑是,先会进入monitorenter将锁占有,然后执行代码逻辑,再使用第六行的monitorexit去时放锁。不过,如果执行代码逻辑的过程中出现了异常,就会执行第12行时放锁,并抛出异常。可以参考下面程序流程图进行理解。

还有一个问题,为什么需要在synchronized中写一个Demo02.class呢?其实这是因为每个对象都有一个monitor与之关联,这里我们使用的锁就是存储在了Demo02的monitor,它存储在对象头中。我们知道java的对象存储在堆内存中,而每个对象的存储都会有对象头的空间区域,如果这个对象使用了锁,对象头中就会存储这个锁的信息了。

3.重量级锁

JDK6以前,java中的synchronized锁会映射到操作系统的Lock来实现,而java的线程则映射到操作系统的线程,切换成本较高。

我们之前提到过,每个对象都会有一个monitor与之关联。其底层实现是c++中的ObjectMonitor,每个等待锁的线程都会被封装成为一个ObjectWaiter对象。

每个ObjectWaiter首先都会进入Entry Set进行等待,当获取到monitor后,会进入The Ownner,将 ObjectMonitor中的线程置未当前线程,count变量自增1.当线程调用wait方法时,会暂时释放当前的锁,进入Wait Set等待,并且将ObjectMonitor中的owner置成null,将count自减1.当当前线程执行完时,也将会释放当前的monitor并复位变量的值。以便其它线程能够获取锁对象。

这样的设计看似合理,不过一般应用中的线程占用同步代码块的时间并不是很长,我们没有必要将竞争中的线程挂起又唤醒。并且目前的cpu都是多核的,jdk1.4.2提供了一种新的解决方案:自旋锁,在jdk6以后,自旋锁默认开启。

自旋锁的逻辑是,竞争中的线程不必被挂起,而是不断循环的检测能否获取锁资源,一旦能够获取锁资源,就马上拿到锁进行运行。由于线程占用锁的时间不是太常,这种循环检测不用进行太多次,可以很快的拿到锁资源。从而避免了挂起、唤醒线程所产生的额外开销。但是由于线程并没有被挂起,所以其实是在消耗cpu的资源的,当每个线程占用锁的时间较长时,使用自旋锁就会比较消耗cpu的资源。因此,自旋锁的等待是有限制的,在jdk6之前,自旋锁默认为10次,当超过10次仍然没有获取到锁,就会进入到重量级锁的机制。

在jdk6以后,自旋锁机制得到了进一步的优化,采用了自适应的策略。如果在同一个锁对象上,刚刚有线程使用自旋锁成功获取了锁,并且正处于运行中,就会认为目前采用自旋锁获取的概率比较大,允许自旋运行更多次。当然,如果这个锁经常自旋失败,就有可能会不再使用自旋策略,而是直接使用重量级锁。

4.轻量级锁

在jdk1.6中,为了避免在无竞争的情况使用重量级锁带来的性能损耗。引入了轻量级锁。

在无竞争的情况下(虽然是同步代码块,但是并不是一定会总是处于竞争状态),会使用轻量级锁减少重量级锁所带来的性能消耗。轻量级锁并不能够代替重量级锁,它其实是赌线程不会进入竞争状态,同一时间只有一个线程占用同步资源,从而减少系统内核态与用户态切换,线程阻塞造成线程切换等造成的资源消耗。它并不像重量级锁一样需要向操作系统申请互斥量。下图说明了这一点。

轻量级锁的运行机制如下,在即将执行同步代码块之前,会先检查锁资源对象头中的Mark Word,看看当前锁对象是否被其它线程所占用,如果没有其它线程占用,会在当前线程的栈帧中建立一块区域:Lock Record空间,用于复制并存储Mark Word信息,对数据进行备份。接着使用CAS算法对Displaced MarkWoed变量值编程Lock Record指针,直接指向当前线程的栈帧。

CAS(CompareAndSet)是一种无锁算法,它在修改变量时不会加锁,而是直接进行修改,不过在修改前会查看当前数据值与我们的预期数据值是否一致,如果一致说明没有被其它线程修改,将替换变量值。如果不一致则说明当前数据已经被其它线程修改过,放弃修改变量值。

在cpu中,CAS采用的是cmpxchg指令,能够从底层硬件级别对于cpu效率进行提升。

如果CAS将变量修改成功,Mark Word的数据结构就变成了轻量级结构,编程Lock Record指针,直接指向当前线程的栈帧.

如果CAS执行替换没有成功,则说明可能有线程已经进入了同步代码块中。这时虚拟机会再次检测Mark word,看看是否是指向当前线程的栈帧,如果是,则说明当前线程已经占有了这把锁(可能在之前的操作中已经获取了对同步资源进行了其它修改),就可以大胆的进入同步代码块,如果不是,则同步代码块确实是被其它线程占用了。我们需要将轻量级锁膨胀成为重量级锁(锁的膨胀不可逆)。

上面的过程可以用下面的流程图表示。

解锁的过程同样是采用CAS算法,会将对象头中Mark Word使用CAS算法恢复成栈帧中的Replaced Mark Word,如果恢复成功则成功释放该锁,如果恢复失败,说明其它线程尝试过获取该锁,在释放锁的同时,需要唤醒被挂起的线程。

5.偏向锁

偏向锁比轻量级锁更加极致,干脆就把同步取消掉,不需要进行CAS了。它的发现主要得益于人们发现某个线程可以频繁的获取到锁。

偏向锁其实就是为了单个线程设计的,如果某个锁资源一直是被某个线程获取,而且没有其它线程来获取锁,就可以在Mark Word中记录下这个线程id,该线程就没有必要花时间来进行CAS操作了,可以直接进入到同步代码块。直到发现有其它线程来抢占锁资源了,就会根据当前状态判断是否把偏向锁膨胀成为轻量级锁。

如果需要使用偏向锁,可以使用参数:-XX:+UseBiased参数来添加。

值得注意的是,偏向锁的对象头中没有空间存储hash值,如下图:

因此,如果一个对象通过哈希算法计算了一致性哈希值,就不能使用偏向锁,而直接使用轻量级锁。如果一个锁已经是偏向锁,再调用hashcode(),就会将轻量级锁直接退化成为重量级锁。将hash值存放到ObjectMonitor中。

上述过程可以用下图表示。

6.锁消除与锁粗化

如果对于某段代码进行了加锁,但是再运行期间根本不可能出现同步资源竞争的情况,可能会进行锁消除。锁粗化是指某同步代码块频繁的出现同步互斥的情况,需要频繁的判断锁资源,比如在循环中加锁,这样很明显非常消耗性能,虚拟机检测到这种情况就可能出现锁粗化。

例如:

代码语言:javascript
复制
public class Demo3 {
    public static void main(String[] args) {
        for (int i = 0; i < 100; i++) {
            synchronized (Demo3.class){}
        }
    }
}

不如粗化加锁的范围:

代码语言:javascript
复制
public class Demo3 {
    public static void main(String[] args) {
        synchronized (Demo3.class){
            for (int i = 0; i < 100; i++) {
                
            }
        }
    }
}
本文参与 腾讯云自媒体同步曝光计划,分享自作者个人站点/博客。
原始发表:2022-04-08,如有侵权请联系 cloudcommunity@tencent.com 删除

本文分享自 作者个人站点/博客 前往查看

如有侵权,请联系 cloudcommunity@tencent.com 删除。

本文参与 腾讯云自媒体同步曝光计划  ,欢迎热爱写作的你一起参与!

评论
登录后参与评论
0 条评论
热度
最新
推荐阅读
目录
  • 1.为什么要有并发编程
  • 2.锁机制
  • 3.重量级锁
  • 4.轻量级锁
  • 5.偏向锁
  • 6.锁消除与锁粗化
相关产品与服务
对象存储
对象存储(Cloud Object Storage,COS)是由腾讯云推出的无目录层次结构、无数据格式限制,可容纳海量数据且支持 HTTP/HTTPS 协议访问的分布式存储服务。腾讯云 COS 的存储桶空间无容量上限,无需分区管理,适用于 CDN 数据分发、数据万象处理或大数据计算与分析的数据湖等多种场景。
领券
问题归档专栏文章快讯文章归档关键词归档开发者手册归档开发者手册 Section 归档