线程回顾
线程基本概念
程序:静态的代码
进程:进行中的程序,被加载到内存中,是操作系统分配内存的基本单位
线程:线程是程序处理的基本最小单位,是CPU执行的单位
线程的创建方式
继承Thread重写run() 创建类的对象
实现Runnable 接口 重写run()任务 new Thread(任务)
实现Callable接口 重写call()有返回值,可以抛异常
线程状态
多线程
什么是多线程
一个程序中,支持同时运行多个线程
优点:提高程序响应速度,提升硬件(CPU)利用率
缺点:线程过多占内存,CPU需要处理线程,需要性能能够满足,多线程访问同一个资源
并发编程
什么是并发编程
并行:在同一个时间节点上,同时发生(是真正意义上的同时执行)
并发:在一段时间内,对各个事件交替执行
并发编程:由于大量的请求访问同一个资源会出现线程安全问题,所以需要通过编程来控制解决多个线程依次访问资源,称为并发编程
并发编程的根本原因
多核CPU
JMM Java内存模型
Java内存模型,是Java虚拟机规范的工作模式
将内存分为主内存和工作内存
变量数据存储在主内存中,线程在操作变量是,会将主内存中的数据复制一份到工作内存
在工作内存中操作完成后,再写回到主内存中
多线程核心根本问题
基于Java内存模型的设计,多线程操作一些共享的数据是,出现以下3个问题
不可见性:分别同时对共享数据操作,彼此之间不可见,操作完协会主内存,有可能出现问题
无序性:为了性能对一些代码指令的执行顺序重排,以提高速度
非原子性:
int i =0;//主内存 预期结果为2,实际值为1
i++;//先把主内存中的数据读到工作内存中有i=0变为i=1,再写回到主内存中
i++;//同时又有一个线程进入,从主内存总获取到i的值为0,再进行++操作,i的值变为1,写回到主内存
解决办法
让不可见变为可见
让无序变为不乱序/不重排(有序)
非原子执行变为原子(加锁) 由于线程切换执行导致
volatile关键字
volatile 修饰的变量被一个线程修改后,可以在其他线程中立即执行
volatile 修饰的变量,在执行的过程中不会被重排序执行
volatile 不能解决原子性问题
缓存(工作内存)带来了不可见性
指令重排优化 带来了无序性
线程切换带来了非原子性
volatile 底层实现原理
在底层指令级别来进行控制
volatile 修饰的变量在操作前,添加内存屏障,不让其他的指令干扰
volatile 修饰的变量添加内存屏障之外,还要通过缓存一致性协议(MESI)将数据写回到主内存,其他工作内存嗅探后,把自己的工作内存数据获取,重新从主内存读取最新的数据。
原子性
只有通过加锁的方式。让线程互斥执行来保证依次只有一个线程对共享资源访问
synchronized: 关键字 修饰代码块,方法 自动获取锁,自动释放锁
ReentrantLock:类 只能对某段代码修饰 需要手动加锁,手动释放锁
CAS(Compare-And-Swap)
在Java中还提供了一些原子类,在低并发情况下使用,是一种无锁实现
采用CAS机制(Compare-And-Swap(比较并交换))是一种无锁实现,在低并发情况下使用
采用自选思想。
第一次获取内存值到工作内存中,存储起来作为预期值
然后对象数据进行修改
将工作内存中值写入到主内存,在写入之前需要做一个判断。用预期值与主内存中的值进行比较
如果预期值与主内存中值一致,说明没有其他线程修改,将更新数的值,写入到主内存
如果预期值与主内存中值不一致,说明其他线程修改,这时就需要重复操作整个过程
特点:不加锁,所有的线程都可以对共享数据操作
适合低并发时使用
由于不加锁,其他线程不需要阻塞,效率高
缺点:大并发时,不停的自旋判断,导致CPU占有率高
Java中的锁分类
Java中锁的名词
乐观锁:认为并发的操作,不加锁的方式是没有问题的,每次操作前判断(CAS,自旋)是否成立,不加锁实现。
悲观锁:认为并发操作肯定不会有问题,必须加锁,是加锁的实现。
可重入锁:当一个线程获取到外层方法的同步锁对象后,可以获取到内部其他方法的同步锁
读写锁:ReentrantReadWriteLock 支持读,写加锁
如果都是读操作,那么就不加锁,一旦有写操作,就加锁
分段锁:不是锁,是一种锁的实现思想,将锁的粒度拆分,提高效率
自旋锁:不是锁,是以自旋的方式重试获取
共享锁:读写锁的读锁就是共享锁,读读是不互斥,共享
独占锁:互斥锁,synchronized ReentrantLock 属于独占锁
公平锁:就是可以根据线程先来后到公平的获取锁 例如ReentrantLock 就可以实现公平锁
非公平锁:就没有先来后到,谁抢到谁获得执行权 ReentrantLock 也可以实现非公平锁 synchronized 是非公平锁
synchronized 锁
在sunchronized 锁的底层实现中,提供锁的状态,用来区别对待
这个锁的状态在同步锁对象头中,有一个屈原叫Mark World 中存储
锁的状态 :
无锁状态
偏向锁状态 一直是一个线程访问 记录线程的id 快速获取锁
轻量级锁状态 当所装他为偏向锁时,有继续有其他线程来范围跟,此时升级为轻量级锁 没有获取到锁的线程,不会阻塞,继续不断尝试获取锁
重量级锁状态 当锁的状态为轻量级锁时,线程自旋达到一定次数,进入到阻塞状态,锁状态升级为重量级锁,等待操作系统调度
AQS(AbstractQueueSynchronized)
抽象同步队列是juc其他锁实现的基础
思路:在类中维护一个state变量,然后还维护一个队列,以及获取锁,是方法锁的方法
当线程创建后,先判断state值,为0,没有线程使用,把state=1,执行完成后将state=0;
期间如果有其他线程访问,state=1,将其他线程放入到队列中
ReentrantLock
非公平
NofairSync
final void lock(){
if(compareAndSetState(0,1))//线程来到后,直接尝试获取锁,是非公平锁
setExclusiveOwnerThread(Thread.currentThread());
else//获取不到
acquire(1);
}
公平实现
FairSync
final void lock(){
acquire(1);
}
ConcurrentHashMap
线程安全的HashMap
对象引用
强引用:即对象有引用指向的,有Object obj = new Object();这种情况下new出来的对象不能被垃圾回收的
区别与 软引用,弱引用,虚引用
软引用,弱引用,虚引用都是用来标记对象的一种状态
当一些对象成为垃圾后,还需要有不同的状态,可以继承
SoftReference,WeakReference,PhantomReference或者把自己的对象添加到软,弱,虚的对象中
软引用:如果内存充足的情况下,可以保留软引用对象,
如果内存不足,经过一次垃圾回收后任然不够,那么将清除软引用的对象
弱引用:弱引用管理的对象,只能存活到下一次垃圾回收
虚引用:和没有任何引用是一样的,只是为了系统的监测
线程池
池的概念
频繁的创建数据连接对象,销毁,在时间上开销较大
集合
事先创建出一些连接对象,每次使用时,从集合中直接获取,用完不销毁,减少创建,销毁。
在jdk5之后,提供线程池的实现
使用ThreadPoolExecutor类实现线程池创建管理
池的好处:减少频繁创建销毁时间,统一管理线程,提高速度
ThreadPoolExecutor
参数
corePoolSize:核心线程池大小
maximumPoolSize:线程池最大数量
keepAliveTime:非核心线程池中的线程,在多久没有任务执行时,就终止
unit:为 keepAliveTime 设定单位
workQueue:一个阻塞队列,用来存储等待的任务。
ArrayBlockingQueue 有界的阻塞对列,必须给定最大容量
threadFactory: 线程池工厂
handler:拒绝策略 核心线程池,阻塞队列,非核心线程池已满,继续有任务,如歌执行
AboryPolicy(); 抛出异常,拒绝执行
DiscardOldestPolicy();丢弃等待时间最长的任务
DiscardPolicy();直接丢弃,不执行
CallerRunsPolicy();交友当前提交任务的线程执行
execute();提交任务,没有返回值 和 submit() 提交任务,可以有返回值
关闭线程池
shutdownNow();直接关闭
shutdown();不再接受任务,等待任务执行完关闭
ThreadLocal
本地线程变量,可以为每个线程创建一个变量副本,使得多个线程主键相互隔离不影响
ThreadLocal底层实现
为每个当前线程创建了一个ThreadLocalMap,唯一的ThreadLocal对象作为key
ThreadLoacl内存泄漏问题
因为ThreadLocal与弱引用有关,key失效后,value还被强引用着,造成内存泄漏
正确的用法,用完之后,及时调用remove()清除