• 深入理解java虚拟机笔记线程安全与锁优化


     

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

    一、Java语言中的线程安全

    我们已经有了线程安全的一个可操作的定义, 那接下来就讨论一下: 在Java语言中, 线程安全具体是如何体现的? 有哪些操作是线程安全的? 我们这里讨论的线程安全, 将以多个线程之间存在共享数据访问为前提。

    因为如果根本不存在多线程, 又或者一段代码根本不会与其他线程共享数据, 那么从线程安全的角度上看, 程序是串行执行还是多线程执行对它来说是没有什么区别的。

    为了更深入地理解线程安全, 在这里我们可以不把线程安全当作一个非真即假的二元排他选项来看待, 而是按照线程安全的“安全程度”由强至弱来排序, 我们可以将Java语言中各种操作共享的数据分为以下五类: 不可变、 绝对线程安全、 相对线程安全、 线程兼容和线程对立。

    1.不可变

    在Java语言里面 , 不可变(Immutable) 的对象一定是线程安全的, 无论是对象的方法实现还是方法的调用者, 都不需要再进行任何线程安全保障措施。 “不可变”带来的安全性是最直接、最纯粹的。

    Java语言中, 如果多线程共享的数据是一个基本数据类型, 那么只要在定义时使用final关键字修饰它就可以保证它是不可变的。 如果共享数据是一个对象, 由于Java语言目前暂时还没有提供值类型的支持, 那就需要对象自行保证其行为不会对其状态产生任何影响才行。

    在Java类库API中符合不可变要求的类型, 除了上面提到的String之外, 常用的还有枚举类型及 java.lang.Number的部分子类, 如Long和Double等数值包装类型、 BigInteger和BigDecimal等大数据类型。 但同为Number子类型的原子类AtomicInteger和AtomicLong则是可变的, 读者不妨看看这两个原子类的 源码, 想一想为什么它们要设计成可变的。

    2.绝对线程安全

    绝对的线程安全能够完全满足Brian Goetz给出的线程安全的定义, 这个定义其实是很严格的, 一个类要达到“不管运行时环境如何, 调用者都不需要任何额外的同步措施”可能需要付出非常高昂的,甚至不切实际的代价。

    在Java API中标注自己是线程安全的类, 大多数都不是绝对的线程安全。 我们可以通过Java API中一个不是“绝对线程安全”的“线程安全类型”来看看这个语境里的“绝对”究竟是什么意思。

    如果说java.util.Vector是一个线程安全的容器, 相信所有的Java程序员对此都不会有异议, 因为它的add()、 get()和size()等方法都是被synchronized修饰的, 尽管这样效率不高, 但保证了具备原子性、可见性和有序性。 不过, 即使它所有的方法都被修饰成synchronized, 也不意味着调用它的时候就永远都不再需要同步手段了。

    很明显, 尽管这里使用到的Vector的get()、 remove()和size()方法都是同步的, 但是在多线程的环境中, 如果不在方法调用端做额外的同步措施, 使用这段代码仍然是不安全的。 因为如果另一个线程恰好在错误的时间里删除了一个元素, 导致序号i已经不再可用, 再用i访问数组就会抛出一个ArrayIndexOutOfBoundsException异常。

    3.相对线程安全

    相对线程安全就是我们通常意义上所讲的线程安全, 它需要保证对这个对象单次的操作是线程安全的, 我们在调用的时候不需要进行额外的保障措施, 但是对于一些特定顺序的连续调用, 就可能需要在调用端使用额外的同步手段来保证调用的正确性。 代码清单13-2和代码清单13-3就是相对线程安全的案例。

    在Java语言中, 大部分声称线程安全的类都属于这种类型, 例如Vector、 HashTable、 Collections的 synchronizedCollection()方法包装的集合等。

    4.线程兼容

    线程兼容是指对象本身并不是线程安全的, 但是可以通过在调用端正确地使用同步手段来保证对象在并发环境中可以安全地使用。 我们平常说一个类不是线程安全的, 通常就是指这种情况。 Java类库API中大部分的类都是线程兼容的, 如与前面的Vector和HashTable相对应的集合类ArrayList和 HashMap等。

    5.线程对立

    线程对立是指不管调用端是否采取了同步措施, 都无法在多线程环境中并发使用代码。 由于Java语言天生就支持多线程的特性, 线程对立这种排斥多线程的代码是很少出现的, 而且通常都是有害的, 应当尽量避免。

    一个线程对立的例子是Thread类的suspend()和resume()方法。 如果有两个线程同时持有一个线程对象, 一个尝试去中断线程, 一个尝试去恢复线程, 在并发进行的情况下, 无论调用时是否进行了同步, 目标线程都存在死锁风险——假如suspend()中断的线程就是即将要执行resume()的那个线程, 那就肯定要产生死锁了。 也正是这个原因, suspend()和resume()方法都已经被声明废弃了。

    常见的线程对立的操作还有System.setIn()、 Sytem.setOut()和System.runFinalizersOnExit()等。

     

  • 相关阅读:
    java wait 与 notify sleep
    java线程安全总结
    ubuntu安装遇到的问题
    python时间处理函数
    js获取当前时间
    sql如何将同个字段不同值打印在一行
    django models数据类型
    django上传图片和文字记录
    django form使用学习记录
    django中request对象详解(转载)
  • 原文地址:https://www.cnblogs.com/wangbin2188/p/16039540.html
Copyright © 2020-2023  润新知