分享

结合线程安全梳理synchronized与Lock

 IT乐知 2020-06-08

多线程与锁虽然我们平时开发中比较少的用到但是面试确实必问,这里简单梳理以下他们的关系。

多线程问题

基本所有的语言都支持多线程,但是多线程如果对共享数据操作,就有可能出现并不期待的结果,所以我们就要通过一些手段保证所有线程执行都能得到正确的结果,如果所有线程都能得到正确的结果那么说明线程执行的这段代码是线程安全的。

解决方案一:互斥

那么如何保证线程安全呢?只要保证对共享数据的访问是正确的!那么如何保证对共享数据正确访问呢?最直接的方法就是保证共享数据在同一时刻只被同一个线程访问(互斥同步)。

在Java中最基本的手段就是synchronized关键字,加了synchronized的代码在字节码会有monitorenter和monitorexit指令,当线程执行到monitorenter是会去获取对象的锁(这个对象可以是程序员指定,也可以根据synchronized所修饰的方法是实例方法或类方法判断是对象实例还是Class对象)。如果这个对象没被锁定或者当前线程已经持有这个对象的锁,就把这个锁的计数加一当遇到monitorexit就减一,如果当前线程获取对象的锁失败,那么线程阻塞,直到其他线程释放。

synchronized关键字的特点:1、同一个线程可重入,也就是不会出现自己锁自己的情况,2、如果获取锁失败就只能阻塞等待其他线程释放。线程的阻塞和唤醒需要需要消耗很多处理器时间,所以synchronized在Java中属于重量级的锁。

在JDK5中Java新加了java.util.concurrent.locks.Lock作为另一个种新的实现互斥同步的手段,它的实现重入锁(ReentrantLock)它与synchronized比较相似,不过它有一些高级功能,比如如果没有获取到锁的线程长时间没有获取到锁可以放弃等待先去执行其他的,再比如可以绑定多个条件对象。

synchronized与ReentrantLock简单流程对比图如下图:

        

虽然ReentrantLock拥有的功能比synchronized多,但是synchronized作为Java基本关键字语法更加简单清晰,同时ReentrantLock必须程序员手动释放锁,而synchronized则是由JVM控制保证,同时由于这么多年的发展synchronized性能并不弱于ReentrantLock,所以如果不是有其他需求还是可以优先使用synchronized。

解决方案二:比较交换

以上两种处理方式都会使线程阻塞和唤醒,他们都比较消耗性能。同时对共享数据默认加锁也不管具体情况,比如很多时候可能共享资源并不存在竞争,使没有必要加锁的,所以这种处理方式是悲观的,所以后面发展出了一种基于共享数据冲突检测的乐观处理方式,比如CAS(Compare-and-Swap,比较并且交换)

CAS的处理方式是拿到变量A进行处理后得到B,当要把B设置进去的时候先去验证对应A是否改变,如果没有改变则把B塞进去,否则不更新,不过不管是否更新都会把内存中的值返回回去。在JDK5中AtomicInteger的incrementAndGet方法等已经有它的实现。

终极方案

还有一种最终方案,那就是在根上去解决问题,就是使这段代码完全支持多线程,即当一个线程对这段代码执行到中途去而去执行其他代码但是完全不影响当前代码执行的结果。

有些人可能觉得这种时间很难,但是在我们web开发中实际上最常见了,我们都知道前端的一个请求都是对应我们服务器的一个线程,而我们的controller、service、dao一般都是单例模式的,可以说很多方法都是多线程状态的,可是我们平时开发基本很少去处理并发问题(除了数据库层面的),就是因为我们这些代码都是默认支持的多线程的。

总结

今天只简单的梳理了一点线程安全与锁的关系,和锁的一点发展,希望能给大家带来一点想法,不过这块的内容特别特别的多,后面会再继续慢慢分析。

Java程序员日常学习笔记,如理解有误欢迎各位交流讨论!

    本站是提供个人知识管理的网络存储空间,所有内容均由用户发布,不代表本站观点。请注意甄别内容中的联系方式、诱导购买等信息,谨防诈骗。如发现有害或侵权内容,请点击一键举报。
    转藏 分享 献花(0

    0条评论

    发表

    请遵守用户 评论公约

    类似文章 更多