目录
1.共享锁与独占锁
共享锁
独占锁
2.锁的状态与锁升级
1.锁的状态分为四种
2.锁升级
3.重量级锁(Mutex Lock)
1.含义
2.缺点
3.Synchronized:本质上依赖于重量级锁实现
4.轻量级锁
5.偏向锁
1.含义
2.优点
6.锁优化操作
1.减少锁持有时间
2.减小锁粒度
3.锁分离
4.锁粗化
5.锁消除
7.线程池的组成
8.拒绝策略
1.使用场景
2.JDK内置策略
9.线程池的工作过程
1.线程池创建
2.调用execute()添加任务
3.完成任务
4.keepAliveTime管理线程
10.Java阻塞队列
共享锁允许多个线程同时获取锁,并发访问共享资源。
共享锁是一种乐观锁,放宽了加锁策略,允许多个执行读操作的线程同时访问共享资源
ReadWriteLock就是一个典型的共享锁
独占锁只有一个线程能持有锁
独占锁是一种悲观锁,限制了不必要的并发性
无锁状态、偏向锁、轻量级锁、重量级锁。
随着锁的竞争,锁进行单向升级,从低到高升级。锁可以从偏向锁升级到轻量级锁,再升级到重量级锁
依赖于操作系统所实现的锁称为重量级锁
依赖于底层的操作系统来实现锁,而操作系统实现线程之间的切换需要从用户态转换为内核态,需要花费比较长的时间,且性能消耗很高
synchronized是通过对象内部的监视器锁monitor来实现的,监视器锁本质上依赖于底层的操作系统的重量级锁来实现。因此synchronized效率低。jdk6为synchronized做了不少优化,核心都是减少重量级锁的使用。
用来在没有多线程竞争的前提下,减少传统的重量级锁使用产生的性能消耗。
轻量级锁能够在线程交替执行同步代码块时提高性能;但若出现同一时间访问同一把锁的情况,比如调用了锁对象的wait()和notify()方法,就会从轻量级锁升级为重量级锁
在多线程并发执行下,若锁没有存在多线程竞争,而是由同一线程多次获得锁,此时该锁就进入了偏向状态。
引入偏向锁后,在无多线程竞争的情况下减少了不必要的轻量级锁执行路径,从而在只有一个线程执行同步代码块时提高性能
只在有线程安全要求的程序上加锁
将一个可能被很多线程访问的对象拆分成一个个小部分,从而增加并行度,减少锁竞争;降低了锁的竞争后,偏向锁、轻量级锁的成功率才会高
将锁的功能进行分离,只要对操作不影响就可以分离。比如读写锁ReadWriteLock,根据功能将锁分离成读锁和写锁,保证了线程安全,提高了性能。
为保证多线程间的有效并发,会要求每个线程持有锁的时间尽量短,操作完共享数据后立即释放锁。但若对同一个锁不停的请求、同步、释放,本身也会消耗很多资源,因此需要粗化处理;将所有对锁的操作整合成锁的一次请求,从而减少对锁的请求同步次数,这就是锁的粗化。
在编译时,如果发现不可能被共享的数据,则可以消除对这个数据的锁操作。
1.线程池管理器:用于创建并管理线程
2.工作线程:线程池中的线程
3.任务接口:每个任务必须实现的接口,用于工作线程调度任务运行
4.任务队列:用于存放待处理的任务,提供一种缓冲机制
以下以代码的形式具体了解线程池的组成:
代码示例:线程池实现需要使用的ThreadPoolExecutor类
public ThreadPoolExecutor(int corePoolSize,
int maximumPoolSize,
long keepAliveTime,
TimeUnit unit,
BlockingQueue workQueue) {
this(corePoolSize, maximumPoolSize, keepAliveTime, unit, workQueue,
Executors.defaultThreadFactory(), defaultHandler);
}
按照注释可知:
corePoolSize:线程池中的线程数
maximumPoolSize:线程池中的最大线程数
keepAliveTime:当前线程池数量超过 corePoolSize 时,多余空闲线程在终止前等待新任务的最长时间。即多长时间内会被销毁
unit: keepAliveTime 的时间单位
workQueue:用于容纳任务的队列,被提交但尚未被执行的任务
threadFactory:线程工厂,用于创建线程
defaultHandler:默认拒绝策略,当任务太多来不及处理,如何拒绝任务
当线程池中的线程被用完,且等待队列已满,此时需要使用拒绝策略处理新任务
1.AbortPolicy-中止策略:直接抛出异常
2.CallerRunsPolicy-调用方运行策略:在调用者线程中运行当前被丢弃的任务
3.DiscardOldestPolicy-丢弃最旧策略:丢弃最老的请求,尝试再次提交当前任务
4.DiscardPolicy-丢弃策略:丢弃无法处理的任务
线程池创建好后,还没有一个线程,等待任务队列传入。
添加任务时,需要做判断:
1)若正在运行的线程数小于corePoolSize,则创建线程运行该任务
2)若正在运行的线程数大于等于corePoolSize,则将该任务放入队列
a)若队列已满,正在运行的线程数量小于maximumPoolSize,则创建线程运行任务
b)若队列已满,正在运行的线程数量大于等于maximumPoolSize,则抛出异常
当线程完成任务后,从任务队列中执行下一个任务
若一个线程超过keepAliveTime没有任务,则线程池判断若当前运行线程数大于corePoolSize,就终止该线程。
线程阻塞具有两种情况:
1.当队列中没有数据的情况下,消费者端的所有线程都会被自动阻塞,直到有数据放入队列
2.当队列中填满数据的情况下,生产者端的所有线程都会被自动阻塞,直到队列中有空位置,线程被自动唤醒