抽象队列同步器AQS
实现原理
AQS在内部定义了一个volatile int state 变量,表示同步状态;当线程调用lock方法时,如果state=0,说明没有任何线程占有共享资源的锁,可以获得锁并将state=1;如果state=1,则说明有线程目前正在使用共享变量,其他线程必须加入同步队列进行等待,
AQS通过Node内部类构成一个双向链表结构的同步队列,来完成线程获取锁的排队工作,当有线程获取锁失败后,就会被添加到队列末尾。
Node类时对要访问同步代码的线程的封装,包含了线程本身以及其状态交waitStatus(有五种取值,分别表示是否被阻塞,是否等待唤醒,是否已经被取消等),每个Node节点关联其prev节点和next节点,方便线程释放后唤醒下一个在等待的线程,是一个FIFO的过程。
Node类有两个常量,SHARED和EXCLUSIVE,分别表示共享模式和独占模式。所谓共享模式是一个锁允许多个线程同时操作(信号量Semaphore就是基于AQS共享模式实现的),独占模式是同一时间段只能有一个线程资源对共享资源进行操作,其余的线程需要排队等待(ReentranLock)。
AQS通过内部类ConditionObject构建等待队列(可有多个),当Condition调用wait()方法后,线程加入等待队列中,而当Condition调用signal()方法后,线程将从等待队列转移到同步队列中进行锁竞争。
AQS和Condition各自维护了不同的队列,在使用Lock和Condition的时候,其实就是两个队列的相互移动。
acquire()
release()
ReentrantLock
非公平锁(默认)
NonfairSync
lock()
tryAcquire()
nonfairTryAcquire()
公平锁 new ReentrantLock(true)
FairSync
lock()
tryAcquire()
首先看队列里是否有人排队
没有人排队则尝试获取锁
未获取到锁,看当前拿到锁的线程是否是自己,是则自己state+1重入
线程池
为什么使用线程池
单个线程的创建和销毁的开销太大
有效的控制线程数量,避免创建过多线程
核心参数
(线程池核心线程数)corePoolSize
(线程池里允许的最大线程数量)maximunPoolSize
(等待时间,coolPoolSize外的线程等待时间大于这个值,则会被清理掉)keepAliveTime
(keepAliveTime的单位)unit
(工作队列,当前运行的线程数 > corePoolSize时,多出来的线程进入queue等待)workQueue
(如果有新的线程需要创建时,就是由这个线程池来进行创建)threadFactory
(线程数超过maximumPoolSize并且queue满了的时候,仍有线程进来所执行的策略(默认直接报错))handle
常见线程池
(单线程池队列)SingleThreadExecutor
里面就一个线程,然后慢慢去消费
如果这个唯一的线程因为异常结束,那么会有一个新的线程来替代它。此线程池保证所有任务执行顺序按照任务的提交顺序执行
corePoolSize:1,只有一个核心线程;maximumPoolSize:1;keeoAlive 0l, workQueue:new LinkBlockingQueue<Runnable>(),其缓冲队列是无界的
(固定数量线程池)FixedThreadExecutor
适用于负载比较均衡的情况,比如我们系统每小时运行一次生成统计生成报表任务
根据你设定的线程数量执行,多出来的进入排队等待队列
(自动回收线程池)CacheThreadEcxecutor
ScheduleThreadEcecutor
Java内存模型(JMM)
Java内存模型规定所有的变量都存储在主内存中,包括实例变量,静态变量,但是不包括局部变量和方法参数。每个线程都有自己的工作内存,线程的工作内存保存了该线程用到的变量和主内存的副本拷贝,线程对变量的操作都在工作内存中进行。线程不能直接读写主内存中的变量。
八种内存交互操作
lock(锁定),作用于主内存中的变量,把变量标识为线程独占的状态。
read(读取),作用于主内存的变量,把变量的值从主内存传输到线程的工作内存中,以便下一步的load操作使用
load(加载),作用于工作内存的变量,把read操作主存的变量放入到工作内存的变量副本中。
use(使用),作用于工作内存的变量,把工作内存中的变量传输到执行引擎,每当虚拟机遇到一个需要使用到变量的值的字节码指令时将会执行这个操作。
assign(赋值),作用于工作内存的变量,它把一个从执行引擎中接受到的值赋值给工作内存的变量副本中,每当虚拟机遇到一个给变量赋值的字节码指令时将会执行这个操作。
store(存储),作用于工作内存的变量,它把一个从工作内存中一个变量的值传送到主内存中,以便后续的write使用。
write(写入):作用于主内存中的变量,它把store操作从工作内存中得到的变量的值放入主内存的变量中。
unlock(解锁):作用于主内存的变量,它把一个处于锁定状态的变量释放出来,释放后的变量才可以被其他线程锁定。
重排序
为了提高执行性能,编译器和处理器会对指令进行重排序
编译器重排序
CPU指令重排序
CPU内存重排序
as-if-serial语义
所有的动作(Action)5都可以为了优化而被重排序,但是必须保证它们重排序后的结果和程序代码本身的应有结果是一致的。Java编译器、运行时和处理器都会保证单线程下的as-if-serial语义。
happens-before规则
程序次序法则:线程中的每个动作A都happens-before于该线程中的每一个动作B,其中,在程序中,所有的动作B都能出现在A之后。
监视器锁法则:对一个监视器锁的解锁 happens-before于每一个后续对同一监视器锁的加锁。
volatile变量法则:对volatile域的写入操作happens-before于每一个后续对同一个域的读写操作。
线程启动法则:在一个线程里,对Thread.start的调用会happens-before于每个启动线程的动作。
线程终结法则:线程中的任何动作都happens-before于其他线程检测到这个线程已经终结、或者从Thread.join调用中成功返回,或Thread.isAlive返回false。
中断法则:一个线程调用另一个线程的interrupt happens-before于被中断的线程发现中断。
终结法则:一个对象的构造函数的结束happens-before于这个对象finalizer的开始。
传递性:如果A happens-before于B,且B happens-before于C,则A happens-before于C
内存屏障<br>
LoadLoad屏障:对于这样的语句Load1; LoadLoad; Load2,在Load2及后续读取操作要读取的数据被访问前,保证Load1要读取的数据被读取完毕。
StoreStore屏障:对于这样的语句Store1; StoreStore; Store2,在Store2及后续写入操作执行前,保证Store1的写入操作对其它处理器可见。
LoadStore屏障:对于这样的语句Load1; LoadStore; Store2,在Store2及后续写入操作被刷出前,保证Load1要读取的数据被读取完毕。
StoreLoad屏障:对于这样的语句Store1; StoreLoad; Load2,在Load2及后续所有读取操作执行前,保证Store1的写入对所有处理器可见。它的开销是四种屏障中最大的。在大多数处理器的实现中,这个屏障是个万能屏障,兼具其它三种内存屏障的功能。