一、各种锁的概念
1.乐观锁与悲观锁
乐观锁
每次读取数据的时候都认为数据没有被修改过,读取数据的时候不加锁 , 但是在更新的时候会去对比一下原来的值,看有没有被别人更改过。适用于读多写少的场景
juc中atomic 使用的就是乐观锁,即CAS
悲观锁
每次读取数据的时候都认为数据已经被修改过,读取数据的时候也会加锁。别人想要拿到数据就要等待锁。适合写操作比较多的场景
synchronized 实现也是悲观锁
2.共享锁/独占锁
独占锁
独占锁指锁一次只能被一个线程占有
ReentrantLock 就是独占锁
共享锁
锁可以被多个线程持有
ReadWriteLock 中 Read 共享, write独占
3.可重入锁
如果当前线程持有obj对象的锁,而内部代码块中还需要获取obj锁,直接放行的方式就是可重入锁。
synchronized 和 ReentrantLock都是可重入锁
实现方式为为锁对象设置一个计数器和占有他的线程,每次获取锁的时候计数器加一,释放锁的时候计数器减一,当计数器为0的时候释放锁。
4.公平锁和非公平锁
公平锁
所有尝试获取锁的线程都会加入锁的等待队列,每次唤醒队列中的第一个线程。
常见于AQS
非公平锁
抢占锁时候判断锁是否被占有,没被占有直接抢占锁,如果被占有就加入等待队列
ReentrantLock使用非公平锁
非公平锁的性能比公平锁好,因为线程有机会不阻塞直接获得锁,公平锁需要唤醒阻塞队列中的线程,所以公平锁的CPU开销会比较大。
5.无锁、偏向锁、轻量级锁、重量级锁
偏向锁
仅有一个线程在使用锁,没有竞争线程,就是偏向锁,一旦有其他线程产生竞争,锁升级为轻量级锁
轻量级锁
当前有两个线程,一个持有锁,另一个会自旋等待锁;当再有一个线程(3个以上)同时竞争锁的时候,锁升级为重量级锁
重量级锁
其他线程试图获取锁的时候都会进入阻塞队列,只有当前线程释放锁的时候才会唤醒线程。
6.自旋锁
获取不到锁就一直循环试图获取锁。
7.互斥锁和读写锁
synchronized、ReentrantLock属于互斥锁;(都是独占锁)
ReadWriteLock 属于读写锁(读为共享锁,写为独占锁)
二、线程的实现方式
Java 中线程的实现方式主要有四种,利用Spring 注解@Asyn 也可以实现,这里不做详细讨论。它们分别是:
- 继承Thread类,重写run 方法
- 实现Runnable 接口,重写run 方法
- 实现Callable 接口,重写call()方法,创建FutureTask 对象,指定Callable 对象
- 利用线程池
1. 继承Thread 类
class TestThread extends Thread {
@Override
public void run() {
// do something ....
}
}
2. 实现Runnable 接口
class TestThread implements Runnable {
@Override
public void run() {
// do something ....
}
}
3. 实现Callable 接口,配合FutureTask
// 创建Callable 对象
Callable<String> stringCallable = () -> {
System.out.println("do something");
Thread.sleep(2000);
return "ok";
};
// 根据Callable 对象创建FutureTask 对象
FutureTask<String> stringFutureTask = new FutureTask<>(stringCallable);
// 创建线程并启动
new Thread(stringFutureTask).start();
// 阻塞当前线程直到FutureTask返回处理结果
String result = stringFutureTask.get();
4. 使用线程池
三、线程池详解
1. 为什么使用线程池
在开发过程中不建议使用直接使用继承Thread类或者直接实现Runnable 的方式来管理线程,当每接收一个请求创建一个线程,线程执行完毕再销毁的这种模式会引发如下事实:
- 频繁创建线程耗费资源
- 线程上下文切换问题
- 可能引发资源耗尽的风险
使用线程池后,优点如下:
- 加快响应时间
- 增加吞吐量
但是线程池使用不当也会有一些风险,比如:
- 死锁:线程池中的线程持有其他线程的锁
- 资源不足:假如不断向无限线程池中添加任务就会导致资源不足。
- 并发错误:wait 和 notify 使用不当
- 请求过载:QPS 极高的情况下,不可能为每个请求都分配一个线程,分配可能导致请求过载。
2. 线程池核心参数与工作原理
核心参数
定义:
public ThreadPoolExecutor(int corePoolSize,
int maximumPoolSize,
long keepAliveTime,
TimeUnit unit,
BlockingQueue<Runnable> workQueue,
ThreadFactory threadFactory,
RejectedExecutionHandler handler);
其中各个参数的含义如下:
- corePoolSize:核心线程数目,核心线程没有最长存活时间,及时线程终止也不会被回收。
- maximumPoolSize:线程池内 (核心线程+非核心线程)的数量
- keepAliveTime:非核心线程的最大存活时间
- unit:keepAliveTime的单位
- workQueue:等待执行的任务队列
- ArrayBlockingQueue:是一个基于数组结构的有界阻塞队列,此队列按 FIFO(先进先出)原则对元素进行排
- LinkedBlockingQueue:一个基于链表结构的阻塞队列,此队列按FIFO (先进先出) 排序元素,吞吐量通常要高于ArrayBlockingQueue。静态工厂方法Executors.newFixedThreadPool()使用了这个队列。
- SynchronousQueue:一个不存储元素的阻塞队列。每个插入操作必须等到另一个线程调用移除操作,否则插入操作一直处于阻塞状态,吞吐量通常要高于LinkedBlockingQueue,静态工厂方法Executors.newCachedThreadPool使用了这个队列。
- PriorityBlockingQueue:一个具有优先级的无限阻塞队列。
- threadFactory:线程工厂类
- handler:拒绝策略
阻塞队列
- ArrayBlockingQueue; //基于数组的先进先出队列,此队列创建时必须指定大小;
- LinkedBlockingQueue; //基于链表的先进先出队列,如果创建时没有指定此队列大小ÿ