搜公众号
推荐 原创 视频 Java开发 开发工具 Python开发 Kotlin开发 Ruby开发 .NET开发 服务器运维 开放平台 架构师 大数据 云计算 人工智能 开发语言 其它开发 iOS开发 前端开发 JavaScript开发 Android开发 PHP开发 数据库
Lambda在线 > 依码平川 > 并发编程5:深入理解Java虚拟机-线程安全

并发编程5:深入理解Java虚拟机-线程安全

依码平川 2019-04-09
举报

概述

       通过并发的方式提升了计算机的处理效率,然而并发的处理是非常复杂的,在计算机世界中当程序对一个对象进行处理时会被不停地进行中断和切换(线程调度),对象的属性可能会在中断期间被修改和变脏,如果对这种场景不做好处理,将导致程序处理错误。

       并发编程是为了提升效率,但若效率与正确性放在一起权衡,相信很多人会选择“正确性”,计算机也应如此,那我们就从如何保证并发的正确性,如何保证线程安全开始说起。

线程安全的定义

  • 定义一:如果一个对象可以安全地被多个线程同时使用,那他就是线程安全的。

  • 定义二:当多个线程访问同一个对象是,如果不考虑这些线程正在运行时环境下的调度和交替执行,也不需要进行额外的同步,或者在调用方进行任何其他的协调操作,调用这个对象的行为都可以获得正确的结果,那这个对象就是线程安全的。

    ----Brian Goetz(《Java Concurrency in practice》作者)

Java语言中的线程安全

不可变

  • 不可变的对象一定是线程安全的

  • 示例:final关键字修饰的对象,String 不可变对象,枚举类型 ,Long,Double,BigInteger,BigDecimal 等

  • 注:原子类AtomicInteger和AtomicLong并非不可变

绝对线程安全

  • 不管运行时环境如何,调用者都不需要任何额外的同步措施

  • Java API中标注自己是线程安全的类,大多数都不是绝对的线程安全

  • 比如: java.util.Vector是一个线程安全的容器,但也不意味着调用它的时候永远都不需要同步手段了

  • 并发编程5:深入理解Java虚拟机-线程安全

    运行结果:

    并发编程5:深入理解Java虚拟机-线程安全

    解决方案:


相对线程安全

  • 相对线程安全就是我们通常意义上的所讲的线程安全,调用的时候不需要额外的保障措施

  • Java中,大部分的线程安全类都属于相对线程安全:Vector,HashTable,Collections,synchronizedCollection()方法包装的集合等。

线程兼容

  • 线程兼容是指对象本身并不是线程安全的,但是可以通过在调用端正确地使用同步手段来保证对象在并发环境中安全地使用。

  • java中的案例:ArrayList,HashMap,HashSet,Object

线程对立

  • 线程对立是指不管调用端是否采取了同步措施,都无法再多线程环境中并发使用的代码。

  • Java语言天生就具备多线程特性,线程对立这种排斥多线程的代码是很少出现的,而且通常都是有害的,应当尽量避免

  • java中线程对立的例子

           如果并发进行的话,无论调用时是否进行了同步,目标线程都是存在死锁的风险的,如果suspend()中断的线程就是即将要执行resume()的那个线程,那就肯定要产生死锁了,正是由于这个原因,suspend()和resume()方法已经被JDK声明废弃(@Deprecated)了。

    • System类中的部分方法:System.setIn(),System.setOut(),System.funFinalizersOnExit()等。

    • Thread类的suspend()中断线程和resume()方法恢复线程

线程安全的实现方法

互斥同步

  • 互斥是实现同步的一种手段,临界区、互斥量、和信号量都是主要的互斥实现方式 。

  • 同步互斥的问题

           互斥同步最主要的问题是进行线程阻塞和唤醒所带来的性能问题,因此这种同步也被成为阻塞同步。      

    另外,它属于一种悲观的并发策略,总是认为只要不去做正确的同步措施(加锁)就会出现问题,无论共享数据是否真的会出现竞争,它都要进行加锁、用户态核心态转换、维护锁计数器和检查是否有被阻塞的线程需要被唤醒等操作。

  • Java中的实现

    • synchronized关键字(原生语法层面的互斥锁),原理:在同步块的前后分别形成monitorenter和monitorexit这两个字节码指令,这两个字节码都需要一个reference类型的参数来指明要锁定的解锁对象。

    • juc: ReentrantedLock(Api层面的互斥锁)

非阻塞同步

  • 非阻塞同步概念 :非阻塞同步是基于冲突检测的乐观并发策略,许多实现都不需要把线程挂起(自旋处理,不断重试),因此这种操作被称为非阻塞同步,需要依赖硬件指令集 。

  • 非阻塞同步实现方式:CAS

    • ABA问题解决方式:JUC为了解决这个问题,提供了已个带有标记的原子引用类“AtomicStampedReference”,它可以通过控制变量值的版本来保证CAS的正确性。不过这个类目前来说比较鸡肋,大部分情况下ABA问题不会影响程序并发的正确性,如果要解决ABA问题,改用传统的互斥同步可能会比原子类更高效。

    • JDK1.5之后,JAVA程序才可以使用CAS操作,该操作由sun.misc.Unsafe 类里面的compareAndSwapInt()和compareAndSwapLong()等几个方法包装提供 。

    • CAS问题:ABA问题

无同步方案

  •  要保证线程安全,并不是一定要进行同步,两者没有因果关系。同步只是保障共享数据争用时的正确性手段,如果一个方法本来就不涉及共享数据,那它自然就无须任何同步措施去保证正确性,因此有一些代码天生就是线程安全的。

  • 两类无同步方案解决线程安全问题

    • 原理:把共享数据的可见范围限制在同一个线程之内,这样,无须同步也能保证线程之间不出现数据征用的问题。

    • 实现方案

    • 消费队列的架构模式(如生产者消费者模式)都会讲产品的消费过程尽量在一个线程中消费完,其中最中重要的一个应用实例就是经典的Web交互模式种的“一个请求对应一个服务器线程”的处理方式 。

    • ThreadLocal:每一个线程的Thread对象中都有一个ThreadLocalMap对象,这个对象存储了一组以ThreadLocal.threadLocalHashCode为键,以本线程变量为值的K-V值对,ThreadLocal对象就是当前线程的ThreadLocalMap的访问入口,每一个独一无二的threadLocalHashCode值,使用这个值就可以在线程K-V值对中找到对应的本地线程变量 。

    • 可重入代码

           可重入代码有一些共同的特性:例如不依赖存储在堆上的数据和公用的系统资源、用到的状态量都有参数中传入、比调用非可重入的方法等。

    • 线程本地存储

高频面试题:synchronized与lock的异同?

  • 共同点

    • 都实现了互斥同步,synchronized关键字是基于原生语法实现,ReentrantLock基于Java API实现

    • 都具备线程可重入的特性

  • 区别

    • 等待可中断

    • 实现公平锁

    • 锁和绑定多个条件

    • ReentrantLock比synchronized增加了一些高级动作,主要有以下三项

  • ReentrantLock与synchronized的性能对比

    • jdk1.6之前,ReentrantLock的性能优于synchronized

    • jdk1.6版中加入了很多针对synchronized锁的优化措施,性能因素就不再是选择ReentrantLock的理由了

    • 虚拟机在未来的性能改进中肯定也会更加偏向于原生的synchronized,所以还是提倡在synchronized能实现需求的情况下,优先考虑使用synchronized来进行同步 。

总结

       本节介绍了实现高效并发的必要条件:线程安全,对java语言中线程安全的实现方式做了分类及比较,理论性很强,需要花点时间去理解。

       下一篇我们将认识下并发编程中的各种锁,了解下为了实现高效并发,JVM虚拟机中做了哪些优化。

       理论性概念介绍完后,后续章节将介绍一些实操性强的案例进行补充学习,欢迎继续关注。


版权声明:本站内容全部来自于腾讯微信公众号,属第三方自助推荐收录。《并发编程5:深入理解Java虚拟机-线程安全》的版权归原作者「依码平川」所有,文章言论观点不代表Lambda在线的观点, Lambda在线不承担任何法律责任。如需删除可联系QQ:516101458

文章来源: 阅读原文

相关阅读

举报