美文网首页
十六 synchronized实现 锁升级降级

十六 synchronized实现 锁升级降级

作者: BeYearn | 来源:发表于2018-11-22 20:20 被阅读0次
    1. 在 Java 6 之前,Monitor 的实现完全是依靠操作系统内部的互斥锁,因为需要进行用户态到内核态的切换,所以同步操作是一个无差别的重量级操作。

    现代的(Oracle)JDK 中,JVM 对此进行了大刀阔斧地改进,提供了三种不同的 Monitor 实现,也就是常说的三种不同的锁:偏斜/向锁(Biased Locking)、轻量级锁和重量级锁,大大改进了其性能。

    所谓锁的升级、降级,就是 JVM 优化 synchronized 运行的机制,当 JVM 检测到不同的竞争状况时,会自动切换到适合的锁实现,这种切换就是锁的升级、降级。

    当没有竞争出现时,默认会使用偏斜锁。JVM 会利用 CAS 操作(compare and swap),在对象头上的 Mark Word 部分设置线程 ID,以表示这个对象偏向于当前线程,所以并不涉及真正的互斥锁。这样做的假设是基于在很多应用场景中,大部分对象生命周期中最多会被一个线程锁定,使用偏斜锁可以降低无竞争开销。

    如果有另外的线程试图锁定某个已经被偏斜过的对象,JVM 就需要撤销(revoke)偏斜锁,并切换到轻量级锁实现。轻量级锁依赖 CAS 操作 Mark Word 来试图获取锁,如果重试成功,就使用普通的轻量级锁;否则,进一步升级为重量级锁。

    1. 为什么我们需要读写锁(ReadWriteLock)等其他锁呢?

    这是因为,虽然 ReentrantLock 和 synchronized 简单实用,但是行为上有一定局限性,通俗点说就是“太霸道”,要么不占,要么独占。实际应用场景中,有的时候不需要大量竞争的写操作,而是以并发读取为主,如何进一步优化并发操作的粒度呢?

    Java 并发包提供的读写锁等扩展了锁的能力,它所基于的原理是多个读操作是不需要互斥的,因为读操作并不会更改数据,所以不存在互相干扰。而写操作则会导致并发一致性的问题,所以写线程之间、读写线程之间,需要精心设计的互斥逻辑。

    public class RWSample {
        private final Map<String, String> m = new TreeMap<>();
        private final ReentrantReadWriteLock rwl = new ReentrantReadWriteLock();
        private final Lock r = rwl.readLock();
        private final Lock w = rwl.writeLock();
        public String get(String key) {
            r.lock();
            System.out.println(" 读锁锁定!");
            try {
                return m.get(key);
            } finally {
                r.unlock();
            }
        }
    
        public String put(String key, String entry) {
            w.lock();
        System.out.println(" 写锁锁定!");
                try {
                    return m.put(key, entry);
                } finally {
                    w.unlock();
                }
            }
        // …
        }
    
    1. StampedLock
      JDK 在后期引入了 StampedLock,在提供类似读写锁的同时,还支持优化读模式。优化读基于假设,大多数情况下读操作并不会和写操作冲突,其逻辑是先试着读取,然后通过 validate 方法确认是否进入了写模式,如果没有进入,就成功避免了开销
    public class StampedSample {
        private final StampedLock sl = new StampedLock();
    
        void mutate() {  //写
            long stamp = sl.writeLock();
            try {
                write();
            } finally {
                sl.unlockWrite(stamp);
            }
        }
    
        Data access() { //读
            long stamp = sl.tryOptimisticRead();
            Data data = read();
            if (!sl.validate(stamp)) {
                stamp = sl.readLock();
                try {
                    data = read();
                } finally {
                    sl.unlockRead(stamp);
                }
            }
            return data;
        }
        // …
    }
    
    1. 自旋锁:竞争锁的失败的线程,并不会真实的在操作系统层面挂起等待,而是JVM会让线程做几个空循环(基于预测在不久的将来就能获得),在经过若干次循环后,如果可以获得锁,那么进入临界区,如果还不能获得锁,才会真实的将线程在操作系统层面进行挂起。

    适用场景:自旋锁可以减少线程的阻塞,这对于锁竞争不激烈,且占用锁时间非常短的代码块来说,有较大的性能提升,因为自旋的消耗会小于线程阻塞挂起操作的消耗。
    自旋锁只有在多核CPU上有效果,单核毫无效果,只是浪费时间

    相关文章

      网友评论

          本文标题:十六 synchronized实现 锁升级降级

          本文链接:https://www.haomeiwen.com/subject/roqrqqtx.html