再谈多线程(一)
再谈多线程
好久没有更新了,最近忙的复习期末,一直顾不上写博客,今天实在有点手痒,于是就有了这一篇,来进一步谈一谈关于多线程的事,主题如你所见,是关于多线程的内容。至于这一篇,我们就先来看一看Java中的锁机制到底是怎样实现的
锁机制的原始实现
事实上,我们的锁机制是经历了多次的迭代的,我们先来看一看在jdk6之前的锁是怎样实现的
我们都知道想要给某个对象加锁,最简单的方法就是使用synchronized
关键字,那么加入这个关键字会具体产生哪些影响呢
首先我们需要简单的了解一个类ObjectMonitor
,这个类具体有哪些方法我们暂且不看,只先了解它有哪些属性
1 |
|
每个对象都有一个与之对应的Monitor对象相对应,在这个对象的监视对象有一个entryLIst,所有的需要获得该对象的锁的线程都将被包装为一个objectWaiter
对象并放入这个LIst中,随后将会依据LIst中的顺序依次让这些线程获得锁,而使用完锁的线程则调用wait函数挂起并进入waitset,在entryLIst变为空后再次从waitset中获取需要的锁的线程,如此循环往复
自旋锁
但这样的锁机制其实存在一个问题,反复的将线程挂起再唤醒的操作实际上会消耗大量的不必要的资源,所以在jdk6时提出来自旋锁的概念,每个线程在使用完锁后不再挂起而是选择进入一个循环,反复尝试获取锁,这避免了挂起与唤醒时将线程中数据保存再读取的资源浪费
但这个操作实际上基于一个认知:每个线程都不会长时间占用一个锁,所有的线程都能在可接受的时间内等到锁.如果一个线程长时间无法获得锁,那么这个线程实际上最好还是挂起,避免算力上的无意义浪费.所以后来自旋锁实际上做出了一定的改进,在循环一定的次数后如果仍然无法获取锁,那么这个线程同样会被挂起,这个具体的次数由JVM决定
轻量级锁
上面说到的锁的原始实现实际上也被称之为重量级锁或者说悲观锁,即我们认为随时都存在某个线程会和当前的线程发生竞争,这种情况我们无法预料也无法阻止.这种做法当然没有问题,但是这会增加切换锁状态的算力开销
所以后来人们又提出了轻量级锁的概念,在某一个线程希望获取锁时这个线程将复制此时对象的信息并在该对象的对象头添加此时该对象已经被其他线程获取的信息.,在需要修改这个对象时先检查这个对象的值是否和获取锁时保持一致如果不一致则此次循环无效,一致则正常修改(这种操作成为CAS操作)
(注意,不管是重量级锁还是轻量级锁,它们的实现实际上都涉及到了硬件底层的不同指令,不止是简单的软件层面的差异)
由于这种做法在底层实现上与重量级锁的差异,实际上无法阻止别的线程去获取这个对象,所以实际上如果某次CAS操作失败,这个锁就将被直接升级为重量级锁,
轻量级锁更适合各个线程之间竞争不激烈时使用,这可以显著的加快程序的运行速度,但如果各个线程之间的竞争过于激烈,轻量级锁被升级为重量级锁的过程本身要消耗不少的资源,这是得不偿失的
Java中的atomic类与unsafe类就是使用轻量级锁实现的
偏向锁
实际上还有一类情况,如果在大部分时间内某个对象都是由一个线程反复的占有再释放,那么我们可以使用偏向锁来进一步提升性能,在这种锁机制下,当一个对象的锁反复被同一个线程获取时,这个对象的锁就会被一直打上这个线程的标签,此后该线程获取这个锁时不再需要反复的给对象的对象头添加信息,进一步的提高性能.不过如果某次CAS操作发生异常,这个锁就会升级为轻量级锁
要使用偏向锁,请修改JVM参数
1 |
|
补充
这里补充一点,所有的锁的升级过程都是不可逆的且消耗资源,所以在码代码时要实现考虑清楚各个线程之间资源的使用情况,避免锁升级造成的资源浪费
结语
兴之所至才写了这么一篇,所以内容有点简单,字数也不怎么样,将就着看吧.