多线程与并发-并发关键字(synchronized,volatile,final)

多线程与并发-并发关键字(synchronized,volatile,final)

synchronized

原理
synchronized在字节码中采用monitor机制实现,在修饰的方法中使用flag标记ACC_SYNCHRONIZED,在修饰的代码块时通过monitorenter和monitorexit环绕进行同步处理。
synchronized是可重入锁,在重入时monitor中计数+1,在释放时-1,而只有计数为0时其他被阻塞线程才能去竞争当前锁。
使用案例

public class MyService
{
 synchronized public static void serviceMethod1(){
 }
 public void serviceMethod2(){
  synchronized(MyService.class){
  }
 }
 synchronized public void serviceMethod3(){
 }
 public void serviceMethod4(){
  synchronized(this){
  }
 }
 public void serviceMethod5(){
  synchronized("123"){
  }
 }
}

以上代码中展现了synchronized的几种基本写法,一共展现了3把不同的锁。

  • serviceMethod1()与serviceMethod2()持有的锁为同一个,即MyService.java对应Class类的对象(在java中每个java对象对应的Class类的对象都是单例的),当前的两个方法在多个MyService的实例调用时是同步的,因为Class对象是唯一的。
  • serviceMethod3()与serviceMethod4()持有的锁为同一个,即MyService.java类的对象。同一个对象的操作是同步的,如果new创建了多个对象,多个对象调用自己的serviceMethod3()或serviceMethod4()方法时是异步的,因为创建多个对象即多把锁。
  • serviceMethod5()持有的锁为字符串123。

不同锁之间是异步的,只有持有同一把锁才会同步进行。
备注:

  • 当使用常量作为锁的时候建议使用枚举对象,字符串通过引用常量池对象完成字符串创建还是创建一个对象放在堆内需要注意,另外其他基本类型也有相似的缓存cache操作(-127~128)。
  • 同步方法、同步代码块之间是同步进行的,具有原子性。但方法或代码块的调用顺序是由虚拟机或操作系统决定的,可以认为是随机的。
  • 锁对象禁止修改(个人建议),锁对象改变将导致原有同步方法、代码块异步进行而变得不可控。当然如果只是对象属性改变并不会对同步产生影响。
  • 尽量使用同步代码块的方式进行同步,减小同步代码段的大小,减少同步操作额时间,从而提高效率。
  • 子类重写父类同步方法是需要加synchronized关键字,不然无法与父类方法进行同步操作

锁优化

适应性自旋

物理机有多个处理器,能让多个线程并行,看看前一个持有锁的线程是否会很快释放锁,通过自旋操作进行一定的等待但不放弃处理器时间。
自旋锁默认自旋10次,以防长时间自旋等待占用处理器资源。
自适应的自旋锁由前一次在同一个锁上的自旋时间和持有者状态来决定,动态调整自旋次数与是否需要进行自旋操作。

锁消除

代码运行时被检测到不可能存在共享数据竞争的锁进行消除。
同步操作可能由编译器添加
判断依据源于逃逸分析的数据支持

锁粗化

原则上我们要减小同步代码块的同步范围,以便可以尽快的释放/获取锁。
但是如果一系列操作对同一对象进行加锁和解锁,会导致不必要的性能损耗,虚拟机将会扩大锁的范围减少加锁和解锁的操作。

轻量级锁

目的:在没有多线程竞争的前提下减少传统重量级锁使用操作系统互斥量产生的性能消耗
依据:对于绝大部分锁,在整个同步周期内部是不存在竞争的
通过CAS操作减少同步操作的开销
这个于Mark Word有关,有待研究

偏向锁

在无竞争的情况下把整个同步消除掉,连CAS操作都不做。从而提高有同步但是无竞争的程序性能。
当有另一个线程尝试获取锁的时候,撤销偏向锁恢复到未锁定或轻量级锁的状态。
但是如果程序中大多数锁存在竞争可以使用参数来禁止偏向锁优化

volatile

特性:

  • 可见性:
    • 当前线程可以马上看到其他线程更改后的值
    • 未使用volatile修饰的变量在线程私有堆栈中的值可能与公共堆栈中不一致
    • volatile修饰的变量,每次获取时强制从公共内存中获取放入工作内存,写入时将工作内存强制写入公共内存
  • 原子性:32位系统long,double写入是非原子性的,这个跟操作系统有关
  • 非原子性:volatile最大的缺陷,i++;分为三步,取值,相加计算,放回结果。实现需要进行同步操作或采用原子类实现。
  • 禁止代码重排:虚拟机在优化是可能会改变代码的执行顺序,变量使用volatile修饰后,该变量的操作将变成一堵墙,优化后代码中前后的操作不会跨越该操作。

final

写final域重排序规则
禁止对final域的写,重排序到构造函数之外:

  • JMM禁止编译器把final域的写,重排序到构造函数之外。
  • 编译器会在final域写之后,构造函数return之前,插入一个storestore内存屏障。这个屏障可以禁止处理器把final域的写,重排序到构造函数之外

读final域重排序规则
在一个线程中,初次读对象的引用,和初次读该对象包含的final域,JMM会禁止这两个操作的重排序。(注意,这个规则仅仅是针对处理器),处理器会在读final域操作的前面插入一个LoadLoad屏障
引用数据类型,额外增加约束:禁止在构造函数对一个final修饰的对象的成员域的写入与随后将这个被构造的对象的引用赋值给引用变量 重排序

final这一块以后深入了解并发后在来重写吧

你可能感兴趣的:(多线程与并发)