线程安全

线程安全

  1. 线程安全的定义
    当多个线程访问一个对象时,如果不用考虑这些线程在运行时环境下的调度和交替执行,也不需要进行额外的同步,或者在调用方进行任何其他的协调操作,调用这个对象的行为都可以获得正确的结果,那这个对象是线程安全的。
  2. Java语言中各种操作共享数据的情况,按照线程安全程度,由强至弱来排序
    不可变(Immutable)
    不可变的对象一定是线程安全的,无论是对象的方法实现还是方法的调用者,都不需要再采取任何的线程安全保障措施。
    如果共享数据是一个对象,那就需要保证对象的行为不会对其状态产生任何影响才行,例如java.lang.String
    类的对象,它是一个典型的不可变对象,调用它的substring(),replace()和从cat()这些方法都不会影响它原来的值,只会返回一个新构造的字符串对象。
    比可变的类型:String、枚举类型、java.lang.Number的部分子类(Long、Double、BigInteger、BigDecimal);但是Number下的AtomicInteger和AtomicLong则并非不可变的。
  3. 绝对线程安全
    绝对线程安全完全满足上面线程安全的定义。但是实现起来难度很大。
  4. 相对线程安全
    就是通常意义上所讲的线程安全,需要保证对这个对象单独的操作是线程安全的。
  5. 线程兼容
    线程兼容是指对象本身不是线程安全的,但是可以通过在调用端正确地使用同步手段来保证对象在并发环境中可以安全地使用。
  6. 线程对立
    线程对立是指无论调用端是否采取了同步措施,都无法在多线程环境中并发使用的代码。

线程安全实现方法

  1. 互斥同步(Mutual  Exclusion&Synchronization)
    互斥同步是常见的一种并发正确性保障手段。互斥是因,同步是果;互斥是方法,同步是目的。同步是指在多个线程并发访问共享数据时,保证共享数据在同一个时刻只被一个(或者一些,使用信号量的时候)线程使用。而互斥是实现同步的一种手段,临界区(Critical  Section)、互斥量(Mutex)、信号量(Semaphore)都是主要的互斥实现方式。
    在Java中,最基本的互斥同步手段---synchronized关键字,synchronized关键字经过编译之后,会在同步的前后分别形成monitorenter和monitorexit这两个字节码指令,这两个字节码都需要一个reference类型的参数来指明要锁定和解锁的对象。如果Java程序中的synchronized明确指定了对象参数,那就是这个对象的reference;如果没有明确指定,那就根据synchronized修饰的是实例方法还是类方法,去取对应的对象实例或Class对象来作为锁对象。
    synchronized同步块对同一条线程来说是可重入的,不会出现自己把自己锁死的问题。同步块在已进入的线程执行完之前,会阻塞后面其他线程的进入。
    Java的线程是映射到操作系统的原生线程之上的,如果要阻塞或唤醒一个线程,都需要操作系统来帮忙完成,这就需要从用户态转换到和心态中,因此状态转换需要耗费很多处理器时间。所以synchronized是Java语言中一个重量级(Heavyweight)的操作,虚拟机本身也会进行一些优化,在通知操作系统阻塞线程之前加入一段自旋等待过程,避免频繁地切入到核心态之中。
    java.util.concurrent(下文称J.U.C)包中的重入锁(ReentrantLock)来实现同步,具备与synchronized一样的线程重入特性。相比synchronized,ReentrantLock增加了一些高级功能:等待可中断、可实现公平锁、锁可以绑定多个条件。
    等待可中断:当持有锁的线程长期不释放锁的时候,正在等待的线程可以选择放弃等待,改为处理其他事情。
    公平锁:多个线程在等待同一个锁时,必须按照申请锁的时间顺序来一次获得锁。而非公平锁则不保证这一点,在锁被释放时,任何一个等待锁的线程都有机会获得锁。synchronized中的锁是非公平锁,ReentrantLock默认情况下也是非公平的,但可以通过带布尔值的构造函数要求使用公平锁。
    锁绑定多个条件:一个ReentrantLock对象可以同时绑定多个Condition对象,而synchronized中,锁对象的wait()和notify()、notifyAll()方法可以实现一个隐含的条件,如果要和多于一个的条件关联的时候,就不得不额外地添加一个锁,而ReentrantLock则无须这样做,只需要多次调用newCondition()方法即可。
  2. 非阻塞同步(Non-Blocking  Synchronization)
    互斥同步最主要的问题就是进行线程阻塞和唤醒所带来的性能问题,因此这种同步也称为阻塞同步(Blocking  Synchronization)。非阻塞同步是基于冲突检测的乐观并发策略,通俗的说,就是先进行操作,如果没有其他线程争用共享数据,那操作就成功了;如果共享数据有争用,产生了冲突,那就再采取其他的补偿措施(最常见的补偿措施就是不断的重试,直到成功为止)。
    CAS(Compare-And-Swap)指令需要有三个操作数,分别是内存位置(在java中可以简单理解为变量的内存地址,用V表示)、旧的预期值(用A表示)和新值(用B表示)。CAS指令执行时,当且仅当V符合旧预期值A时,处理器用新值B更新V的值,否则它就不执行更新,但是无论是否 更新了V的值,都会返回V的旧值,上述的处理过程是一个原子操作。
  3. 无同步方案
    要保证线程安全,并不是一定就要进行同步,两者没有因果关系。同步只是保证共享数据争用时的正确性的手段,如果一个方法本来就不涉及共享数据,那它自然就无须任何同步措施去保证正确性,因此会有一些代码天生就是线程安全的。
    可重入代码(Reentrant  Code):这种代码也叫纯代码(Pure Code),可以在代码执行的任何时刻中断它,转而去执行另外一段代码(包括递归调用它本身),而在控制权返回后,原来的程序不会出现任何错误。可重入代码的共同特征:不依赖存储在堆上的数据和公用的系统资源、用到的状态量都由参数传入、不可调用非可重入的方法等。
    线程本地存储(Thread  Local  Storage):可以把共享数据的可见范围限制在同一个线程之内,无须同步也能保证线程之间不出现数据争用的问题。Web交互模型中的“一个请求对应一个服务线程”(Thread-per-Request)的处理方式,这种处理方式的广泛应用使得很多Web服务端应用都可以使用线程本地存储来解决线程安全问题。
    在java语言中,如果一个变量要被多线程访问,可以使用volatile关键字声明它为“易变的”;如果一个变量要被某个线程独享,可以通过java.lang.ThreadLocal类来实现线程本地存储的功能。每个线程的Thread对象中都有一个ThreadLocalMap对象,这个对象存储了一组以ThreadLocal.threadLocalHashCode为键,以本地线程变量为值的K-V值对,ThreadLocal对象就是当前线程的ThreadLocalMap的访问入口(ThreadLocalMap类是ThreadLocal类的静态内部类),每一个ThreadLocal对象都包含了一个独一无二的threadLocalHashCode值,使用这个值就可以在线程K-V值对中找回对应的本地线程变量。
注意:Java虚拟机可以支持方法级的同步和方法内部一段指令序列的同步,这两种同步结构都是使用 管程(Monitor)来支持的。
    同步一段指令序列通常是由Java语言中的synchronized语句块来表示的,java虚拟机的指令集中有monitorenter和monitorexit两条指令来支持synchronized关键字的语     义。


  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值