多线程同步控制

volatile

内存可见性

当变量被volatile关键字修饰后,CPU将越过寄存器向主内存中直接请求。可理解为使用同一监视器对单个读写操作同步

在这里我们可以说基于volatile读写在多线程中是安全的,当时基于volatile的运算是不安全的。
原因在于JMM允许多个线程同时计算volatile变量,但运算操作却不是原子的

底层实现

如何volatile变量对不同线程的可见性

  1. 缓存一致性协议
    在CPU写入数据时,如操作的是volatile变量且其他CPU中存在该变量的副本,则发出通知告知其他CPU将该变量缓存行设置为无效。当其他CPU读取时向主内存重新读取
  2. 嗅探
    每个处理器嗅探总线上数据,检查缓存是否过期,当发现缓存行对应的内存地址被修改,置缓存行无效

总线风暴:当大量使用volatile时,将导致大量主内存嗅探及无效CAS,从而使总线带宽达到峰值

保证有序性

在单线程下,由于as-if-serial语义的存在,无论对指令集如何排序,其执行结果不变,具体表现为:不对 存在数据依赖关系的操作重排
但多线程切换的随意性,使得我们从外部看线程实际上无序的,这样的弱有序性不能满足并发要求

volatile通过防止指令重排来保证有序性,底层使用内存屏障,部分指令先行而部分指令后行

是否重排 second operate
first operate 普通读写 volatile读 volatile写
普通读写 NO
volatile读 NO NO NO
volatile写 NO NO

可见

  1. 当第二个操作为volatile写时,无论第一个操作时什么都不能重排
    确保volatile写之后的操作不会被编译器重排到volatile写之前
  2. 当第一个操作为volatile读时,无论第二个操作是什么都不能重排
    确保volatile读之后的操作不会被编译器重排到volatile读之后
  3. 对于volatile变量,write happens-before read

保证64位数据的读写原子性

synchronized

synchronized关键字使代码同步于某个对象上。所有同步在一个对象的同步块只能被一个线程进入并执行,其他阻塞。以下为sync的四种常用形式
(如果不理解什么是监视器可以跳到sync机制实现的说明)

  • 实例方法同步

    public synchronized void func(){
        System.out.println("sync in instance method");
    } 
    

    同步在 拥有该方法的对象上 ,也就是this指针上。所以只允许一个线程执行一个实例方法

  • 静态方法同步

    public static synchronized void func(){
        System.out.println("sync in instance static method");
    }
    

    同步在该方法所在的 类对象 ,即XX.class。允许有多个线程同时操作该类不同实例,但仅允许一个线程执行该类的静态方法

  • 实例方法中同步代码块

    Object monitor = new Object();
    public void func(){
        synchronized (monitor){
            System.out.println("sync in instance method code");
        }
    }
    

    同步在 监视器 对象上,就是对sync关键字传入的对象。仅允许获得监视器对象的线程执行,每个监视器仅容许一个线程获取
    当采用this作为监视器,近似等效于sync修饰方法名

    public void func(){
        synchronized (this){
            System.out.println("sync in instance method code");
        }
    }
    
  • 静态方法中同步块

    static Object monitor = new Object();
    public static void func(){
        synchronized (monitor){
            System.out.println("sync in instance method code");
        }
    }
    

    仍然同步在 监视器 对象上,但是由于静态对象初始化时间的特殊性,要注意监视器对象的选取。
    当采用类对象作为监视器时,近似等效于sync修饰方法名 前提是你把代码块都包起来

    public static void func(){
        synchronized (T1.class){
            System.out.println("sync in instance method code");
        }
    }
    

注意:

  1. 类对象与锁对象并不冲突。根本在于这是两个不同的对象,也就是两个不同的监视器,我们可以在执行同步实例方法的同时,执行同步静态方法
  2. sync修饰方法时,在.class文件中设置方法的ACC_SYNCHRONIZED访问标识;对于代码块的同步则是依赖于monitorenter/monitorexit指令,当执行到monitorenter指令时尝试获取监视器所有权

sync同步机制实现

sync同步借助 monitor机制以及wait/notify共同实现
考虑如下场景:现在有台电脑在一个房间中,一群人排队上网,但是这个房间只允许一个人进入,为了防止别人进去,先进去的从里面把门锁上,这样外面的人想进去的时候发现门打不开了,便只能在外面等,时不时来试试这门能不能开。但是时间一长总不能干等吧,于是有的人就开始征战王者峡谷了,不管这门能不能进了。当里面的人玩完了,可能出来的时候吼一嗓子告诉所有人我下机了,然后所有人就去抢房间,谁先抢到是谁的,抢不到老倒霉蛋了;也有可能他出来看见打王者的太可怜,随便挑了一个和他说:无内鬼,门没锁,然后这货就去了。
现在把人换成线程,房间变为共享内存,就是整个sync的同步机制了。

什么是monitor

在上述场景中,monitor就是人进去反手锁上的那扇门。在Java中通过对象头信息实现。
当线程想要获取锁时,会在对象头里的Mark Word里查找锁状态,若无锁则CAS添加自己的信息给锁;如果锁对象已被其他线程占用,那就只能一边凉快去了。
关于对象头更详细的说明,如为什么在sync中调用hashCode()会导致直接生成轻量级锁:null

锁升级

在第一次进入同步之后,首先生成偏向锁,CAS修改对象头里的锁标志位。偏向锁偏向于第一个获得它的线程A,执行完毕之后不主动释放,monitor上持有锁的线程不改变仍为A。这样如果线程A继续对该同步块进行操作,则无需获取monitor。
如果有其他线程B也需要对该同步块进行操作,出现锁竞争时,锁升级为轻量级锁别看说的花里胡哨,实际就一自旋锁 此时两线程循环重试执行条件。当自旋次数达到最大次数后,继续自旋影响程序性能,于是再次升级锁为重量级锁
此时调用wait方法将超出最大值的自旋线程挂起,放弃对CPU使用权的竞争,减少性能损耗。直到待获取锁对象被释放,调用notify唤醒线程,重新竞争锁

重入性

允许同一个线程多次获取同一把锁

final

推荐阅读:你以为你真的了解final吗?

final域写重排

JMM禁止final域写重排到构造器之外。
保证在对象引用为任意线程可见前被正确初始化(无论哪个线程在何时,该对象引用都连到了一个初始完毕的内存空间) 如果构造对象不从构造器逸出
实现:编译器在final写之后插入storestoer屏障

final读重排

在初次读对象引用和该对象包含的finial域,JMM禁止重排
读对象final域前,一定先读包含final域对象引用
实现:在读final前加入loadload屏障

final域为引用对象

对final修饰对象成员写入 happens-before 构造对象引用的赋予。
意思就是最后交给你一个从引用连到内存的整体,而不是缺斤少两的奇葩。。

Lock接口

不同于volatile和sync这样的关键字,Java提供了可自定的同步控制组件:Lock接口

Lock lock = new ReentrantLock();
lock.lock();
try{
	System.out.println("Using lock!");
}finally{
	lock.unlock();
}

需要注意的是synchronized同步块执行完成或者遇到异常是锁会自动释放,而lock必须调用unlock()方法释放锁,因此在finally块中释放锁。
使用lock的优点是可以自定义lock。我们可以继承AQS并定义若干同步状态的获取和释放,从而实现自己的lock实现。


文章参考:

1.通俗易懂 悲观锁、乐观锁、可重入锁、自旋锁、偏向锁、轻量/重量级锁、读写锁、各种锁及其Java实现! - Pickle Pee的文章 - 知乎
2. CL0610 /Java-concurrency

猜你喜欢

转载自blog.csdn.net/white_156/article/details/107724357