三个概念:
原子性: 一个操作不可中断,要么成功,要么失败;
可见性: 一个线程修改共享变量后其他线程能够立即获取这个修改;
有序性: 同一时刻只有一个线程可以获取锁,其他线程只能等待获取锁;
synchronized 原子性,有序性,可见性;
volatile 有序性,可见性
禁止指令重排的方式:1.volatil;2.lock;3.final
单例模式:
饿汉式: 加载的直接创建对象,避免多线程同步问题,缺点是浪费内存;
懒汉式: 判断不为空,创建对象,解决了内存浪费问题,但多线程可能产生多个实例;
双重校验锁(DCL): 判空 加锁 判空 创建对象;缺点:cpu指令重拍导致调用构造函数前先分配了内存指向了引用,其他线程获取的就是未执行构造器的对象;
解决方式:增加volatile关键字,禁止指令重拍;
静态内部类
枚举
JAVA内存模型,JMM,JAVA MEMORY MODE
1.java内存模型将内存分为
堆和栈
;栈线程私有,堆是线程共享;
2.线程会在硬件的CPU核心上运行,CPU内部会寄存器和高速缓存(1,2,3级),单个CPU运算通过高速缓存与计算机主内存通过缓存一致性协议交互(为提高CPU运算单元充分利用,在保证执行结果一致的前提下发生指令重排);
3.与硬件架构类似,java每个线程通过JMM控制各自的本地内存与计算机主内存中的共享变量交互
;
Happens-before:
前一个操作结果可以被后续的操作获取;
为什么需要?
线程运行程序过程都是在本地内存中完成,完成后以copy方式写入主内存,这就存在可见性问题,为了避免编译优化指令重排影响并发安全(如:DCL单例指令重排问题),通过happens-before原则保证并发安全;
规则
程序顺序执行 同一线程前面的写操作对后面的读操作可见;
volatile规则 对一个volatile变量的写操作结果,对后续这个变量的读操作可见;
线程启动规则 运行中的线程对共享变量修改结果对新开启的线程可见;
线程终止规则 线程终止前对共享变量的修改对其他线程可见;
线程中断规则 调用interrupt()方法设置线程可中断标记对后续获取可中断标记可见;
传递性 A happens-before B ,B hapepends-before C 则 A happens-before C;
管程锁定规则 同一个锁的unlock操作前对共享变量的修改对其他lock的线程可见;
java实现同步的方式:
1.synchronized关键字;
2.synchronized代码块;
3.volatile保证共享变量可见性,一般结合cas实现同步;
4.Lock,比如ReentrantLock,ReentrantReaderWriterLock实现同步;
5.通过ThreadLocal实现同步;
JUC包
public final void acquire(int arg) {
if (!tryAcquire(arg) &&
acquireQueued(addWaiter(Node.EXCLUSIVE), arg))
selfInterrupt();
}
共享锁与独占锁?
独占锁 也叫排他锁,只有一个线程可以获取锁,获取锁的线程可以读写数据;
共享锁 可以被多个线程同时拥有,某个线程设置了共享锁,其他线程不可以再设置独占锁,获取共享锁的线程只能读数据,不可写;
公平锁与非公平锁?
公平锁 按照CLH队列顺序依次获取锁,先去排队,每次获取锁都会检查当前节点是否为头结点;
非公平锁 先尝试获取锁,尝试失败,才会去排队,每次获取锁不会校验是否为头结点;
获取锁流程;
1.内部维护一个Sync类继承
AQS
(AbstractQueuedSynchronizer),根据构造方法不同创建公平锁与非公平锁,独占锁或者共享锁
;
2.调用lock()
方法;公平锁(acquire())
1.获取
state
值;
state = 0 查看当前是否为等待队列第一个位置;
等待队列为空或者处于第一个位置 --> cas将state设置值;
有其他线程等待分配锁 --> 加入等待队列,等待唤醒执行3;
state != 0 加入CLH等待队列;
2.cas
state
成功 --> 获取到锁,将占用线程设置为当前线程;
失败 --> 获取锁失败,加入CLH等待队列;
3.占有锁线程释放锁,唤醒等待队列的头结点所在线程,被唤醒线程重复1;
>>非公平锁**(cas --> acquire())
1.获取state者;
== 0 (不管等待队列是否有等待的线程)直接cas设置值;
= 0 加入CLH等待队列
2.cas state
成功 获取到锁,设置线程占用;
失败 进入3
3.获取锁失败
自旋加入等待CLH队列(addWaiter),队列首节点自旋获取锁(acquireQueued()),其他等待队列节点阻塞,等待成为首节点;
addwaiter方法:
1.如果head tail为空,创建哨兵节点,head,tail指向哨兵节点;
2.将当前线程封装的节点加入CLH队列尾部;
将tail指向的节点(当前尾节点)的next指针指向新加入节点;
将新加入节点的pred指向tail节点;
改变tail指针指向当前节点,完成双向联表的插入;
锁流程共享锁与独占锁几个区别点
获取锁的时候,独占锁会判断state是否为0后直cas设置state;共享锁判断是否存在独占锁,不存在则通过cas增加state;
释放锁的时候,独占锁只会unpark头结点,共享锁会唤醒所有waitStatus为-1的节点;
J.U.C Condition类;
由AQS实现,ConditionObject.class
condition需要绑定一个锁使用,通过Lock接口的的newCondition方法获取,获取的方法在AQS里实现;
condition的await和single方法必须先获取到锁;
常用的并发工具类
ReentrantLock
可重入锁,可以根据构造不同创建公平锁和非公平锁,默认非公平锁;
ReentrantReadWriteLock
可重入读写锁,可以创建公平锁和非公平锁,分为读锁和写锁,读锁是共享锁,写锁是独占锁;
CountDownLatch
同步计数器,构造函数设置需要等待的线程数,每完成一个,count--,count为0继续,否则等待,用于协调多个线程同步;
countDown,线程调用,减少计数器(state值),await,线程调用,可用于main线程,等待计数器为0;
不可重置
CyclicBarrier
屏障,栅栏,主要解决线程组内部线程之间的相互等待问题,一组知道最后一个线程达到屏障(线程内部调用await方法),所有阻塞的线程才可以继续执行,不会阻塞主线程,;
可通过reset方法重置
Semaphore
信号量,可以控制某个资源访问的线程数量,初始构造state个线程;
底层通过AQS共享锁原理实现;
调用acquire方法,如果state不为0,则可以获取锁,将state--;
调用release方法,将state++;
Exchange
交换器,exchange相当于一个交换场所,内部维护slot槽(Node,包换index,hash,待交换对象等)节点,通过exchange()方法设置交换点;
首先执行到exchange()方法阻塞等待其他交换线程;
线程池 Executor
创建线程的两种方式
new Thread()
实现Runnable接口
为什么使用线程池
创建和销毁线程花销较大,有时候比业务时间还长,频繁创建和销毁线程,增加系统负担和系统耗时;
线程池的作用
提高效率 减少系统维护线程的开销;
解耦 运行和创建分开;
复用 线程可以复用;
方便管理 方便管理线程,比如设置最大访问线程数等;
jdk的给的线程池创建方式
ThreadPoolExcutorextends
AbstractExecutorServiceimplement
ExecutorServiceextends
Executor
通过Executors类静态方法通过不同参数调用ThreadPoolExcutor的构造方法;ThreadPoolExeutor参数
corePoolSize:
核心线程个数,创建线程池的时候创建,与pool生命周期相同;
maximumPoolSize
最大线程个数,核心线程加后创建的线程最大个数;
keepAliveTime
和unit
非核心线程空闲销毁等待时间,unit为时间单位;
workQueue
任务等待获取线程的阻塞队列;
newSingleThreadPool
创建核心线程为1,最大线程数为1,等待队列为LinkedBlockingQueue的线程池;
newFixedThreadPool
创建一个指定线程个数的线程池,core和max一样,等待队列为LinkedBlockingQueue;
newCacheThreadPool
创建一个可变大小的线程池,等待队列为LinkedBlockingQueue;
newScheduledThreadPool
创建一个指定核心线程数的可变线程池,等待队列为DelayedWorkQueue();
几个问题
固定线程数,可能会导致等待队列创建大量任务耗费内存,甚至oom;
最大线程数Integer.MAX_VALUE,可能创建大量线程导致oom;
线程池执行任务流程
提交任务 -> 判断线程池线程个数是否达到最大核心线程数 -> 未达到,创建核心线程执行任务 ->达到核心线程数,尝试加入等待队列 ->加入等待队列失败,判断总线程数量是否达到最大线程数,未达到创建线程,达到抛异常;
线程池的执行状态
RUNNING
线程池接受任务并对任务进行处理,线程创建后处于RUNNING状态;
SHOTDOWN
RUNNINT状态调用shotdown()方法转化为SHOTDOWN状态,线程池不接受新任务,但会处理已添加的任务;
STOP
RUNNING/SHOTDOWN状态调用shotdownNow()转化为STOP状态,线程池不接受任务,不处理已添加任务,中断正在执行的任务;
TIDYING
处于STOP/SHOTDOWN状态的线程池中执行任务数为0转化为TIDYING状态,同时调用terminnated()方法;
TERMINATED
线程池执行完terminated方法后处于中止状态;
因此需要使用线程池的时候需要根据系统具体业务,通过ThreadPoolExecutor构造定制化的线程池;