- volatile
volatile是轻量级的synchronized,它比synchronized的使用和成本更低, 它不会引起线程上下文的切换和调度。
volatile的定义:java编程语言允许线程访问共享变量,为了确保共享变量能被准确和一致地更新,线程应该确保通过排它锁单独获得这个变量。 有的时候比锁还更加的方便,如果一个字段被声明成volatile,java线程内存模型确保所有线程都会看到这个变量的值是一致的。 两个特性: 一致性和可见性
有volatile变量修饰的共享变量进行写操作的时候会多出第二行汇编代码:
i.将当前处理器缓存行的数据写回到系统内存。
ii. 这个写回的操作会导致在其他cpu里面缓存了该内存地址数据无效。
为了保证各个处理器缓存是一致的,就会实现缓存一致性协议,每个处理器通过嗅探在总线上传播的数据来检查自己的值是不是已经过期了。
在JDK7中,对volatile的使用进行了优化,在并发包里面新增了一个队列集合类LinkTransferQueue,它在使用volatile变量时,用一种追加字节的方法来优化队列出队和入队的性能。 - synchronized
synchronized一直被大家称之为重量级锁,可是在jdk1.6之后就对其进行相应的优化偏向锁,轻量级锁。
synchronized的实现同步的三种方式:
1.对于普通同步方法,锁是当前实例对象。
2.对于静态同步方法,锁是当前类的class对象。
3.对于同步代码块,锁是synchronized括号里面配置的对象。
synchronized用的锁是保存在java的对象头里面。
synchronized的锁升级
锁一共有四种形态: 无状态锁,偏向锁,轻量级锁,重量级锁。这几个状态会随着竞争情况逐渐升级。锁可以升级但不能降级,意味着偏向锁升级后,不能再降回偏向锁。不能降级的策略,是为了提高获得锁和释放锁的效率。
i. 偏向锁
当一个线程访问同步代码块并获取锁时,会在对象头和栈帧中的锁记录里面存储偏向锁的线程ID,以后线程进入或退出同步代码块的时候就不需要进行CAS操作来加锁和解锁的过程了,只需要简单的在Mark Word里是否存储这只向当前线程的偏向锁。如果测试成功的话,就证明改线程已经获得锁,如果失败,则需要在测试Mark Word 中偏向锁的标示是否设置成1(表示当前是偏向锁):如果没有设置,则使用CAS竞争锁,如果设置了,则尝试使用CAS将对象头的偏向锁指向改线程。
i.i 偏向锁的撤销
偏向锁使用了一种等到竞争出现才释放锁的机制,所以其他线程尝试竞争偏向锁时,持有偏向锁的线程才会释放锁。偏向锁的撤销,需要等到全局安全点(在这个时间点上面没有正在执行的字节码)。
在java6 和java7里面是默认启动偏向锁的
ii 轻量级锁
线程在执行同步代码快之前,JVM会先在当前线程的栈帧中创建用于存储锁记录的空间,并将对象头中的Mark Word 复制到锁记录中,官方称为DisplacedMarkWord ,然后线程尝试将使用CAS将对象头中的Mark Word 替换为指向锁记录的指针,如果成功,当前线程获得锁,如果失败,表示其他线程竞
争锁,当前线程便尝试使用自旋来 获取锁。
自旋锁:所谓自旋就是说,线程频繁的从阻塞到唤醒这段时间,可能会非常耗时,那么自旋锁就会让线程等待一段时间,执行一段无意义的代码(通常是无意义的循环)
ii.i 轻量级锁解锁
轻量级解锁时,会使用原子的CAS操作将Displace Mark Word 替换回到对象头,如果成功表示没有发生竞争关系,如果失败,表示当前 锁存在竞争关系,锁就会膨胀为重量级锁。
锁 | 优点 | 缺点 | 使用场景 |
---|
偏向锁 | 加锁和解锁不需要额外的消耗,和执行非同步方法相比仅存在纳秒级的差距 | 如果线程间存在锁竞争,会带来额外的锁撤销的消耗 | 适用于只有一个线程 |
轻量级锁 | 竞争的线程不会阻塞,提高了程序的响应速度 | 如果始终得不到锁竞争的线程,使用自旋会消耗CPU | 追求响应时间 同步块执行速度非常快 |
重量级锁 | 线程竞争不使用自旋,不会消耗CPU | 线程阻塞,响应时间缓慢 | 追求吞吐量 同步块执行速度较长 |
-----------------------摘自java并发编程的艺术