Android

多线程(二)-线程同步

2019-08-25  本文已影响0人  Stan_Z

一、概念

二、JMM与happens-before规则

2.1 JMM抽象结构模型:

我们知道CPU执行指令的速度是远远快于内存读写速度的,如果任何时候对数据的操作都要通过和内存的交互来进行,会大大降低指令执行的速度。因此在CPU里面就有了高速缓存。因此,线程在执行的时候,不会直接读取主内存,而是会在每个CPU核的高速缓存里面读取数据,每次CPU在执行线程的时候,会将需要的数据从主内存读取到高速缓存中。JMM就从抽象层次定义了这种方式,并且JMM决定了一个线程对共享变量的写入何时对其他线程是可见的。

JMM抽象结构模型

因为jvm会对代码进行编译优化,指令会出现重排序的情况,为了避免编译优化对并发编程安全性的影响,需要happens-before规则定义一些禁止编译优化的场景,保证并发编程的正确性。

2.2 happens-before6条规则如下:

三、三大性质:

通过三大性质来保证线程安全。

四、同步方案

4.1 volatile

volatile是Java关键字,仅保证可见性、有序性,不保证原子性。

public class Singleton {  
      private volatile static Singleton singleton;  
      private Singleton (){
      }   
      public static Singleton getInstance() {  
      if (singleton== null) {  
          //首先通过synchronized来保证同一时刻只有一个线程能操作
          synchronized (Singleton.class) {  
          //拿到锁之后还需要再判一次空,可能之前持锁线程已经创建了实例了
          if (singleton== null) {  
             //singleton是引用类型,不具备原子性,因此可能发生指令重排。
             //正常情况:实例化对象的流程:
             //1.在堆上开辟空间
             //2.属性初始化
             //3.将栈上空间指向堆。
             //正常情况是1->2->3 ,重排可能为1->3->2
             //由于3步骤先执行,singleton已经不为空了,这时候其他进程访问getInstance直接return 对象,但是当前初始化并没有完成,出现问题。因此可以使用volatile来保证有序性,防止指令重排。
              singleton= new Singleton();  
          }  
         }  
     }  
     return singleton;  
     }  
}
4.1 JUC类库
4.1.1 java.util.concurrent.atomic 下的AtomicInteger、AtomicBoolean、AtomicLong等。保证类的原子性

底层原理:
CAS操作(又称为无锁操作)是一种保证原子性的机制,它不是通过加锁阻塞其他线程操作,而是通过比较交换来鉴别线程是否出现冲突,出现冲突就重复当前操作直到没有冲突为止。有竞争就会自旋。而CAS修改值时的原子性问题通过汇编lock cmpxchg 锁总线来保证,即从硬件层面保证原子性。

交换过程:
三个值:内存地址存放的实际值 、预期旧值 、更新的新值
如果实际值=旧值,证明没有更新过,直接将新值赋给实际值。
如果实际值≠旧值,证明被修改,不能将新值赋给实际值,直接返回之前的实际值。

当多个线程使用CAS操作一个变量是,只有一个线程会成功,并成功更新,其余会失败。失败的线程会重新尝试,当然也可以选择挂起线程。CAS的实现需要硬件指令集的支撑,在JDK1.5后虚拟机才可以使用处理器提供的CMPXCHG指令实现。

CAS的问题:

4.1.2 java.util.concurrent.locks.ReentrantLock

底层原理:
AQS: 在cas基础上包了一个fifo的双向链表来保存尝试获取锁的线程。
公平:进来的线程都加入队尾,串行获取锁。
非公平:刚进来的线程有机会插队获取锁,一旦获取不到则还是加入队尾。
通过state来表示:没有线程持锁、有线程持锁、重入多少次。
另外RentrantLock可中断,能避免死锁。

4.2 synchronized

synchronized锁升级
锁升级过程:偏向锁->自旋锁->重量级锁

synchronized几种锁应用的区别
对象锁:作用于对象实例。多个线程调用同一个对象的同步方法会阻塞,调用不同对象的同步方法不会阻塞。

锁方法:
public synchronized void func(){}
锁代码块:
public void func(){
  synchronized(object){
    ...
   }
}

类锁:作用于类。一个class不论被实例化多少次,其中的静态方法和静态变量在内存中都只有一份拷贝,因此多线程调用此类同步方法均阻塞。

锁方法:
public static synchronized void func(){}
锁代码块:
public void func(){
  synchronized(XXX.class){
    ...
   }
}

synchronized 与ReentrantLock区别
支持的功能:

加锁方式 可重入 公平非公平 可中断 互斥
synchronized 非公平
ReenTrantLock 可以在构造函数指定,默认非公平

注:

区别总结

参考:
https://www.jianshu.com/p/d53bf830fa09
https://www.jianshu.com/p/d52fea0d6ba5
https://www.zhihu.com/question/41016480?sort=created

上一篇 下一篇

猜你喜欢

热点阅读