zoukankan      html  css  js  c++  java
  • JAVA虚拟机JVM-5.多线程以及锁

    线程安全

    Java语言中,不可变的对象一定是线程安全的,无论是对象的方法实现还是方法的调用者,都不需要在进行任何线程安全的保障措施。

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

    Java类库API中符合不可变要求的类型,除了String之外,常用的还有枚举类型以及java.lang.Number的部分子类,如Long和Double等数值包装类型、BigInteger和BigDecimal等大数据类型。但同为Number子类型的原子类AtomicInteger和AtomicLong则是可变的。

    绝对线程安全

    javaApi中标注自己是线程安全的类,大多数都不是绝对的线程安全。Vector所有方法都被标记为同步,但是当在多线程情况下,错误的删除了某一个值,就会出现ArrayIndexOutOfBoundsException。

    相对线程安全

    通常意义下的线程安全,大部分声称自己是线程安全的类是相对线程安全。

    线程兼容

    线程兼容是指对象本身并不是线程安全,但是可以通过调用端正常的使用同步方法来保证对象在并发环境中安全地使用。Java中大部分类都是线程兼容的。

    线程对立

    线程对立是指不管调用端是否采用了同步措施,都无法在多线程环境中使用代码。Thread类中的suspend()方法和resume()方法,两个线程同时持有一个对象,一个尝试中断,一个尝试恢复,无论是否经过了同步措施,都有死锁的风险。

    线程安全的实现方法

    互斥同步

    Synchornized的使用

    最常见的也是最主要的并发正确性保障手段。同步是指在多线程并发访问共享数据时,保证共享数据在同一个时刻只被一条线程使用。而互斥是实现同步的一种手段,临界区、互斥量和信号量都是常见的互斥实现方式。

    Synchronized关键字是最基本的互斥同步的手段。该关键字在Javac编译后会在同步块中前后加入monitorenter和monitorexit两个字节码指令。这两个字节码指令都需要一个reference类型的参数来指明要锁定和解锁的对象。

    执行monitorenter指令的时候,首先尝试获取对象的锁,如果没有被锁定,或者当前线程持有那个对象的锁,就把锁的计数器的值加一,而在执行monitorexit指令时会将锁计数器的值减一。一旦计数器的值为零,锁就会被释放。如果当前的线程获取对象锁失败,那么该线程会被阻塞等待,直到请求锁定的对象被持有他的线程释放为止。

    Java线程模型是映射到操作系统的内核线程上的,每次阻塞一个线程或者唤醒一个线程都需要从用户态到内核态的转换,这种转换状态耗费系统时间。

    Lock的使用

    重入锁是Lock接口最常见的一种实现,他与Synchornized一样是可重入的。基本用法上ReentrantLock与Synchornized类似,只是写法上面有区别。多了一些功能。

    等待可中断:是指当前持有锁的线程长期不释放锁的时候,正在等待的线程可以选择放弃等待,改为处理其他事情。
    公平锁:多线程等待同一个锁的时候,必须按照申请时间排序依次获取锁。非公平锁则不保证这一点,在锁被释放的时候,任何一个等待的线程都有机会获得锁。(Synchornized是非公平锁,ReentrantLock在默认下也是非公平锁,但可以通过带boolean值的函数要求使用公平锁,但是使用了公平锁,会影响ReentrantLock的性能,会明显影响吞吐量)
    锁绑定多个条件:是指ReentrantLock绑定多个condition对象。

    非阻塞同步

    互斥同步面临的是线程阻塞和唤醒带来的状态切换的性能开销,互斥同步属于悲观锁。

    基于CAS指令的乐观锁属于非阻塞同步,CAS指令需要三个操作数,分别是内存位置V、旧的预期值A和准备设置的新值B。CAS指令执行时,当且仅当V符合A时,处理器才会用B更新V的值,否则不执行更新。上述操作过程是一个原子操作,执行期间不会被其他线程中断。

    CAS存在ABA问题,通过控制变量值的版本保证CAS操作的正确性,不过一般ABA问题并不影响程序的正确性,如果需要的话最好采用互斥同步。(J.U.C包提供的原子标记引用类AtomicStampedReference)

    无同步方案

    可重入代码与线程本地存储(ThreadLocal)是天生线程安全的实现方式。

    可重入代码:可以在代码执行的任何时候中断,去执行另一段代码,而在控制权返回后,原来的程序还能继续执行,不会出现错误。

    线程本地存储:如果一段代码中可以保证多线程共享的数据在同一个线程执行,那么我们可以把共享数据的可见范围控制在同一个线程内,符合这种特点的应用不少见,比如消费队列架构模式。Java中也可以使用volatile关键字也是。

    Java语言中,ThreadLocal类来实现线程本地存储的功能,每个线程的Threa对象都有一个ThreadLocalMap对象,这个对象存储了一组ThreadLocal.threadLocalHasCode为键,本地线程变量为值的K-V值对,ThreadLocal对象就像一个当前线程的ThreadLocalMap的访问入口,每一个ThreadLocal对象都包含了一个独一无二的threadLocalHashCode值,使用这个值就可以找到对应的本地线程的变量。

  • 相关阅读:
    python易混易乱(2)
    python易混易乱(1)
    #1062 – Duplicate entry ‘1’ for key ‘PRIMARY’
    关于 flask 实现数据库迁移以后 如何根据创建的模型类添加新的表?
    Linux同步互斥(Peterson算法,生产者消费者模型)
    正则表达式(Python)
    进程间通信
    CSS常见简写规则整理
    Django Model
    Django杂记
  • 原文地址:https://www.cnblogs.com/wangb0402/p/12641451.html
Copyright © 2011-2022 走看看