当多个线程访问一个对象时,若是不考虑这些线程在运行环境下的调度和交替执行,也不须要进行额外的同步,或者在调用地方进行额外的协调操做,调用这个对象的行为均可以得到正确的结果,那么这个对象是线程安全的。web
各类操做共享的数据分类5类:安全
互斥是实现同步的一种手段。须要阻塞和唤醒,使得性能下降,也称阻塞同步,是悲观锁。不管共享数据是否会发生竞争都须要加锁。服务器
最基本互斥手段是synchronized。会在同步块的先后造成monitorenter和monitorexit,在执行monitorenter时尝试去获取锁,若是已被本身持有或者未被持有则锁计数器加1,执行monitorexit就会减1,直到为0才释放锁。若是获取锁失败则会处于阻塞状态。多线程
除了synchronnized外还有JUC的重入锁(ReentrantLock),都具有线程重入特性,只是代码写法上有区别,一个为API层面的互斥锁(lock和unlock方法配合try/finally语句实现),一个是原生语法层面的互斥锁。RenetrantLock多了3个高级功能:并发
乐观锁。其并发策略时先进行操做,若是没有线程用共享数据,那操做就成功;若是有争用,那么产生冲突,就采起其它措施补偿(常见的补偿是不断重试直到成功为止)。所以就不要把线程挂起。app
乐观锁须要保证操做和冲突检测具备原子性,若是使用互斥同步来保证就失去意义了,所以须要硬件来完成这件事。如CAS指令。性能
CAS有三个操做数:内存地址A、旧的预期值O、新值N。当且仅当A符合旧预期值O时才进行更新V为新值N,并返回旧值O,若是不符合则不进行更新。该操做是一个原子操做。优化
保证线程安全不必定就须要同步,两者无因果关系。若是一个方法不涉及共享数据,那么就无须任何同步。例以下面两类代码是天生线程安全的:spa
锁优化技术:自适应自旋、锁消除、锁粗化、轻量级锁、偏向锁等、操作系统
互斥同步最大的性能消耗就是线程的挂起和恢复,都须要转入内核态完成。大部分共享数据的锁定状态只持续很短的时间,所以为了这段时间取挂起和恢复不值得,因此可让后面请求的锁“等待一下”,但不放弃处理器的执行时间,只需执行忙循环(自旋),这就是所谓的自旋锁。
自旋不能代替阻塞。若是自旋的时间开销超过了线程挂起和恢复的时间开销,那么就白白消耗处理器资源。默认自旋10次,可-XX:PreBlockSpin更改。JDK1.6后引入自旋锁,自旋时间不是固定的,而是由上一次在同一个锁上的自旋时间以及锁的拥有者状态来决定。若是上一次自旋成功且线程正在运行中,则会认为这次自旋可能成功,所以会让自旋持续更长时间,若是一个锁的自旋不多成功则之后获取可能直接省略自旋直接挂起。
锁消除是指在即时编译时,对一些代码上要求同步,可是被检测到不存在共数据竞争的锁进行消除。例如StringBuffer的append有同步块,其引用sb不会逃逸到concatString外,其余线程没法访问到sb来进行操做,所以锁能够被安全消除。sb放到实例变量去就得加锁处理。
public String concatString(String s1, String s2, String s3) { StringBuffer sb = new StringBuffer(); sb.append(s1); sb.append(s2); sb.append(s3); return sb.toString(); }
原则上是推荐将同步的做用范围变小,只在共享数据的实际做用域才发生同步。但若是在一系列操做对同一个对象反复加锁解锁,甚至加锁出如今for循环里,那么即便没竞争,频繁的互斥同步也会致使性能损耗。例如上面的append,虚拟机就会把加锁范围变成第一个append扩展到最后一个append。
轻量级锁是相对使用操做系统互斥量来实现的传统锁而言的,传统锁是重量级锁。轻量级锁是减小使用操做系统的互斥量产生的性能损耗。(使用自旋锁)
若是一开始线程获取锁时,对象头标志未锁定,则用CAS操做使对象头标志为轻量锁,则拥有了该对象的锁。若是更新失败,则判断对象的mark word是否指向当前线程的栈帧,是则代表已经拥有这个锁,直接进入同步代码块,不然说明该锁被其它线程占了。若是两条以上的线程争同一个锁,那么轻量级锁就变成重量级锁。
轻量级锁提高同步性能的依据是“大部分锁,在整个同步周期是不存在竞争的”,这是一个经验数据。没有竞争,则使用CAS操做就避免了使用互斥的开销。
锁会偏向于第一个获取它的线程,若是在接下来的执行过程当中,锁没被其它线程获取,则持有偏向锁的线程不须要再进行同步。当另一个线程尝试获取该锁时,则根据锁对象是否被锁定转换为轻量级锁或未锁定状态。