synchronize 底层原理
每个对象都有一个监视器锁(monitor),通过monitorEnter和monitorExist尝试获取锁和释放锁,当monitor的进入数为0时,通过monitorEnter指令尝试将进入数设置为1表示获取到锁,synchronize是可重入锁(double check的单例模式是最好证明)。
Lock基本思想
- 个volatile类型变量用来表示锁的状态
- 一个队列用来存储因未获取到锁而阻塞的线程
获取锁的流程
- 读取锁状态
- 若锁状态为0,通过CAS操作将锁状态置为1(多个状态只有一个会成功)
- 成功获取到锁从队列中移除,将下一个节点设置为首节点
- 若不成功继续留在队列中
释放锁的流程
- 释放锁的线程将锁的状态由1置为0,并唤醒队列的中的首节点,从unlock方法返回,继续执行其他代码
- 队列中的首节点和其他节点竞争,尝试获取锁
公平锁和非公平锁
公平锁是按照顺序获取锁,非公平锁可以插队获取锁,非公平锁效率高,非公平锁在获取锁是不会判断队列中是否为空,(前一个释放了锁,并唤醒了下一个线程,但下一线程并未从队列中移除)
volatile
volatile保证内存变量的可见行,保证单一读写操作的原子性,不能保证对j++符合操作的原子性,volatile禁止指令重排序
volatile的long或者double的变量读写是原子的,不然在多线程中可能只读取到改值的前32位
volatile和synchronize对比
volatile不会造成线程阻塞,synchronize 会造成线程阻塞
volatile保证可见行,synchronize保证可见性和原子性
volatile本质是在告诉jvm当前变量工作内存中的值是不确定的,需要从主存中读取; synchronized则是锁定当前变量,只有当前线程可以访问该变量,其他线程被阻塞住。
synchronize和lock对比
两种锁的底层实现
Synchronized:底层使用指令码方式来控制锁的,映射成字节码指令就是增加来两个指令:monitorenter和monitorexit。当线程执行遇到monitorenter指令时会尝试获取内置锁,如果获取锁则锁计数器+1,如果没有获取锁则阻塞;当遇到monitorexit指令时锁计数器-1,如果计数器为0则释放锁。
Lock:底层是CAS乐观锁,依赖AbstractQueuedSynchronizer类,把所有的请求线程构成一个CLH队列。而对该队列的操作均通过Lock-Free(CAS)操作。
lock需要手动释放而synchronize自动释放
try{
lock.lock();
}finally {
lock.unlock();
}
lock更灵活
synchronized的锁可重入、不可中断、非公平,而Lock锁可重入、可判断、可公平(两者皆可)
线程A和B都要获取对象O的锁定,假设A获取了对象O锁,B将等待A释放对O的锁定,
如果使用 synchronized ,如果A不释放,B将一直等下去,不能被中断
如果 使用ReentrantLock,如果A不释放,可以使B在等待了足够长的时间以后,中断等待具体是使用tryLock(long timeout,TimeUnit unit)方法,在规定时间内获取到锁返回true,否则返回false
lock可以可以使用Interruptibly 中断,synchronize必须执行完毕后释放
Lock可以使用读锁提高多线程读效率
线程池
// Java线程池的完整构造函数
public ThreadPoolExecutor(
int corePoolSize, // 线程池长期维持的线程数,即使线程处于Idle状态,也不会回收。
int maximumPoolSize, // 线程数的上限,线程数超过corePoolSize,且排队队列已经满
long keepAliveTime, TimeUnit unit, // 超过corePoolSize的线程的idle时长,
// 超过这个时间,多余的线程会被回收,但线程数不会低于corePoolSize
BlockingQueue<Runnable> workQueue, // 任务的排队队列
ThreadFactory threadFactory, // 新线程的产生方式
RejectedExecutionHandler handler) // 拒绝策略
-
workingQueue选择
ArrayBlockingQueue;
LinkedBlockingQueue;
SynchronousQueue;- ArrayBlockingQueue内部是一个定长数组,有两个变量分别记录队列的头节点和为节点在数组中的位置。添加元素和移除元素使用的是同一个锁。
- LinkedBlockingQueue 默认长度是Integer.Max 容易引起OOM,添加元素和移除元素使用的是独立的锁。
-
threadFactory 线程工程,用来创建线程
-
RejectedExecutionHandler 拒绝策略
AbortPolicy:丢弃任务并抛出RejectedExecutionException异常。
也是丢弃任务,但是不抛出异常
丢弃队列最前面的任务,然后重新尝试执行任务(重复此过程
丢弃最先进入的任务
由调用线程处理该任务 -
corePoolSize核心线程数和最大线程数设置不当会影响效率
-
maximumPoolSize等待队列设置不当可能引发OOM