Java 多线程与并发【原理第二部分笔记】

什么是Java内存模型中的happens-before

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

JMM中的主内存

主内存主要存储的是Java的实例对象,其中还包括了类里面的成员变量,类信息,常量以及静态变量等等,其属于静态数据区,多线程并发操作时会引发线程安全的问题

JMM中的工作内存

其主要存储当前方法的所有的本地变量信息,本地变量信息对其他线程不可见,且还有字节码行号指示器以及native方法信息,其属于线程私有数据区域,不存在线程安全问题

JMM与Java内存区域划分是不同的概念层次

JMM描述的是一组规则,围绕着原子性,有序性以及可见性展开,其和Java内存区域的相似之处就是都存在共享区域和私有区域

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

根据虚拟机规范,对于一个实例对象中的成员方法而言,如果方法变量中包含有基本数据类型本地变量的,这些本地变量将直接存储在工作内存的栈帧结构中,但是如果本地变量是引用类型的,那么引用将存储在工作内存中,实例存储在主内存中

但是对于成员变量,static变量以及类信息都会被存储在主内存中,需要注意的是,在主内存中的对象可以被多线程共享,主内存共享的方式就是线程各拷贝一份数据到工作内存,操作完成后刷新回主内存

JMM解决可见性问题

指令重排序的需要的条件

首先是,在单线程环境下不能改变程序运行的结果,其次就是存在数据依赖的不允许进行重排序,其实这两个可以合成一个,即无法通过happens-before原则推出来的,才能进行指令的重排序

happens-before关系以及概念

当a操作的结果需要对b操作可见的时候,我们就说,a和b存在happens-before的关系

如果两个操作不满足任意一个happens-before规则,那么这两个操作就没有顺序的保障,JVM可以对这两个操作进行重排序,如果操作A happens-before操作B,那么操作A在内存上所做的操作对操作B都是可见的,这就是happens-before的概念

happens-before的八大原则

1.程序次序规则︰一个线程内,按照代码顺序,书写在前面的操作先行发生于书写在后面的操作

2锁定规则︰一个unLock操作先行发生于后面对同一个锁的lock操作

3. volatile变量规则︰对一个变量的写操作先行发生于后面对这个变量的读操作

4.传递规则:如果操作A先行发生于操作B,而操作B又先行发生于操作C,则可以得出操作A先行发生于操作C

5.线程启动规则:Thread对象的start()方法先行发生于此线程的每一个动作

6.线程中断规则:对线程interrupt()方法的调用先行发生于被中断线程的代码检测到中断事件的发生

7.线程终结规则:线程中所有的操作都先行发生于线程的终止检测,我们可以通过Thread.join()方法结束,Thread.isAlive()的返回值手段检测到线程已经终止执行

8.对象终结规则︰一个对象的初始化完成先行发生于他的finalize()方法的开始

volatile

在并发中很常见,其是JVM提供的轻量级同步机制,其作用有两个:一是可以保证被volatile修饰的共享变量对所有线程总是可见的,二是禁止指令重排序优化

volatile的可见性

我们需要意识到被volatile修饰的变量对所有的线程总是立即可见的,对volatile变量的所有的写操作都可以立即反应到其他线程中,但是对于volatile的运算操作在多线程的环境中并不一定是安全的

volatile变量为何立即可见?

当写一个volatile变量时,JMM会把该线程对应的工作内存中的共享变量值刷新到主内存中,而在读取一个volatile变量时,JMM会把该线程对应的工作内存置为无效

volatile如何禁止重排优化?

需要先知道内存屏障(memory barrier)这个概念,内存屏障保证了特定操作的执行顺序,并且还保证了某些变量的内存可见性

通过插入内存屏障指令禁止在内存屏障前后的指令执行重排序优化,也就是说,强制刷出各种CPU的缓存数据,因此任何CPU上的线程都能读取到这些数据的最新版本

volatile和synchronized的区别

  1. volatile本质是在告诉JVM当前变量在寄存器(工作内存)中的值是不确定的,需要从主存中读取,synchronized则是锁定当前变量,只有当前线程可以访问该变量,其他线程被阻塞住直到该线程完成变量操作为止

  2. volatile仅能使用在变量级别,synchronized则可以使用在变量、方法和类级别

  3. volatile仅能实现变量的修改可见性,不能保证原子性,而synchronized则可以保证变量修改的可见性和原子性

  4. volatile不会造成线程的阻塞,synchronized可能会造成线程的阻塞

  5. volatile标记的变量不会被编译器优化,synchronized标记的变量可以被编译器优化

CAS

CAS是一种高效实现线程安全性的方法,它支持原子更新操作,适用于计数器,序列发生器等场景,属于乐观锁机制,号称lock-free

CAS操作失败时由开发者决定是否要继续尝试,还是说执行别的操作先

CAS在多数情况下对于开发者来说是透明的,J.U.C的atomic包提供了常用的原子性数据类型以及引用,数组等相关原子类型和更新操作工具,是很多线程安全程序的首选,像UNsafe类,虽然提供了CAS服务,但是因为能够操纵任意内存地址读写而由隐患

在Java9以后,可以使用variable handle api来代替Unsafe

CAS思想

其包含三个操作数——内存位置(V),预期原值(A)和新值(B),执行的时候,将内存位置的值与预期原值的值进行比较,如果相匹配,那么处理器就会将该位置的值更新为新值,否则处理器不做任何操作

CAS缺点

1.如果循环时间长的话,开销就会很大

2.只能保证一个共享变量的原子操作

3.有ABA问题,解决问题的方法是atomicstampedreference

Java线程池

开发者一般会利用executors创建不同的线程池满足不同场景的需求,目前有五种不同的线程池创建配置

1. newFixedThreadPool(int nThreads),指定工作线程数量的线程池

2. newCachedThreadPool(),处理大量短时间工作任务的线程池

(1)试图缓存线程并重用,当无缓存线程可用时,就会创建新的工作线程

(2)如果线程闲置的时间超过阈值,则会被终止并移出缓存﹔

(3)系统长时间闲置的时候,不会消耗什么资源

3. newSingleThreadExecutor(),创建唯一的工作者线程来执行任务,如果线程异常结束,会有另一个线程取代它

4.newSingleThreadScheduledExecutor()与newScheduledThreadPool(int corePoolSize),定时或者周期性的工作调度,两者的区别在于单一工作线程还是多个线程

5. newWorkStealingPool(),内部会构建ForkJoinPool,利用working-stealing算法,并行地处理任务,不保证处理顺序

Fork/join框架

是一个把大任务分割成若干的小任务并行执行,最终汇总每一个小任务结果后得到大任务结果的框架,可以使用work-stealing算法(某个线程从其他队列里窃取任务来执行)来调整

为什么要使用线程池?

1.降低资源消耗

2.提高线程的可管理性

Executor的框架

J.U.C的三个Executor接口

1.Executor:运行新任务的简单接口,将任务提交和任务执行细节解耦

2.executorservice:具备管理执行器和任务生命周期的方法,提交任务机制更加的完善

3.scheduledexecutorservice:支持future和定期执行任务

threadpoolexecutor

这是最基础的

threadpoolexecutor的构造函数

1.corepoolsize:核心线程数量

2.maximumpoolsize:线程不够用的时候能够创建的最大线程数

3.workqueue:任务等待队列

4.keepalivetime:抢占的顺序不一定,看运气

5.threadfactory:创建新线程,executors.defaultthreadfactory()

handle:线程池的饱和策略

1.AbortPolicy:直接抛出异常,这是默认策略

2.CallerRunsPolicy:用调用者所在的线程来执行任务

3.DiscardOldestPolicy:丢弃队列中靠最前的任务,并执行当前任务

4.DiscardPolicy:直接丢弃任务

5.实现RejectedExecutionHandler接口的自定义handler

新任务提交execute执行后的判断

如果运行的线程少于corePoolSize,则创建新线程来处理任务,即使线程池中的其他线程是空闲的

如果线程池中的线程数量大于等于corePoolSize且小于maximumPoolSize,则只有当workQueue满时才创建新的线程去处理任务,如果设置的corePoolSize和maximumPoolSize相同,则创建的线程池的大小是固定的,这时如果有新任务提交,若workQueue未满,则将请求放入workQueue中,等待有空闲的线程去从workQueue中取任务并处理

如果运行的线程数量大于等于maximumPoolSize,这时如果workQueue已经满了,则通过handler所指定的策略来处理任务

线程池的状态

有五种状态,分别是running,shutdown,stop,tidying以及terminated,具体来说

RUNNING:能接受新提交的任务,并且也能处理阻塞队列中的任务

SHUTDOWN:不再接受新提交的任务,但可以处理存量任务

STOP:不再接受新提交的任务,也不处理存量任务

TIDYING:所有的任务都已终止

TERMINATED:terminated()方法执行完后进入该状态

五种状态的转换图

工作线程的生命周期

线程池的大小怎么选定?

一般有两种CPU密集型和I/O密集型

CPU密集型:线程数=按照核数或者核数+1设定

I/O密集型:线程数=CPU核数*(1+平均等待时间/平均工作时间)

Java 多线程与并发【原理第二部分笔记】的更多相关文章

  1. Java 多线程与并发【原理第一部分笔记】

    Java 多线程与并发[原理第一部分笔记] Synchronized synchronized的基本含义以及使用方式 在Java中线程安全问题的主要诱因就是存在共享数据(也称为临界资源)以及存在多条线 ...

  2. Java 多线程高并发编程 笔记(一)

    本篇文章主要是总结Java多线程/高并发编程的知识点,由浅入深,仅作自己的学习笔记,部分侵删. 一 . 基础知识点 1. 进程于线程的概念 2.线程创建的两种方式 注:public void run( ...

  3. Java 多线程与并发【知识点笔记】

    Java 多线程与并发[知识点笔记] Java多线程与并发 先说一下线程与进程的由来: 在初期的计算机,计算机只能串行执行任务,并且需要长时间的等待用户的输入才行 到了后来,出现了批处理,可以预先将用 ...

  4. JAVA 多线程和并发学习笔记(三)

    Java并发编程中使用Executors类创建和管理线程的用法 1.类 Executors Executors类可以看做一个“工具类”.援引JDK1.6 API中的介绍: 此包中所定义的 Execut ...

  5. Java 多线程:并发编程的三大特性

    Java 多线程:并发编程的三大特性 作者:Grey 原文地址: 博客园:Java 多线程:并发编程的三大特性 CSDN:Java 多线程:并发编程的三大特性 可见性 所谓线程数据的可见性,指的就是内 ...

  6. Java多线程与并发模型之锁

    这是一篇总结Java多线程开发的长文.文章是从Java创建之初就存在的synchronized关键字引入,对Java多线程和并发模型进行了探讨.希望通过此篇内容的解读能帮助Java开发者更好的理清Ja ...

  7. 031.[转] 从类状态看Java多线程安全并发

    从类状态看Java多线程安全并发 pphh发布于2018年9月16日 对于Java开发人员来说,i++的并发不安全是人所共知,但是它真的有那么不安全么? 在开发Java代码时,如何能够避免多线程并发出 ...

  8. JAVA多线程和并发基础面试问答(转载)

    JAVA多线程和并发基础面试问答 原文链接:http://ifeve.com/java-multi-threading-concurrency-interview-questions-with-ans ...

  9. [转] JAVA多线程和并发基础面试问答

    JAVA多线程和并发基础面试问答 原文链接:http://ifeve.com/java-multi-threading-concurrency-interview-questions-with-ans ...

随机推荐

  1. acwing 868. 筛质数

    线性筛 #include<bits/stdc++.h> #define N 1000010 using namespace std; int v[N],p[N]; void pr(int ...

  2. SpringBoot | 1.4 数据库事务处理

    前言 前面讲解了Sring的AOP,可以知道它是用来抽取公共代码,增强方法的.而在JDBC操作数据库进行数据处理时,有很多重复的公共代码:事务的提交与回滚跟AOP的约定流程很相似.因此,Spring数 ...

  3. Linux使用shell脚本监控

    (1)性能监控脚本 performance.sh #!/bin/bash #-------------------------------------------------------------- ...

  4. getopt模块的学习

    在运行程序时,可能需要根据不同的条件,输入不同的命令行选项来实现不同的功能.目前有短选项和长选项两种格式.短选项格式为"-"加上单个字母选项:长选项为"--"加 ...

  5. PHP经典算法之背包问题

    问题:假设有一个背包的负重最多可达8公斤,而希望在背包中装入负重范围内可得之总价物品,假设是水果好了,水果的编号.单价与重量如下所示: 1 栗子 4KG $4500 2 苹果 5KG $5700 3 ...

  6. Nginx 实践:location 路径匹配

    1. 目标 nginx 反向代理,路径映射的过程是什么?如何配置路径映射规则? 2.location 路径匹配 2.1 匹配规则: location 路径正则匹配: 符号 说明 ~ 正则匹配,区分大小 ...

  7. javascript数组 (转)

      javascript的Array可以包含任意数据类型,并通过索引来访问每个元素.   要取得Array的长度,直接访问length属性:   var arr = [1,2,3.14,'Hell0' ...

  8. http、tcp和socket简单理解

    1.Http属于应用层,主要解决如何包装数据. 2.Tcp属于传输层,主要解决数据如何在网络上传输. 3.Socket是对TCP/IP协议的封装,Socket本身并不是协议,而是一个调用接口(API) ...

  9. C语言共同体

    结构体(Struct)是一种构造类型或复杂类型,它可以包含多个类型不同的成员.在C语言中,还有另外一种和结构体非常类似的语法,叫做共用体(Union),它的定义格式为: union 共用体名{    ...

  10. 微信小程序云开发-云存储-上传单张照片到云存储并显示到页面上

    一.wxml文件 页面上写上传图片的按钮,按钮绑定chooseImg. <button bindtap="chooseImg" type="primary" ...