资讯专栏INFORMATION COLUMN

浅谈Java中锁的实现和优化

DevWiki / 1311人阅读

摘要:这两种策略的区别就在于,公平策略会让等待时间长的线程优先执行,非公平策略则是等待时间长的线程不一定会执行,存在一个抢占资源的问题。

之前有一篇文章我们简单的谈到了Java中同步的问题,但是可能在平常的开发中,有些理论甚至是某些方式是用不到的,但是从程序的角度看,这些理论思想我们可以运用到我们的开发中,比如是不是应该一谈到同步问题,就应该想到用synchronized?,什么时候应该用ReentrantLock?,是不是应该考虑用原子类解决某些问题?那我们就来聊一聊我们如何用这个锁。

上一篇文章中第一个例子,我们后来通过对方法加synchronized修改了代码,在这里还有一种修改方式,我们可以考虑使用原子类,这样也可以解决这个问题,我们来看看伪代码:

private static AtomicInteger i = new AtomicInteger(0);

private static void increse(){
        i.incrementAndGet();
    }

因为在这个问题上,不只是有线程的可见性问题,还有一个就是操作的原子性问题,说到这里,我们应该明白,在我们平常的开发中,有时候应该区分什么时候是需要原子操作,什么时候只需要可见性,那么这样我们才能够正确的判断用某种合理的方式写出合理的代码。

好,这是上次的一点小问题我们再这里重复一下,接下来我们聊一聊具体的锁。

互斥同步

synchronized就是一个很典型的例子,这种锁也叫做可重入锁,就是一个线程持有一个相同的锁对象,可以进行重复加解锁,换句话说,持有相同锁对象的线程不会自己把自己锁住。

还有另外一个和synchronized很相近的锁,就是我们上面提到的ReentrantLock,这个和synchronized一样,都提供了可重入性,这两个锁的效果是差不多的(在以前的一些比较旧的JDK版本中,并发数比较大的情况下,ReentrantLock的性能是要优于synchronized的),可能有些小伙伴还没有用过这种锁,那这里我们就简单说一下具体用法:

ReentrantLock lock = new ReentrantLock();

lock.lock();
// code
lock.unlock();

这里从API的层面提供了一个比较简单的写法,同时也提供了一些比较高级的特性,像信号量,线程终止等等,有兴趣的小伙伴可以去查阅相关资料去了解一下,在这里我们不深入探讨,但是有一个地方需要注意一下,就是ReentrantLock提供了两种竞争策略,一种是公平策略,另一种是非公平策略。

ReentrantLock lock = new ReentrantLock();// 非公平策略

ReentrantLock lock = new ReentrantLock(true);// 公平策略

公平策略与非公平策略

这里我就先举一个比较简单的例子,如果我们在公司上班,有时候我们中午会带午饭,然后需要用微波炉来加热一下,那这里就有个问题了,在我们去加热午饭的时候,如果前面有同事在排队,当然了,我们都是有素质的人,出于礼貌,我们也需要排队,等待前面的同事操作完,后面来的同事当然也需要排在我们后面,那这种情况下,我们可以称之为公平策略
如果在前面正在热午饭的同事,他有关系比较好的同事也来热午饭,这个时候是否可以插队,如果这位同事插队成功了,那么他就可以继续热午饭了,那么我们又得在后面等了,这种情况我们可以称之为非公平策略。这两种策略的区别就在于,公平策略会让等待时间长的线程优先执行,非公平策略则是等待时间长的线程不一定会执行,存在一个抢占资源的问题。如果从源码的角度来看,那么我们就来简单的说一下非公平策略的执行方式。

非公平策略执行方式

一个线程首先会试探性的获取一次锁,如果获取到,则将当前锁设置为该线程独占,如果没有设置成功,则再次试探性的获取一次,如果还是没有成功,则将该线程加入到等待队列,后面再次等待获取,看到这里,可能有一些小伙伴不太明白了,那么非公平是体现在哪里,如果排在你前面的以为同事刚好热好午饭,然后你在后面玩手机,前面的同事还没有跟你说,“喂,该你去热午饭了。”,然后这会儿又来了一个其他人插队,那么作为高素质的你,肯定不能和别人计较了,好了,那就等着吧,非公平策略就体现在这里了。这里实现的方式很复杂,可以一点一点去看,其中有用到AQSCAS等这些比较底层的原理,值得一提的是,现在JVMsynchronized的优化已经相当不错,其性能表型已经和ReentrantLock不相上下,如果从推荐的角度来说,还是推荐使用synchronized,除非需要使用一些比较高级的特性。

非互斥同步

接下来我们再来聊一聊什么是非互斥同步,互斥同步就是指阻塞同步,就是阻塞线程让其一直等待某个锁可用的过程,那么非互斥同步刚和和这个是对立的,这里用到了一个CAS的原理,这个是操作系统的指令,即compare and swap,比较并交换,说的简单一点,就是如果在内存中一个值为V,然后我们又一个预估值A,然后还有一个新值B,如果V和A相同,那么就把V的值替换为B,然后返回V,这里要说一下,无论是否能替换成功,都会返回V的值,这个过程称作CAS。这种就涉及到操作系统级别了,因为在之前的阻塞同步中,阻塞线程然后再将其恢复获得锁的过程,是比较耗费性能的,我们知道,Java中的线程是对操作系统线程的一个映射,如果我们在阻塞同步的时候,将一个线程挂起,然后再恢复,那么这里要经历一个向核心态的转换过程,消耗是比较巨大的。因此,在非互斥同步中,通过CAS这种方式,能够相对比较好的处理这个问题,但是有个问题需要明确一下,就是在Java中,这种处理方式,是通过这个类来完成的,sun.misc.Unsafe,这个类只能通过boot class loader来调用,要么就是通过反射,或者通过某些方法来调用,比如AtomicInteger类中的incrementAndGet()方法,当然,还有很多这种方法,个人也是对CAS这种机制理解的比较片面,还需要更加深层次的研究。到这里,我们不得不说,Doug Lea的编码能力实属上乘,确实佩服!

锁优化

我们再来谈一谈锁优化,JVM为我们的代码或者说其内部就做了一些优化方式,我们就来简单的说几个。

自旋

什么是自旋,是的,有些小伙伴也可以这么理解,就是自己旋转,当然这是开个玩笑,不过这种方式对于阻塞线程来说,是有一定的效果,简单来说,就是如果一个线程获得了锁,然后另一个线程按照以前的方式只能阻塞等待,那么JVM对这里做了一个优化,就是让这种等待的线程去执行一个循环,但是此时CPU的时间片是不会让出去的,也就是说这里的这个线程还是占有着一个处理时间,如果循环结束之后,这个锁就能获取了,那这个线程就拿到这个锁继续执行,这样做的一个目的就在于我们上面说的,线程从挂起到恢复需要像核心态的一个转换,这个性能的消耗和占用时间片的消耗比是很大的,但是同时也有一个问题就是如果自旋结束后,还是没有获得锁,那么这段时间性能的消耗就是浪费了,所以也很难权衡,因此在一些比较旧的JDK版本中,JVM是禁止使用自旋的。

自适应自旋

这里就是说如果一个线程在一次成功的自旋结束后,并且成功的获得了锁然后成功运行,那么JVM会“认为”这次自旋是成功的,那么下次如果继续有线程发生自旋,那么JVM能够判断出来是否需要让这个线程自旋来减少性能的消耗,从这里来看,JVM还是相对比较“机智”的。

锁消除

这个例子在上一篇文章中曾经提到过,对于字符串的拼接,我们反编译就能看出来,如果能够判断不存在方法逃逸的情况下,那么JVM会对这种操作转换为StringBuilderappend操作,可能有的小伙伴会有疑问,为什么不转换为StringBuffer呢?因为JVM已经能够正确判断出没有方法逃逸,那么如果再用线程安全类来处理也意义不大。

好了,这篇文章就先到这里了,这里只是很简略的聊了一下在Java中的锁的相关知识,更深入的还有待后面继续研究。

文章版权归作者所有,未经允许请勿转载,若此文章存在违规行为,您可以联系管理员删除。

转载请注明本文地址:https://www.ucloud.cn/yun/76670.html

相关文章

  • 浅谈Java锁的问题

    摘要:之前我们简单的讨论了一下关于中的同步还有一些锁优化的问题,今天我们就来简单的聊一聊关于中的死锁问题。这里显示两个线程的状态现在是处于阻塞状态,然后都在等待锁的获取,我们再继续往下看。 之前我们简单的讨论了一下关于Java中的同步还有一些锁优化的问题,今天我们就来简单的聊一聊关于Java中的死锁问题。 这个问题我们在开发的时候,或多或少都能遇到,对业务逻辑没有正确的梳理,又或者是在多线程...

    fox_soyoung 评论0 收藏0
  • 【推荐】最新200篇:技术文章整理

    摘要:作为面试官,我是如何甄别应聘者的包装程度语言和等其他语言的对比分析和主从复制的原理详解和持久化的原理是什么面试中经常被问到的持久化与恢复实现故障恢复自动化详解哨兵技术查漏补缺最易错过的技术要点大扫盲意外宕机不难解决,但你真的懂数据恢复吗每秒 作为面试官,我是如何甄别应聘者的包装程度Go语言和Java、python等其他语言的对比分析 Redis和MySQL Redis:主从复制的原理详...

    BicycleWarrior 评论0 收藏0
  • 【推荐】最新200篇:技术文章整理

    摘要:作为面试官,我是如何甄别应聘者的包装程度语言和等其他语言的对比分析和主从复制的原理详解和持久化的原理是什么面试中经常被问到的持久化与恢复实现故障恢复自动化详解哨兵技术查漏补缺最易错过的技术要点大扫盲意外宕机不难解决,但你真的懂数据恢复吗每秒 作为面试官,我是如何甄别应聘者的包装程度Go语言和Java、python等其他语言的对比分析 Redis和MySQL Redis:主从复制的原理详...

    tommego 评论0 收藏0
  • 浅谈java中的并发控制

    摘要:并发需要解决的问题功能性问题线程同步面临两个问题,想象下有两个线程在协作工作完成某项任务。锁可用于规定一个临界区,同一时间临界区内仅能由一个线程访问。并发的数据结构线程安全的容器,如等。 并发指在宏观上的同一时间内同时执行多个任务。为了满足这一需求,现代的操作系统都抽象出 线程 的概念,供上层应用使用。 这篇博文不打算详细展开分析,而是对java并发中的概念和工具做一个梳理。沿着并发模...

    Gilbertat 评论0 收藏0

发表评论

0条评论

最新活动
阅读需要支付1元查看
<