B站有视频教程:尚硅谷java面试题
评论区有老哥整理的笔记:笔记
- volatile是什么?
1.1. JMM之内存可见性
1.2. JMM之原子性
1.3. JMM之指令重排序
1.4. 单例模式双检锁 - 什么是CAS?
2.1. 乐观锁与悲观锁
2.2. CAS缺点 - 集合类线程安全问题
- Java中的锁
4.1. 公平与非公平锁
4.2. 可重入锁
4.3. 独占锁与共享锁
4.4. 自旋锁 - CountDownLatch/CyclicBarrier/Semaphore
5.1. CountDownLatch
5.2. CyclicBarrier
5.3. Semaphore - 阻塞队列
6.1. 阻塞队列的实现类
6.2. 核心方法
6.3. SychronousQueue
6.4. 生产者消费者问题 - 线程池
7.1. 线程池的作用
7.2. 线程池分类
7.3. ThreadPoolExecutor
7.4. 线程池的工作流程 - synchronized和lock有什么区别
1. volatile是什么?
volatile是jvm提供的轻量级的同步机制,它有三个特性:保证可见性、不保证原子性、禁止指令重排序。
1.1 JMM之内存可见性
JMM(java内存模型)是一组规范,定义了程序中各个变量(包括实例变量、静态变量以及构成数组的元素)的访问方式。
JMM关于同步的规定:
线程解锁前,必须把共享变量的值刷新回主内存
线程加锁前,必须读取主内存的最新值到自己的工作内存
加锁解锁是同一把锁
可见性:各个线程对主内存中共享变量的操作都是各个线程拷贝到自己的工作内存进行操作再写回去的。当多个线程访问同一个变量时,一个线程修改了这个变量的值,其他线程能够立即看到修改的值。
1.2 JMM之原子性
原子性:某个线程正在做某个具体业务时,中间不可以被加塞或者被分割,要么同时成功,要么同时失败,与事务的原子性类似。
自增操作不是原子性的,至少涉及一个读操作和写操作,用volatile修饰的变量自增时,在读取主存的值并自增再写回主存的过程中会有其它线程对共享变量进行操作。解决方法是用atomic包下的原子类。
1.3 JMM之指令重排序
有序性:在计算机执行程序时,为了提高性能,编译器和处理器常常会对指令做重排。分为3种:编译器优化的重排、指令并行的重排、内存系统的重排。
单线程环境里面确保程序最终执行结果和代码顺序执行的结果一致。
处理器在进行重排顺序是必须要考虑指令之间的数据依赖性。
多线程环境中线程交替执行,由于编译器优化重排的存在,两个线程中使用的变量能否保证一致性时无法确定的。volatile实现禁止指令重排,从而避免了多线程环境下程序出现乱序执行的现象。
1.4 单例模式双检锁
如下是单线程下懒汉式单例,在多线程下构造函数可能会被调用多次,可通过synchronize同步方法保证线程安全,但效率不高。
class Singleton{
private Singleton() {}
private static Singleton s ;
public static Singleton getInstance() {
if(s == null) {
s = new Singleton();
}
return s;
}
}
用synchronize同步代码块,在加锁前和后都判空(双检锁)能保证大部分情况下构造函数只执行一次,但因为有指令重排序的存在,不能保证线程安全。
public static Singleton getInstance() {
if (instance == null) {
synchronized (Singleton.class) {
if (instance == null) {
instance = new Singleton();
}
}
}
return instance;
}
可通过volatile禁止指令重排:
private static volatile Singleton s ;
2 什么是CAS?
CAS:比较并交换,比较当前工作内存中的值和主内存中的值,如果相同则执行规定操作,否则继续比较直到主内存和工作内存中的值一致为止,是乐观锁的一种实现。
CAS算法涉及到三个操作数:
需要读写的内存值 V。
进行比较的值 A。
要写入的新值 B。
当且仅当 V 的值等于 A 时,CAS通过原子方式用新值B来更新V的值(“比较+更新”整体是一个原子操作),否则不会执行任何操作。
AtomicInteger.compareAndSet(int expect, int update)
是CAS的实现,通过本地类unsafe实现。
public static void checkCAS(){
AtomicInteger atomicInteger = new AtomicInteger(5);
//期望值与内存值都为5,将内存值更新为2019,返回true
System.out.println(atomicInteger.compareAndSet(5, 2019) + "\t current data is " + atomicInteger.get());
//期望值5与内存值2019不相等,不更新内存值,返回false
System.out.println(atomicInteger.compareAndSet(5, 2014) + "\t current data is " + atomicInteger.get());
}
2.1 乐观锁与悲观锁
悲观锁:对于同一个数据的并发操作,悲观锁认为自己在使用数据的时候一定有别的线程来修改数据,因此在获取数据的时候会先加锁,确保数据不会被别的线程修改。Java中,synchronized关键字和Lock的实现类都是悲观锁。
悲观锁适合写操作多的场景,先加锁可以保证写操作时数据正确。
乐观锁:认为自己在使用数据时不会有别的线程修改数据,所以不会添加锁,只是在更新数据的时候去判断之前有没有别的线程更新了这个数据。如果这个数据没有被更新,当前线程将自己修改的数据成功写入。如果数据已经被其他线程更新,则根据不同的实现方式执行不同的操作。最常采用的是CAS算法,Java原子类中的递增操作就通过CAS自旋实现的。
乐观锁适合读操作多的场景,不加锁的特点能够使其读操作的性能大幅提升。
2.2 CAS缺点
- 循环时间长,开销大
例如getAndAddInt方法执行,有个do while循环,如果CAS失败,一直会进行尝试,如果CAS长时间不成功,可能会给CPU带来很大的开销。 - 只能保证一个共享变量的原子操作
对多个共享变量操作时,循环CAS就无法保证操作的原子性,这个时候就可以用锁来保证原子性 - ABA问题
CAS算法实现一个重要前提需要去取内存中某个时刻的数据并在当下时刻比较并替换,那么在这个时间差会导致数据的变化。
比如线程1从内存位置V取出A,线程2同时也从内存取出A,并且线程2进行一些操作将值改为B,然后线程2又将V位置数据改成A,这时线程1进行CAS操作发现内存中的值依然时A,然后线程1操作成功。
解决方法:使用原子引用+版本号(AtomicStampedReference)
public class ABADemo {
//原子引用类不带版本号
static AtomicReference<Integer> atomicReference = new AtomicReference<>(100);
//原子引用类带版本号: 解决aba
static AtomicStampedReference<Integer> atomicStampedReference = new AtomicStampedReference<>(100, 1);
public static void main(String[] args) {
System.out.println("=====以下是ABA问题的产生=====");
new Thread(() -> {
atomicReference.compareAndSet(100, 101);
atomicReference.compareAndSet(101, 100);
}, "Thread 1").start();
new Thread(() -> {
try {
//保证线程1完成一次ABA操作
TimeUnit.SECONDS.sleep(1);
} catch (InterruptedException e) {
e.printStackTrace();
}
System.out.println(atomicReference.compareAndSet(100, 2019) + "\t" + atomicReference.get());
}, "Thread 2").start();
try {
TimeUnit.SECONDS.sleep(2);
} catch (InterruptedException e) {
e.printStackTrace();
}
System.out.println("=====以下是ABA问题的解决=====");
new Thread(() -> {
int stamp = atomicStampedReference.getStamp();
System.out.println(Thread.currentThread().getName() + "\t第1次版本号" + stamp);
try {
TimeUnit.SECONDS.sleep(2);
} catch (InterruptedException e) {
e.printStackTrace();
}
atomicStampedReference.compareAndSet(100, 101, atomicStampedReference.getStamp(), atomicStampedReference.getStamp() + 1);
System.out.println(Thread.currentThread().getName() + "\t第2次版本号" + atomicStampedReference.getStamp());
atomicStampedReference.compareAndSet(101, 100, atomicStampedReference.getStamp(), atomicStampedReference.getStamp() + 1);
System.out.println(Thread.currentThread().getName() + "\t第3次版本号" + atomicStampedReference.getStamp());
}, "Thread 3").start();
new Thread(() -> {
int stamp = atomicStampedReference.getStamp();
System.out.println(Thread.currentThread().getName() + "\t第1次版本号" + stamp);
try {
TimeUnit.SECONDS.sleep(4);
} catch (InterruptedException e) {
e.printStackTrace();
}
boolean result = atomicStampedReference.compareAndSet(100, 2019, stamp, stamp + 1);
System.out.println(Thread.currentThread().getName() + "\t修改是否成功" + result + "\t当前最新实际版本号:" + atomicStampedReference.getStamp());
System.out.println(Thread.currentThread().getName() + "\t当前最新实际值:" + atomicStampedReference.getReference());
}, "Thread 4").start();
}
}
输出结果:
=====以下时ABA问题的产生=====
true 2019
=====以下时ABA问题的解决=====
Thread 3 第1次版本号1
Thread 4 第1次版本号1
Thread 3 第2次版本号2
Thread 3 第3次版本号3
Thread 4 修改是否成功false 当前最新实际版本号:3
Thread 4 当前最新实际值:100
3 集合类线程安全问题
多个线程同时修改java.util
下的集合类时,会报并发修改异常:java.util.ConcurrentModificationException
解决方法:
- 使用集合工具类的同步方法,可以将对应集合变成同步集合:
Collections.synchronizedxxx()
- 使用
java.util.concurrent
下的集合类:
2.1CopyOnWriteArrayList
替代ArrayList
CopyOnWrite容器即写时复制,往一个容器添加元素的时候,不直接往当前容器Object[]添加,而是先将当前容器Object[]进行copy,
复制出一个新的容器Object[] newElements,让后新的容器添加元素,添加完元素之后,再将原容器的引用指向新的容器setArray(newElements),
这样做可以对CopyOnWrite容器进行并发的读,而不需要加锁,因为当前容器不会添加任何元素,所以CopyOnWrite容器也是一种读写分离的思想,
读和写不同的容器
public boolean add(E e) {
final ReentrantLock lock = this.lock;
// 使用ReentrantLock 保证线程同步
lock.lock();
try {
Object[] elements = getArray();
int len = elements.length;
// 复制一个容器数组
Object[] newElements = Arrays.copyOf(elements, len + 1);
// 将新元素插入新容器数组
newElements[len] = e;
// 用新容器替换旧容器
setArray(newElements);
return true;
} finally {
lock.unlock();
}
}
2.2 `CopyOnWriteArraySet`替代`HashSet`
构造时还是构造了一个`CopyOnWriteArrayList`
private final CopyOnWriteArrayList<E> al;
/**
* Creates an empty set.
*/
public CopyOnWriteArraySet() {
al = new CopyOnWriteArrayList<E>();
}
2.3 `ConcurrentHashMap`替代`HashMap`
- 原理待补充
4 锁
4.1 公平与非公平锁
公平锁:多个线程按照申请锁的顺序来获取锁,每个线程在获取锁时,会先查看此锁维护的等待队列,如果为空,或者当前线程就是等待队列的第一个,就占有锁,否则就会加入到等待队列中。
非公平锁:多个线程并不是按照申请锁的顺序来获取锁,后申请的线程有可能先获得锁。
并发包中的ReentrantLock
默认是非公平锁,可通过构造函数中的布尔值设置为公平锁。
synchronize
也是非公平锁。
4.2 可重入锁
可重入锁:同一个线程在外层方法获取锁的时候,在进入内层方法会自动获取锁,也就是说,线程可以进入任何一个它已经拥有的锁所同步着的代码块,可重入锁可防止死锁。
ReentrantLock
/synchronized
就是一个典型的可重入锁
示例:
public static void main(String[] args) {
Phone phone = new Phone();
new Thread(() -> {
try {
phone.sendSMS();
} catch (Exception e) {
e.printStackTrace();
}
}, "Thread 1").start();
new Thread(() -> {
try {
phone.sendSMS();
} catch (Exception e) {
e.printStackTrace();
}
}, "Thread 2").start();
}
}
class Phone{
public synchronized void sendSMS()throws Exception{
System.out.println(Thread.currentThread().getName()+"\t -----invoked sendSMS()");
Thread.sleep(3000);
// synchronized 是可重入锁,所以进入sendEmail()时自动获取锁,打印出来的还是线程1的name
sendEmail();
}
public synchronized void sendEmail() throws Exception{
System.out.println(Thread.currentThread().getName()+"\t +++++invoked sendEmail()");
}
}
4.3 共享与独占锁
独占锁:指该锁一次只能被一个线程所持有,对ReentrantLock
和Synchronized
而言都是独占锁
共享锁:只该锁可被多个线程所持有
ReentrantReadWriteLock
其读锁是共享锁,写锁是独占锁
4.4 自旋锁
是指尝试获取锁的线程不会立即阻塞,而是采用循环的方式去尝试获取锁,这样的好处是减少线程上下文切换的消耗,缺点是循环会消耗CPU.
// 利用原子引用+CAS实现自旋锁
public class SpinLock {
private AtomicReference<Thread> cas = new AtomicReference<Thread>();
public void lock() {
Thread current = Thread.currentThread();
// 利用CAS
while (!cas.compareAndSet(null, current)) {
// DO nothing
}
}
public void unlock() {
Thread current = Thread.currentThread();
cas.compareAndSet(current, null);
}
}
5 CountDownLatch/CyclicBarrier/Semaphore
5.1 CountDownLatch
CountDownLatch: 它允许一个或多个线程一直等待,直到其他线程的操作执行完后再执行。利用它可以实现类似计数器的功能。例如,应用程序的主线程希望在负责启动框架服务的线程已经启动所有的框架服务之后再执行。
public static void countDownLatchTest() throws InterruptedException {
CountDownLatch countDownLatch = new CountDownLatch(6);
for (int i = 1; i <= 6; i++) {
new Thread(() -> {
System.out.println(Thread.currentThread().getName()+"\t被灭");
// 计数器-1
countDownLatch.countDown();
}, CountryEnum.forEach_CountryEnum(i).getRetMessage()).start();
}
// 唤醒主线程
countDownLatch.await();
System.out.println(Thread.currentThread().getName()+"\t=====秦统一");
}
5.2 CyclicBarrier
CyclicBarrier
: 可循环(Cyclic)使用的屏障。让一组线程到达一个屏障(也可叫同步点)时被阻塞,直到最后一个线程到达屏障时,屏障才会开门,所有被屏障拦截的线程才会继续干活,线程进入屏障通过CycliBarrier的await()方法。
public static void cyclicBarrierTest() {
CyclicBarrier cyclicBarrier = new CyclicBarrier(7, () -> {
System.out.println("====召唤神龙=====");
});
for (int i = 1; i <= 7; i++) {
final int tempInt = i;
new Thread(() -> {
System.out.println(Thread.currentThread().getName() + "\t收集到第" + tempInt + "颗龙珠");
try {
cyclicBarrier.await();
} catch (InterruptedException e) {
e.printStackTrace();
} catch (BrokenBarrierException e) {
e.printStackTrace();
}
}, "" + i).start();
}
}
5.3 Semaphore
Semaphore
(信号量):可以代替synchronize
和Lock
;信号量主要用于两个目的,一个是用于多个共享资源的互斥作用,另一个用于并发线程数的控制。
public static void main(String[] args) {
Semaphore semaphore = new Semaphore(3);//模拟三个停车位
for (int i = 1; i <= 6; i++) {//模拟6部汽车
new Thread(() -> {
try {
semaphore.acquire();
System.out.println(Thread.currentThread().getName() + "\t抢到车位");
try {
TimeUnit.SECONDS.sleep(3);//停车3s
} catch (InterruptedException e) {
e.printStackTrace();
}
System.out.println(Thread.currentThread().getName() + "\t停车3s后离开车位");
} catch (InterruptedException e) {
e.printStackTrace();
} finally {
semaphore.release();
}
}, "Car " + i).start();
}
}
6 阻塞队列
BlockingQueue
接口的实现类
- 当阻塞队列是空是,从队列中获取元素的操作会被阻塞;
- 当阻塞队列是满时,从队列中添加元素的操作会被阻塞;
- 试图从空的阻塞队列中获取元素的线程将会被阻塞,直到其他的线程往空的队列插入新的元素;
- 试图往已满的阻塞队列中添加新元素的线程同样会被阻塞,直到其他的线程从列中移除一个或者多个元素或者完全清空队列后使队列重新变得空闲起来并后续新增;
6.1 阻塞队列的实现类
ArrayBlockingQueue: 由数据结构组成的有界阻塞队列。
LinkedBlockingQueue: 由链表结构组成的有界(但大小默认值为Integer.MAX_VALUE)阻塞队列。
PriorityBlockingQueue: 支持优先级排序的无界阻塞队列。
DelayQueue: 使用优先级队列实现的延迟无界阻塞队列。
SychronousQueue: 不存储元素的阻塞队列,也即单个元素的队列。
LinkedTransferQueue: 由链表结构组成的无界阻塞队列。
LinkedBlockingDeque: 由链表结构组成的双向阻塞队列。
6.2 核心方法
方法类型 | 抛出异常 | 特殊值 | 阻塞 | 超时 |
---|---|---|---|---|
插入 | add(e) | offer(e) | put(e) | offer(e,time,unit) |
移除 | remove() | poll() | take | poll(time,unit) |
检查 | element() | peek() | 不可用 | 不可用 |
方法类型 | status |
---|---|
抛出异常 | 当阻塞队列满时,再往队列中add会抛IllegalStateException: Queue full ;当阻塞队列空时,在网队列里remove会抛NoSuchElementException |
特殊值 | 插入方法,成功true失败false;移除方法,成功返回出队列的元素,队列里没有就返回null |
一直阻塞 | 当阻塞队列满时,生产者线程继续往队列里put元素,队列会一直阻塞线程知道put数据或响应中断退出;当阻塞队列空时,消费者线程试图从队列take元素,队列会一直阻塞消费者线程知道队列可用。 |
超时退出 | 当阻塞队列满时,队列会阻塞生产者线程一定时间,超过限时后生产者线程会退出 |
public static void main(String[] args) throws InterruptedException {
// 设置容量为3
BlockingQueue<String> blockQueue = new ArrayBlockingQueue<>(3);
// 抛出异常api
System.out.println(blockQueue.add("z")); // true
System.out.println(blockQueue.add("y")); // true
System.out.println(blockQueue.add("k")); // true
System.out.println(blockQueue.add("z")); // 队列满抛出异常:java.lang.IllegalStateException: Queue full
System.out.println(blockQueue.remove()); // true
System.out.println(blockQueue.remove()); // true
System.out.println(blockQueue.remove()); // true
System.out.println(blockQueue.remove()); // 队列为空抛出异常:java.util.NoSuchElementException
// 返回特殊值api
System.out.println(blockQueue.offer("z")); // true
System.out.println(blockQueue.offer("y")); // true
System.out.println(blockQueue.offer("k")); // true
System.out.println(blockQueue.offer("z")); // false
System.out.println(blockQueue.poll()); // z
System.out.println(blockQueue.poll()); // y
System.out.println(blockQueue.poll()); // k
System.out.println(blockQueue.poll()); // null
// 阻塞
blockQueue.put("z");
blockQueue.put("y");
blockQueue.put("k");
System.out.println("wait----");
blockQueue.put("z"); // 阻塞
blockQueue.take();
blockQueue.take();
blockQueue.take();
blockQueue.take(); // 阻塞
// 超时退出
System.out.println(blockQueue.offer("z", 2l, TimeUnit.SECONDS)); // true
System.out.println(blockQueue.offer("y", 2l, TimeUnit.SECONDS)); // true
System.out.println(blockQueue.offer("k", 2l, TimeUnit.SECONDS)); // true
System.out.println(blockQueue.offer("k", 2l, TimeUnit.SECONDS)); // 2sec后返回false
System.out.println(blockQueue.poll()); // z
System.out.println(blockQueue.poll()); // y
System.out.println(blockQueue.poll()); // k
System.out.println(blockQueue.poll()); // 2sec后返回null
}
6.3 SychronousQueue
SynchronousQueue
没有容量,与其他BlockingQueue不同,SychronousQueue是一个不存储元素的BlockingQueue,每一个put操作必须要等待一个take操作,否则不能继续添加元素,反之亦然。
public static void main(String[] args) throws InterruptedException {
BlockingQueue<String> blockingQueue = new SynchronousQueue<>();
new Thread(() -> {
try {
System.out.println(Thread.currentThread().getName() + "\t put 1");
blockingQueue.put("1");
System.out.println(Thread.currentThread().getName() + "\t put 2");
blockingQueue.put("2");
System.out.println(Thread.currentThread().getName() + "\t put 3");
blockingQueue.put("3");
} catch (InterruptedException e) {
e.printStackTrace();
}
}, "AAA").start();
new Thread(() -> {
try {
TimeUnit.SECONDS.sleep(5);
System.out.println(Thread.currentThread().getName() + "\ttake " + blockingQueue.take());
TimeUnit.SECONDS.sleep(5);
System.out.println(Thread.currentThread().getName() + "\ttake " + blockingQueue.take());
TimeUnit.SECONDS.sleep(5);
System.out.println(Thread.currentThread().getName() + "\ttake " + blockingQueue.take());
} catch (InterruptedException e) {
e.printStackTrace();
}
}, "BBB").start();
}
6.4 生产者消费者问题
传统版:
/**
* 一个初始值为零的变量,两个线程对其交替操作,一个加1一个减1,来5轮
* 1. 线程 操作 资源类
* 2. 判断 干活 通知
* 3. 防止虚假唤起机制
*/
public class ProdConsumer_TraditionDemo {
public static void main(String[] args) {
ShareData shareData = new ShareData();
for (int i = 1; i <= 5; i++) {
new Thread(() -> {
try {
shareData.increment();
} catch (Exception e) {
e.printStackTrace();
}
}, "ProductorA " + i).start();
}
for (int i = 1; i <= 5; i++) {
new Thread(() -> {
try {
shareData.decrement();
} catch (Exception e) {
e.printStackTrace();
}
}, "ConsumerA " + i).start();
}
for (int i = 1; i <= 5; i++) {
new Thread(() -> {
try {
shareData.increment();
} catch (Exception e) {
e.printStackTrace();
}
}, "ProductorB " + i).start();
}
for (int i = 1; i <= 5; i++) {
new Thread(() -> {
try {
shareData.decrement();
} catch (Exception e) {
e.printStackTrace();
}
}, "ConsumerB " + i).start();
}
}
}
class ShareData {//资源类
private int number = 0;
private Lock lock = new ReentrantLock();
private Condition condition = lock.newCondition();
public void increment() throws Exception {
lock.lock();
try {
//1.判断
while (number != 0) {
//等待不能生产
condition.await();
}
//2.干活
number++;
System.out.println(Thread.currentThread().getName() + "\t" + number);
//3.通知
condition.signalAll();
} catch (Exception e) {
e.printStackTrace();
} finally {
lock.unlock();
}
}
public void decrement() throws Exception {
lock.lock();
try {
//1.判断
while (number == 0) {
//等待不能消费
condition.await();
}
//2.消费
number--;
System.out.println(Thread.currentThread().getName() + "\t" + number);
//3.通知
condition.signalAll();
} catch (Exception e) {
e.printStackTrace();
} finally {
lock.unlock();
}
}
}
阻塞队列版:
public class ProdConsumer_BlockQueueDemo {
public static void main(String[] args) {
MyResource myResource = new MyResource(new ArrayBlockingQueue<>(10));
new Thread(() -> {
System.out.println(Thread.currentThread().getName() + "\t生产线程启动");
try {
myResource.myProd();
} catch (Exception e) {
e.printStackTrace();
}
}, "Prod").start();
new Thread(() -> {
System.out.println(Thread.currentThread().getName() + "\t消费线程启动");
try {
myResource.myConsumer();
} catch (Exception e) {
e.printStackTrace();
}
}, "Consumer").start();
try { TimeUnit.SECONDS.sleep(5); } catch (InterruptedException e) { e.printStackTrace(); }
System.out.println("5s后main叫停,线程结束");
try {
myResource.stop();
} catch (Exception e) {
e.printStackTrace();
}
}
}
class MyResource {
private volatile boolean flag = true;//默认开启,进行生产+消费
private AtomicInteger atomicInteger = new AtomicInteger();
BlockingQueue<String> blockingQueue = null;
public MyResource(BlockingQueue<String> blockingQueue) {
this.blockingQueue = blockingQueue;
System.out.println(blockingQueue.getClass().getName());
}
public void myProd() throws Exception {
String data = null;
boolean retValue;
while (flag) {
data = atomicInteger.incrementAndGet() + "";
retValue = blockingQueue.offer(data, 2, TimeUnit.SECONDS);
if (retValue) {
System.out.println(Thread.currentThread().getName() + "\t插入队列" + data + "成功");
} else {
System.out.println(Thread.currentThread().getName() + "\t插入队列" + data + "失败");
}
TimeUnit.SECONDS.sleep(1);
}
System.out.println(Thread.currentThread().getName() + "\t大老板叫停了,flag=false,生产结束");
}
public void myConsumer() throws Exception {
String result = null;
while (flag) {
result = blockingQueue.poll(2, TimeUnit.SECONDS);
if (null == result || result.equalsIgnoreCase("")) {
flag = false;
System.out.println(Thread.currentThread().getName() + "\t超过2s没有取到蛋糕,消费退出");
System.out.println();
return;
}
System.out.println(Thread.currentThread().getName() + "\t消费队列" + result + "成功");
}
}
public void stop() throws Exception {
flag = false;
}
}
7 线程池
7.1 线程池的作用及好处
作用:主要是控制运行的线程的数量,处理过程中将任务放入队列,然后在线程创建后启动给这些任务,如果线程数量超过了最大数量,超出数量的线程排队等候,等其他线程执行完毕,再从队列中取出任务来执行。
好处:线程复用、控制最大并发数、管理线程
- 降低资源消耗,通过重复利用已创建的线程降低线程创建和销毁造成的消耗
- 提过响应速度。当任务到达时,任务可以不需要等到线程创建就能立即执行
- 提高线程的客观理想。线程是稀缺资源,如果无限制的创建,不仅会消耗系统资源,还会降低系统的稳定性,使用线程池可以进行统一的分配,调优和监控
7.2 线程池分类
Executors.newFixedThreadPool(int)
- 执行长期的任务,性能好很多
- 创建一个定长线程池,可控制线程最大并发数,超出的线程会在队列中等待。
- newFixedThreadPool创建的线程池corePoolSize和maximumPoolSize值是相等的,他使用的是
LinkedBlockingQueue
Executors.newSingleThreadExecutor()
- 一个任务一个任务执行的场景
- 创建一个单线程话的线程池,他只会用唯一的工作线程来执行任务,保证所有任务按照指定顺序执行
- newSingleThreadExecutor将corePoolSize和maximumPoolSize都设置为1,使用
LinkedBlockingQueue
Executors.newCachedThreadPool()
- 执行很多短期异步的小程序或负载较轻的服务器
- 创建一个可缓存线程池,如果线程池长度超过处理需要,可灵活回收空闲线程,若无可回收,则新建线程。
- newCachedThreadPool将corePoolSize设置为0,将maximumPoolSize设置为Integer.MAX_VALUE,使用的
SynchronousQueue
,也就是说来了任务就创建线程运行,当线程空闲超过60s,就销毁线程。
以上三个线程池底层都是通过创建ThreadPoolExecutor
对象实现的。
7.3 ThreadPoolExecutor
public ThreadPoolExecutor(int corePoolSize,
int maximumPoolSize,
long keepAliveTime,
TimeUnit unit,
BlockingQueue<Runnable> workQueue,
ThreadFactory threadFactory,
RejectedExecutionHandler handler)
corePoolSize:线程池中常驻核心线程数
- 在创建了线程池后,当有请求任务来之后,就会安排池中的线程去执行请求任务
- 当线程池的线程数达到corePoolSize后,就会把到达的任务放到缓存队列当中
maximumPoolSize:线程池能够容纳同时执行的最大线程数,必须大于等于1
keepAliveTime:多余的空闲线程的存活时间
当前线程池数量超过corePoolSize时,档口空闲时间达到keepAliveTime值时,多余空闲线程会被销毁到只剩下corePoolSize个线程为止
unit:keepAliveTime的单位
workQueue:任务队列,被提交但尚未被执行的任务
threadFactory:表示生成线程池中工作线程的线程工厂,用于创建线程一般用默认的即可
handler:拒绝策略,表示当队列满了并且工作线程大于等于线程池的最大线程数(maximumPoolSize)时如何来拒绝请求执行的runable的策略
7.4 线程池的工作流程
-
在创建了线程池之后,等待提交过来的任务请求。
-
当调用execute()方法添加一个请求任务时,线程池会做出如下判断
2.1 如果正在运行的线程数量小于corePoolSize,那么马上船舰线程运行这个任务;
2.2 如果正在运行的线程数量大于或等于corePoolSize,那么将这个任务放入队列;
2.3 如果此时队列满了且运行的线程数小于maximumPoolSize,那么还是要创建非核心线程立刻运行此任务
2.4 如果队列满了且正在运行的线程数量大于或等于maxmumPoolSize,那么启动饱和拒绝策略来执行 -
当一个线程完成任务时,他会从队列中却下一个任务来执行
-
当一个线程无事可做超过一定的时间(keepAliveTime)时,线程池会判断:
-
如果当前运行的线程数大于corePoolSize,那么这个线程会被停掉;所以线程池的所有任务完成后他最大会收缩到corePoolSize的大小
8 synchronized和lock有什么区别
原始构成
synchronized
时关键字属于jvm monitorenter,底层是通过monitor对象来完成,其实wait/notify等方法也依赖于monitor对象只有在同步或方法中才能掉wait/notify等方法 monitorexitLock
是具体类,是api层面的锁(java.util.)
使用方法sychronized
不需要用户取手动释放锁,当synchronized代码执行完后系统会自动让线程释放对锁的占用ReentrantLock
则需要用户去手动释放锁若没有主动释放锁,就有可能导致出现死锁现象,需要lock()
和unlock()
方法配合try/finally语句块来完成
等待是否可中断synchronized
不可中断,除非抛出异常或者正常运行完成ReentrantLock
可中断,设置超时方法tryLock(long timeout, TimeUnit unit)
,或者lockInterruptibly()
放代码块中,调用interrupt()
方法可中断。
加锁是否公平synchronized
非公平锁ReentrantLock
两者都可以,默认公平锁,构造方法可以传入boolean值,true为公平锁,false为非公平锁
锁绑定多个条件Conditionsynchronized
没有ReentrantLock
用来实现分组唤醒需要要唤醒的线程们,可以精确唤醒,而不是像synchronized
要么随机唤醒一个线程要么唤醒全部线程。