多线程相关问题(二)

2019-02-10  本文已影响0人  NealLemon

JMM(Java 内存模型)

定义

Java内存模型(即Java Memory Model,简称JMM)本身是一种抽象的概念,并不真实存在,它描述的是一组规则或规范,通过这组规范定义了程序中各个变量(包括实例字段,静态字段和构成数组对象的元素)的访问方式。

模型详解

jmm内存模型.png

​ Java内存模型中规定所有变量都存储在主内存,主内存是共享内存区域,所有线程都可以访问,但线程对变量的操作(读取赋值等)必须在工作内存中进行,首先要将变量从主内存拷贝的自己的工作内存空间,然后对变量进行操作,操作完成后再将变量写回主内存,不能直接操作主内存中的变量,工作内存中存储着主内存中的变量副本拷贝工作内存是每个线程的私有数据区域,因此不同的线程间无法访问对方的工作内存,线程间的通信(传值)必须通过主内存来完成。

主内存与工作内存的数据存储类型以及操作方式

JMM(Java 内存模型)如何解决可见性问题

jmm内存与硬件.png

如图所示,我们在对主内存的变量进行操作时,可能其中一个线程的操作在CPU缓存区执行的,另一个则是在主内存中执行的,因此也会导致数据不一致问题,那么JMM如何解决可见性问题的?

1.重排序

在程序处理的时候,为了提高性能,处理器和编译器常常会对指令重排序,那么我们就必须满足以下条件才可以进行重排序

happens-before 原则是判断数据是否存在竞争、线程是否安全的依据

如果两个操作不满足上述任意一个原则,操作就没有顺序保障,则JVM可以对这两个操作进行重排序操作。如果A happens-before B,则A的操作B是可见的。

2.volatile

volatile是Java虚拟机提供的轻量级的同步机制。

JMM如何保证volatile可见?

JMM如何保证禁止指令重排优化?

通过插入内存屏障指令禁止在内存屏障前后的指令执行重排序优化

内存屏障

内存屏障,又称内存栅栏,是一个CPU指令,它的作用有两个。

3.synchronized关键字

保证对于方法级别或者代码块级别的原子性操作。具体原理可以看上一章的内容 synchronized相关问题

4.synchronized和volatile的区别

既然这两个关键字都提及到了,我们来对比一下他们之间的区别。

对比内容 synchronized volatile
本质 锁定当前变量,只有当线程可以访问,其他线程被阻塞住,直到该线程完成变量的操作为止 告诉JVM当前变量在寄存器(工作内存)中的值是不确定的,需要从主内存读取
级别 变量,方法,类 变量
特性 既能保证变量的修改可见性,也能保证原子性 仅能实现变量的修改可见性,不能保证原子性
阻塞状态 阻塞 不会阻塞
编译器优化策略 可以被优化 不能被优化

CAS(compare and swap)

首先我们需要了解悲观锁与乐观锁

CAS一种高效实现线程安全性的方法

CAS算法思想

包含三个操作数

执行CAS操作时,先将V与A进行比较,如果相等,则将V更新为B ,否则不做任何操作。

这里可以看JDK中的Unsafe.java实现。

public final native boolean compareAndSwapObject(Object var1, long var2, Object var4, Object var5);

public final native boolean compareAndSwapInt(Object var1, long var2, int var4, int var5);

public final native boolean compareAndSwapLong(Object var1, long var2, long var4, long var6);

这里的调用了native方法。调用的是不同系统对应的指令方法。

JDK给我们提供了java.util.concurrent.atomic 包。如果我们要做原子操作,我们可以熟悉一下这个包中的各种实现。

CAS ---ABA问题

如果有三个线程同时对一个变量进行操作。

假如线程1,2执行在3之前,线程3无法确定A是否被改变过。这个就是CAS比较突出的ABA问题。

java线程池

线程池的五种状态

状态转换图

状态转换图.png

Executors五种创建配置

5种线程池.png

ThreadPoolExecutor

ThreadPoolExecutor构造函数以及参数详解

public ThreadPoolExecutor(int corePoolSize,  //核心线程数
                          int maximumPoolSize, //最大线程数
                          long keepAliveTime,  //当线程数大于核心时,这是多余空闲线程在终止之前等待新任务的最长时间
                          TimeUnit unit,  //等待时间的单位
                          BlockingQueue<Runnable> workQueue  //任务等待队列
                          ) {
    this(corePoolSize, maximumPoolSize, keepAliveTime, unit, workQueue,
         Executors.defaultThreadFactory(),   //在执行程序创建新线程时使用的工厂
         defaultHandler //因为已达到线程边界和队列容量,执行被阻止时使用的处理程序。也就是我们的饱和策略
        );
}

任务提交后的执行流程

threadpool流程图.png

ThreadPoolExecutor设计与实现

threadpool工作与实现.png

1.当用户执行execute()或sumbit()方法时,将提交的线程保存到工作队列中也就是图中的WorkQueue。

WorkQueue包括

2.提交内部的工作线程池,将每个线程抽象为worker对象。调用java.util.concurrent.ThreadPoolExecutor#runWorker来执行任务。

3.对于内部线程池的管理,每当线程池需要创建一个线程时,都是通过线程工厂方法来完成的。当线程池工作量较大的时候,会创建线程,当线程池闲置线程较多时,会按配置销毁线程。
线程工厂

4.如果执行结束,则正确返回。

5.如果线程池处于shutdoun的状态,需要特定的策略或自己实现RejectExecutionHandler。

四种策略

线程池的大小设定

基于前人的实战经验推导而出

上一篇 下一篇

猜你喜欢

热点阅读