目录
1.并发编程特性:
-
多线程是一种程序开发或设计环境
-
并发编程是一种程序设计概念或设计目标,在多线程开发环境中,同一系列的程序设计与机制的使用,确保多线程开发环境是稳定的,快速的,性能优秀的。
-
并发编程具有3个特征
-
原子性
-
可见性
-
有序性
-
-
在多线程开发时,如果程序设计不是特别的完美,有可能会出现原子性问题,可见性问题,有序性问题
-
我们就会尽可能的解决上述问题,使得我们的程序具有原子性,可见性,有序性。 这样的程序开发就成为并发编程。
2.并发编程的特性之一:原子性
-
一系列操作或一些列代码指令属于一个独立的单元
-
在一个线程执行过程中,不受到另一个线程的影响
-
我们就称之为复合原子性。否则就是不符合原子性。
-
所以原子性的特点就是:一系列指令要么全都执行完毕,要么全都不执行。
-
原子性问题如何解决?上锁
-
悲观锁 : synchronized , lock
-
乐观锁:CAS + 自旋 。 AtomicInteger
-
3.并发编程的特性之一:可见性
要想了解可见性,需要先了解 JMM (JAVA 内存 模型) , 需要再先回忆JVM内存模型(运行时数据区)
3.1 JVM内存模型
-
jvm在运行过程中,会产生很多的数据(对象,属性,变量等),这些数据都会存储在jvm内存中随着作用及特点不同,会存储在不同的区域
-
这个区域分为5部分
-
方法区
用来存放类加载的信息,同时存放静态属性 和 方法(静态方法和普通方法)
jdk1.7之后,取消了方法区名称,改为元空间
方法区也叫元空间也叫永久区
方法区中的数据,可以被多线程共享。访问时会有数据共享的安全问题
-
堆区
用来存放对象或数组的 , 也就是用来存放new关键字创建的内容
堆区中的数据,可以被多线程共享。访问时会有数据共享的安全问题
-
方法栈
用来存放调用方法及方法执行的过程中产生的临时数据的(临时变量,参数列表)
这些数据是以栈帧结构存入方法栈(压栈)
随着方法执行完毕,这些数据就会被销毁回收(弹栈)
栈帧分为:局部变量表, 操作数栈,返回地址,动态链接。
方法栈是线程独享的,也就是每一个线程都会有一个方法栈。
-
程序计数器
在线程上下文切换时,用来记录刚刚执行的行号
程序计数器也是线程独享的。
-
本地方法栈
特点与方法栈基本相同
不同点时,当调用native修饰的方法时,其产生的数据会存储在本地方法栈中。
-
3.2JMM(Java 内存 模型)
-
JMM不是一个具体的内容,也不是对内存的一个划分,JMM是一个规则规范
-
JVM开发人员,按照这个规范,来开发JVM,使其在多线程应用中复合JMM规范
-
JVM使用人员,按照这个规范,来使用JVM,使得可以更好的实现多线程开发。
-
JMM规范
-
所有的共享数据,存储在一个主内存中
-
每一个线程都有自己的工作内存
-
当线程需要使用共享数据时,不能主内存中直接操作
而是要先从主内存备份到工作内存
在工作内存中对数据进行修改
再将工作内存中的数据,更新回主内存。
-
每个线程的工作内存彼此之间是不可见的。
-
-
基于JMM规范的特点,如果我们在多线程访问共享资源时,不加以设计,有可能出现数据不可见问题
-
所以我们需要解决多线程(工作内存中共享)数据(备份)不可见问题
-
如何解决可见性问题?
-
加锁 ,每次加锁前,都会从主内存备份数据,每次释放锁时,都会向主内存更新数据。
System.out.println方法底层使用synchronized关键字加了锁
-
线程睡眠,每次睡醒都会去主内存重新加载数据
-
将需要可见的变量,使用volatile关键字修饰 (轻量级同步机制)
线程每次要获得volatile关键字修饰的变量值时,都会从主内存加载新数据
线程每次为volatile关键字修饰的变量赋值时,都会将新值跟新会主内存。
-
4.并发编程的特性之一:有序性
-
在编译和运行过程中,有可能为了优化编译和运行过程,会调整代部分码指令的顺序。
-
使得在单线程环境中,最终的执行结果不变,称之为 指令重排序
-
在多线程的环境中,可能就会存在一些问题,称为有序性问题。
-
如何解决有序性问题?
-
有序性问题会涉及一个 happen-before原则
-
只要复合上述原则的程序代码,就不会出现有序性问题 (代表不会出现指令重排序)
-
加锁可以避免指令重排序
-
使用volatile关键字修饰属性,在使用该属性时(赋值,取值)。该属性上面和下面的代码不会重排序
volatile修饰的属性,在jvm执行时,会增加内存屏障
-
-