多线程技术点二

1.Java线程之间的通信由Java内存模型控制(JMM)。

JMM定义了线程和主内存之间的抽象关系:线程之间的共享变量存储在主内存中,每个线程都有一个私有的本地内存,本地内存中存储了该线程以读/写共享变量的副本。

2.先行发生原则(happens-before)

3.数据依赖性

如果两个操作访问同一个变量,且这两个操作中有一个为写操作,此时这两个操作之间就存在数据依赖性。

数据依赖分为3种类型:写后读,写后写,读后写

编译器和处理器在重排序时,会遵守数据依赖性,编译器和处理器不会改变存在数据依赖关系的两个操作的执行顺序。

4.as-if-serial语义

不管怎么重排序,(单线程)程序的执行结果不能被改变。

5.JMM和顺序一致性模型的差异

1)顺序一致性模型保证单线程内的操作会按照程序的顺序执行,而JMM模型不保证单线程内的操作会按照程序的顺序执行(临界区内的重排序)

2)顺序一致性模型保证所有线程只能看到一致的操作执行顺序,而JMM不保证所有线程能看到一致的操作执行顺序

3)JMM不保证对64位的long型和double型变量的写操作具有原子性

6.volatile关键字

可以把对volatile变量的单个读/写看成是使用同一个锁对这些单个读/写操作做了同步。

volatile变量自身具有下列特性

1)可见性:对一个volatile变量的读,总是能看到(任意线程)对这个volatile变量最后的写入

2)原子性:对任意单个volatile变量的读/写具有原子性,但类似于volatile++这种复合操作不具有原子性

7.volatile写-读的内存语义

1)当写一个volatile变量时,JMM会把该线程对应的本地内存中的共享变量值刷新到主内存

2)当读一个volatile变量时,JMM会把该线程对应的本地内存置为无效。线程接下来将从主内存中读取共享变量

8.重排序分为编译器重排序和处理器重排序,为了实现volatile内存语义,JMM会分别限制这两种类型重排序类型。为了实现volatile的内存语义,编译器在生成字节码时,会在指令序列中插入内存屏障来禁止特定类型的处理器重排序。

9.锁的释放和获取的内存语义

当线程释放锁时,JMM会把该线程对应的本地内存中的共享变量刷新到主内存中。

当线程获取锁时,JMM会把该线程对应的本地内存置为无效。从而使得被监视器保护的临界区代码必须从主内存中读取共享变量。

10.双重检查锁定的问题根源

instance = new Singleton()这行代码可以分解为如下3行伪代码

memory=allocate();//分配对象的内存空间

ctorInstance(memory);//初始化对象

instance=memory;//设置instance指向刚分配的内存地址

解决方案:将instance声明为volatile型,就可以实现线程安全的延迟初始化。

11.队列同步器

用来构建锁或者其他同步组件的基础框架,它使用了一个int成员变量表示同步状态,通过内置的FIFO队列来完成资源获取线程的排队工作。

同步器的主要使用方式是继承,子类通过继承同步器并实现它的抽象方法来管理同步状态,在抽象方法的实现过程中通过同步器提供的3个方法对同步状态进行修改(getState()、setState(int newState)和compareAndSetState(int expect, int update))。

子类推荐被定义为自定义同步组件的静态内部类。

12.同步器的设计是基于模板方法模式的,即使用者需要继承同步器并重写指定的方法,随后将同步器组合在自定义同组组件的实现中,并调用同步器提供的模板方法。

13.同步器可重写的方法:

protected boolean tryAcquire(int arg)——独占式获取同步状态,实现该方法需要查询当前状态并判断同步状态是否复合预期,然后再进行CAS设置同步状态。

protected boolean tryRelease(int arg)——独占式释放同步状态,等待获取同步状态的线程将有机会获取同步状态。

protected int tryAcquireShared(int arg)——共享式获取同步状态,返回大于等于0的值,表示获取成功,反之,获取失败。

protected boolean tryReleaseShared(int arg)——共享式释放同步状态

protected boolean isHeldExecusively()——当前同步器是否在独占模式下被线程占用,一般该方法表示是否被当前线程所独占。

14.队列同步器的实现分析

1)同步队列——

 同步队列中的节点(Node)用来保存获取同步状态失败的线程引用、等待状态以及前驱和后继节点。

同步器包含了两个节点类型的引用,一个指向头节点,而另一个指向尾节点。为了保证未能成功获取同步状态的线程加入同步队列过程的线程安全性,同步器提供了一个基于CAS的设置尾节点的方法:compareAndSetTail(Node expect, Node update),它需要传递当前线程“认为”的尾节点和当前节点,只有设置成功后,当前节点才正式与之前的尾节点建立关联。

节点进入同步队列之后,就进入了一个自旋的过程,每个节点都在自省地观察,当满足条件,获取到了同步状态,就可以从这个自旋过程中退出。

2)独占式同步状态获取与释放

在获取同步状态时,同步器维护一个同步队列,获取状态失败的线程都会被加入到队列中并在队列中进行自旋;移出队列(或停止自旋)的条件是前驱节点为头节点且成功获取了同步状态。在释放同步状态时,同步器调用tryRelease(int arg)方法释放同步状态,然后唤醒头节点的后继节点。

3)共享式同步状态获取与释放

共享式和独占式获取最主要的区别在于同一时刻能否有多个线程同时获取到同步状态。

原文地址:https://www.cnblogs.com/lvjygogo/p/8735171.html