一 线程安全定义
二 Java中的线程安全当多个线程访问一个对象时,如果不用考虑这些线程在运行时环境下的调度和交替执行,也不需要进行额外的同步,或者在调用方进行任何其他的协调操作,调用这个对象
的行为都可以获得正确的结果,那这个对象是线程安全的 。——Brian Goetz ;
按照线程安全的“安全程度”由强至弱来排序,我们可以将Java语言中各种操作共享的数据分为5类:不可变、 绝对线程安全、 相对线程安全、 线程兼容和线程对立。
2.1 不可变
2.2 绝对线程安全在Java语言中,不可变的对象一定是线程安全的 !如果共享数据是一个基本数据类型,那么只要在定义时使用final关键字修饰它就可以保证它是不可变的。 如果共享数据是一个对象,那就需要保证对象的行为不会对其状态产生任何影响才行 ,比如String。保证对象行为不影响自己状态的途径有很多种,其中最简单的就是把对象中带有状态的变量都声明为final,这样在构造函数结束之后,它就是不可变的。
例如Integer类源代码:
在 Java API 中符合不可变要求的类型,除了上面提到的 String 之外,常用的还有枚举类型,以及 java.lang.Number 的部分子类,如 Long 和 Double 等数值包装类型, BigInteger 和 BigDecimal 等大数据类型 。private final int value; public Integer(int value) { this.value = value; }
绝对的线程安全完全满足Brian Goetz给出的线程安全的定义(见线程安全定义),这个定义很严格,极难满足。
2.3 相对线程安全
相对的线程安全就是我们通常意义上所讲的线程安全,它需要保证对这个对象单独的操作是线程安全的,我们在调用的时候不需要做额外的保障措施,但是对于一些特定顺序的连续调用,就可能需要在调用端使用额外的同步手段来保证调用的正确性。
2.4 线程兼容
对象本身并不是线程安全的,但是可以通过在调用端正确地使用同步手段来保证对象在并发环境中可以安全地使用,我们平常说一个类不是线程安全的,绝大多数时候指的是这一种情况。
2.5 线程对立
三 线程安全的实现方法无论调用端是否采取了同步措施,都无法在多线程环境中并发使用的代码。 由于Java语言天生就具备多线程特性,线程对立这种排斥多线程的代码是很少出现的,而且通常都是有害的,应当尽量避免。
3.1 互斥同步
3.1.1 概念
3.1.2 synchronized 关键字同步是指在多个线程并发访问共享数据时,保证共享数据在同一个时刻只被一个(或者是一些,使用信号量的时候)线程使用。 而互斥是实现同步的一种手段,临界区(CriticalSection)、 互斥量(Mutex)和信号量(Semaphore)都是主要的互斥实现方式。
见博客:http://blog.youkuaiyun.com/xiaowang627/article/details/60604787
3.1.3 ReentrantLock
使用java.util.concurrent包中的重入锁(ReentrantLock)也可以实现同步 ,它表现为API层面的互斥锁((lock()和unlock()方法配合try/finally语句块来完成),ReentrantLock增加了一些高级功能,主要有以下3项:
等待可中断:当持有锁的线程长期不释放锁的时候,正在等待的线程可以选择放弃等待 。
可实现公平锁:按照申请锁的时间顺序来依次获得锁 。
锁可以绑定多个条件:可以同时绑定多个Condition对象 。
3.1.4 对比和选择
多线程环境下synchronized的吞吐量下降得非常严重,而ReentrantLock则能基本保持在同一个比较稳定的水平上。JDK 1.6发布之后synchronized与ReentrantLock的性能基本上是完全持平了。 建议在synchronized能实现需求的情况下,优先考虑使用synchronized来进行同步。
3.2 非阻塞同步
基于冲突检测的乐观并发策略,通俗地说,就是先进行操作,如果没有其他线程争用共享数据,那操作就成功了;如果共享数据有争用,产生了冲突,那就再采取其他的补偿措施(最常见的补偿措施就是不断地重试,直到成功为止),这种乐观的并发策略的许多实现都不需要把线程挂起。
3.3 无同步方案
3.3.1 可重入代码
可以在代码执行的任何时刻中断它,转而去执行另外一段代码,而在控制权返回后,原来的程序不会出现任何错误。 所有的可重入的代码都是线程安全的,但是并非所有的线程安全的代码都是可重入的。
3.3.2 线程本地存储
最重要的一个应用实例就是经典Web交互模型中的“一个请求对应一个服务器线程”的处理方式,这种处理方式的广泛应用使得很多Web服务端应用都可以使用线程本地存储来解决线程安全问题。
四 锁优化
4.1 自旋锁与自适应自旋
4.2 锁消除线程请求不到锁的时候不放弃处理器的执行时间,而是执行一个忙循环(自旋),看看持有锁的线程是否很快就会释放锁 ,这项技术就是所谓的自旋锁。自旋时间可以更改;JDK 1.6中引入了自适应的自旋锁 自适应意味着自旋的时间不再固定了,而是由前一次在同一个锁上的自旋时间及锁的拥有者的状态来决定。
虚拟机即时编译器在运行时,对一些代码上要求同步,但是被检测到不可能存在共享数据竞争的锁进行消除。
4.3 锁粗化
如果一系列的连续操作都对同一个对象反复加锁和解锁,甚至加锁操作是出现在循环体中的,那即使没有线程竞争,频繁地进行互斥同步操作也会导致不必要的性能损耗。此时锁粗化可以减少加锁次数。
4.4 轻量级锁
在无竞争的情况下使用CAS操作去消除同步使用的互斥量 。
4.5 偏向锁
在无竞争的情况下把整个同步都消除掉,连CAS操作都不做了 。