并发编程三大特性:可见性,原子性,有序性
进程(在内存中的程序,运行中的程序)是分配资源的基本单位
线程(进程内部的执行单元)是CPU调度的基本(最小)单位,也叫轻量级进程LWP(Light Weight Process)
分为两种:
用户级线程(ULT):用户程序实现,不依赖操作系统核心,应用提供创建、同步、调度和管理线程的函数来控制用户线程。不需要用户态/内核态切换,速度快。内核对ULT无感知,线程阻塞则进程(包括它所有的线程)阻塞
内核级线程(KLT,JVM使用):系统内核管理线程(KLT),内核保存线程的状态和上下文信息,线程阻塞不会引起进程阻塞。在多处理器系统上,多线程在多处理器上并行运行。线程的创建、调度和管理由内核完成,效率比ULT要慢,比进程操作快
创建:继承thread类
Java线程创建依赖于系统内核,通过JVM调用系统库创建内核线程,内核线程与Java-Thread是1:1的映射关系
实现runnable接口
实现callable接口
创建线程池:实现线程池接口Executor
协程是虚拟机模拟的线程(用户线程)
并发:指两个或多个事件再同一时间段内发生
并行:指两个或多个再同一时刻发生
Java数据原子操作:
Read(读取):从主存中读取数据
Load(载入):从主存读取到的数据写入工作内存
Use(使用):从主存读取数据来计算
Assign(赋值):将计算好的值重新复制到工作内存中
Store(存储):将工作内存数据写入主存
Write(写入):将store过去的变量值复制给主存中的变量
Lock(锁定):将准村变量加锁,便是为线程独占状态
UnLock(解锁):将主存变量解锁,解锁后其他线程可以锁定该变量
早期解决线程之间数据不一致问题的解决方式:
总线加锁(性能太低):CPU从主存读取数据到高速缓存,会对总线对这个数据加锁,这样其他CPU没法去读或写这个数据,直到这个CPU使用完数据释放锁之后其他CPU才能读取该数据
现在解决线程之间数据不一致问题的解决方式:
MESI缓存一致性协议:多个CPU从主存读取同一个数据到各自的高速缓存,当其中某个CPU修改了缓存里的数据,该数据会马上同步回主内存,其他CPU通过总线嗅探机制(监听)可以感知到数据的变化从而将自己缓存里的数据失效
Volatile缓存可见性实现原理
底层实现主要是通过汇编lock前缀指令,它回锁定这块内存区域的缓存(缓存行锁定)并会写到主内存
IA-32架构软件开发者手册对Lock指令的解释:
(1) 会将当前处理器缓存行的数据立即写回到系统内存(主存)
(2) 这个写回内存的操作会引起在其他CPU里缓存了该内存地址的数据无效(MESI缓存一致性协议)
Volatile保证可见性与有序性,但是不保证原子性,保证原子性需要借助Synchronized这样的锁机制
voliate关键字:
保证内存可见性
禁止指令重排序
多线程编程中,有可能会出现多个线程同时访问同一个共享、可变资源的情况,这种资源可能是:对象、变量、文件等
共享:资源可以由多个线程同时访问
可变:资源可以在其生命周期内被修改
虚拟机的指令重排不可转换规则:
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()方法的开始;
JVM的内存屏障都必须实现四种内存屏障:
LoadLoad屏障:对于这样的语句Load1;LoadLoad;Load2,在Load2及后续读取操作要读取的数据被访问前,保证Load1要读取的数据被读取完毕
StoreStore屏障:对于这样的语句Store1;StoreStore;Store2,在Store2及后续写入操作执行前,保证Store1的写入操作对其他处理器可见
LoadStore屏障:对于这样的语句Load1;LoadStore;Store2,在Store2及后续写入操作执行前,保证Load1要读取的数据被读取完毕
StoreLoad屏障:对于这样的语句Store1;StoreLoad;Load2,在Load2及后续读取操作执行前,保证Store1的写入操作对其他处理器可见
缓存的速度
主存约80ns
L3高速缓存行 12ns
L2高速缓存行 4ns
L1高速缓存行 约1ns
寄存器< 1ns
Synchronized:
加锁:
(1) 同步实例方法,锁是当前实例对象
(2) 同步类方法,锁是当前类对象
(3) 同步代码块,锁是括号里面的对象
原理:JVM内置锁通过synchronized使用,通过内部对象Monitor(监视器锁)实现,基于进入与退出Monitor对象实现方法与代码块同步,监视器锁的实现依赖底层操作系统的Mutex Lock(互斥锁)实现,它是一个重量级锁,性能较低
对象锁的不同状态:
偏向锁:
轻量级锁:
重量级锁(JVM使用):mutex,系统的互斥锁
锁是控制线程的前后的执行过程
不对线程进行任何控制,线程之间的进行是并行的
锁代码:持有锁对象的时候才能执行这段代码
原子性:上锁之后,
1. 代码不可被打断(持有同样锁的其他代码,打断指同时运行)
线程池的意义:线程是稀缺资源,它的创建与销毁是一个相对偏重且耗资源的操作,而Java线程依赖于内核线程,创建线程需要进行操作系统状态切换,为避免小号需要设法重用线程执行多个任务。线程池就是一个线程缓存,负责对线程进行统一分配、调优和监控
什么时候使用线程池:
· 单个任务处理时间比较短
· 需要处理的任务数量大
线程池的优势:
· 重用存在的线程,减少线程创建,消亡的开销,提高性能
· 提高响应速度,当任务达到时,任务可以不需要等到线程创建就能立即执行
· 提高线程的可管理性,可统一分配、调优和监控
阻塞队列(FIFO):
1. 在任意时刻,不管并发有多高,永远只有一个线程能够进行队列的入队或者出队操作!线程安全队列
2. 队列满,只能进行出队操作,所有入队的操作必须等待;队列空,只能进行入队操作,所有出队的操作必须等待,也就是阻塞
线程池的五种状态:
线程池用一个int类型的值来记录当前线程:高三位记录线程池的生命状态,低二十九位记录当前工作的线程数