一.多线程的基本概念
1.进程与线程
<1>进程
cpu从硬盘中读取一段程序到内存中,该执行程序的实例就叫做进程
<2>线程
线程是程序执行的最小单位,在一个进程中可以有多个不同的线程 同时执行
<3>为什么进程中需要线程
同一个应用程序中(进程),更好并行处理
2.多线程
<1>串行与并行
串行也就是单线程执行 代码执行效率非常低,代码从上向下执行;
并行就是多个线程并行一起执行,效率比较高。
<2>为什么要使用多线程
采用多线程的形式执行代码,目的就是为了提高程序的效率。
目的就是为了提高程序开发的效率
比如:现在一个项目只有一个程序员开发,需要开发功能模块会员模块、支付模块、订单模块。
<3>使用多线程一定提高效率吗
多线程 执行 需要同时执行
不一定,需要了解cpu调度的算法
就是先把前一个任务的 CPU 上下文(也就是 CPU 寄存器和程序计数器)保存起来,然后加载 新任务的上下文到这些寄存器和程序计数器,最后再跳转到程序计数器所指的新位置,运行新任务
线程池:和服务器cpu核数匹配 8核=8个线程 16核=16个线程,如果8核创建n多个线程,cpu的上下文调度会很频繁,反而影响程序的效率
I.CPU调度时间片
i.单核
单核的cpu上每次只能够执行一次线程,如果在单核的cpu上开启了多线程,则会发生对每个线程轮流执行 。 Cpu每次单个计算的时间成为一个cpu时间片,实际只有几十毫秒人为感觉好像是在多线程。
对于线程来说,存在等待cpu调度的时候 该线程的状态是为就绪状态,如果被cpu调度则该线程的状态为运行状态
当cpu转让执行其他的线程时,则该线程有变为就绪状态。
如果在单核的cpu之上开启了多线程,底层执行并不是真正意义上的多线程,而是通过cpu的上下文切换实现单线程cpu对线程之间的调度。
需要利用多核多线程性能。
ii.多核
每个线程都有自己独立的cpu执行,就不会出现cpu的上下文切换
II.程序计数器
cpu在执行线程一的时候,先执行了int i = 1,通过程序计数器保存起来,然后执行线程二,在切换回来执行线程一,就会通过刚才程序计数器对线程一的记录就会执行下一行int j = 3
III.cpu的上下文切换
上下文切换:从该线程执行切换到另外的线程 该线程---运行切换为就绪状态
如果生产环境中开启几百个或者上千个线程,而我们的服务器核数8核 16核 32核,这么多线程都会在我们这些cpu上做上下文切换
3.同步与异步
同步概念:就是代码从上向下执行。
异步的概念:单独分支执行 相互之间没有任何影响
举例:参考下面多线程的使用场景
二.多线程快速入门
1.多线程创建方式
<1>继承Thread类创建线程
//主线程挂掉并不会影响子线程的执行
public class ThreadDemo01 extends Thread {
@Override
public void run() {
System.out.println(Thread.currentThread().getName() + "子线程");
}
public static void main(String[] args) {
//main主线程执行
System.out.println(Thread.currentThread().getName());
// 创建一个线程
ThreadDemo01 threadDemo01 = new ThreadDemo01();
// 执行子线程,启动线程是start方法而不是run方法
threadDemo01.start();
}
}
<2>实现Runnable接口创建线程
public class ThreadDemo02 implements Runnable {
@Override
public void run() {
System.out.println(Thread.currentThread().getName() + ",我是子线程");
}
public static void main(String[] args) {
new Thread(new ThreadDemo02()).start();
}
}
<3>使用匿名内部类的形式创建线程
public class ThreadDemo02 implements Runnable {
@Override
public void run() {
System.out.println(Thread.currentThread().getName() + ",我是子线程");
}
public static void main(String[] args) {
//1.使用匿名内部类创建线程
new Thread(new Runnable() {
@Override
public void run() {
System.out.println(Thread.currentThread().getName() + ",我是子线程");
}
}).start();
}
}
<4>使用lambda表达式创建线程
public class ThreadDemo02 implements Runnable {
@Override
public void run() {
System.out.println(Thread.currentThread().getName() + ",我是子线程");
}
public static void main(String[] args) {
// 2.lambda表达式创建线程
new Thread(()->{
System.out.println(Thread.currentThread().getName() + ",我是子线程");
}).start();
//使用idea提供的快捷键优化代码,并指定线程的名称
new Thread(()->System.out.println(Thread.currentThread().getName() + ",我是子线程","线程名称:alex").start();
}
}
<5>使用Callable和Future创建线程
Callable和Future 线程可以获取到返回结果
<6>使用线程池例如用Executor框架
public class ThreadDemo03 {
public static void main(String[] args) {
ExecutorService executorService = Executors.newCachedThreadPool();
executorService.execute(new Runnable() {
@Override
public void run() {
System.out.println(Thread.currentThread().getName() + ">我是子线程<");
}
});
//executorService.execute(() -> System.out.println(Thread.currentThread().getName() + ">我是子线程<"));
}
}
<7>spring 提供的@Async异步注解 结合线程池
@Async 底层基于aop+自定义注解实现
I.使用单线程方式执行,同步的,响应了3s
II.使用多线程方式实现
使用spring提供的@Async异步注解就相当于开启了子线程来异步执行
III.使用多线程+注解方式执行一段代码,异步的,用了几十毫秒
i.主启动类开启异步注解支持
@SpringbootApplication
@EnableAsync
public class App{
public static void main(String[] args){
SpringbootApplication.run(App.class);
}
}
ii.编写线程方法加上异步注解@Async
@Component
@Slf4j
public class OrderManage {
@Async
public void asyncLog() {
try {
Thread.sleep(3000);
log.info("<2>");
} catch (Exception e) {
}
}
}
iii.编写接口
@RestController
@Slf4j
public class OrderService{
@Autowired
private OrderManage orderManage;
@RequestMapping("/addOrder")
//这个方法内有两个线程,一个是tomcat的,一个是自己创建的线程,同步执行,执行1,然后开启分支线程阻塞3s,执行3,最后执行2
public String addOrder() {
log.info("<1>");
//orderManage.asyncLog();
//它的作用就是下面这样的,开了一个新的线程
new Thread(){
@Override
public void run(){
orderManage.asyncLog();
}
}
log.info("<3>");
return "3";
}
}
iv.访问接口并测试
2.多线程的应用场景
客户端(移动App端/)开发
异步发送短信/发送邮件
将执行比较耗时的代码改用多线程异步执行; 可以提高接口的响应速度
异步写入日志(日志框架底层原理都是多线程)
多线程下载
<1>异步发送短信/发送邮件
<2>将执行比较耗时的代码改用多线程异步执行
举例:http请求就是同步的,如客户端发送一个http的请求给tomcat,客户端就会阻塞等待tomcat服务器响应给他一个请求,这个过程是同步的,可以使用异步的方式优化,把请求日志和响应日志两块耗时的代码交给多线程来异步执行,就可以提高代码的吞吐量
<3>手写日志异步框架(aop采集日志)
3.线程池(池化技术)
<1>7大参数
<2>自定义线程池
<3>项目中在哪用到了线程池
注意:实际开发,一定不是通过new的方式创建的,别人拿到接口,会攻击,所以一定是用的线程池来创建和维护线程
自己的项目中,用异步的方式,通过多线程实现了发短信,发邮件的功能;因为是springboot项目,所以用springboot整合了线程池来实现异步的发短信,邮件;
因为前期项目小,所以是这么做的,如果后期项目要做大,就会考虑用mq做异步
三.多线程线程安全问题
1.线程安全问题是什么
多线程同时对同一个全局变量做写的操作,可能会受到其他 线程的干扰,就会发生线程安全性问题。
//模拟两个线程抢一个全局变量count
public class ThreadCount implements Runnable {
private static Integer count = 100;
/**
*如何保证线程一直在运行状态->死循环控制
*/
@Override
public void run() {
while (count > 1) {
cal();
}
}
private void cal() {
try {
Thread.sleep(20);//释放了cpu的执行权
} catch (Exception e) {
}
count--;
System.out.println(Thread.currentThread().getName() + "," + count);
}
public static void main(String[] args) {
ThreadCount threadCount = new ThreadCount();
Thread thread1 = new Thread(threadCount);
Thread thread2 = new Thread(threadCount);
thread1.start();
thread2.start();
}
}
2.谈谈你对线程安全的理解
简单地说,在多个线程访问某个方法或者对象的时候,不管通过任何方式的调用,或者线程如何交替的去执行,在程序中,不做任何同步干预的情况下,这个方法或对象的执行或修改都能按照预期的结果来反馈,那么这个类我们认为它是线程安全的;实际上线程安全的具体表现,有三个方面,也就是原子性,有序性,可见性,(原子性指一个线程执行的一系列指令操作的时候,他应该是不可中断的,因为一旦中断,会出现前后不一致的问题,这个和数据库里的原子性是一样的,上下文切换是导致原子性的一个核心,而JVM里面提供了sybchronized同步关键字来解决这样一个原子性的问题;可见性是说在多线程环境下,由于读和写是发生在不同线程里的,有可能会出现某个线程对共享变量的修改,对其他线程不是实时可见的,导致可见性的原因有很多,比如像CPU的高速缓存,编译器的指令重排器;有序性指的是程序编写的指令顺序和cpu最终执行时的指令顺序可能会出现不一致的情况,可见性和有序性可以通过JVM提供的volitile关键字解决),在我看来,他们的本质都是工程师为了最大化提升cpu的利用率导致的,比如设计了缓存行,storebuffer等这种预读机制。这就是我的全部理解
3.加锁如何解决线程安全问题/线程如何实现同步
核心思想:当多个线程共享同一个全局变量时,将可能会发生线程安全的代码上锁,保证只有拿到锁的线程才可以执行,没有拿到锁的线程不可以执行,需要阻塞等待。
<1>错误示范-给整个方法加上锁
//这样做导致非公平锁,也就是两个线程只有一个能抢到锁,另一个会阻塞,也就是会变成单线程的执行
public class ThreadCount implements Runnable {
private static Integer count = 100;
@Override
public synchronizedvoid run() {
while (count > 1) {
cal();
}
}
private void cal() {
try {
Thread.sleep(20);//释放了cpu的执行权
} catch (Exception e) {
}
count--;
System.out.println(Thread.currentThread().getName() + "," + count);
}
public static void main(String[] args) {
ThreadCount threadCount = new ThreadCount();
Thread thread1 = new Thread(threadCount);
Thread thread2 = new Thread(threadCount);
thread1.start();
thread2.start();
}
}
<2>给发生线程安全问题的代码上锁
I.synchronized(对象锁){}
i.修饰代码块
指定加锁对象,对给定对象加锁,进入同步代码快前要获得 给定对象的锁;对象锁可以用this,也可以自已定义一个
public class ThreadCount implements Runnable {
private static Integer count = 100;
//private String lock = "lock";
@Override
public run() {
while (count > 1) {
cal();
}
}
private void void cal() {
synchronized(this){
try {
Thread.sleep(20);//释放了cpu的执行权
} catch (Exception e) {
}
count--;
System.out.println(Thread.currentThread().getName() + "," + count);
}
}
public static void main(String[] args) {
ThreadCount threadCount = new ThreadCount();
Thread thread1 = new Thread(threadCount);
Thread thread2 = new Thread(threadCount);
thread1.start();
thread2.start();
}
}
ii.修饰实例方法
作用于当前实例加锁,进入同步代码前要获得 当前实例 的锁;实例方法上默认加上synchronized 默认使用this锁。
public class ThreadCount implements Runnable {
private static Integer count = 100;
//private String lock = "lock";
@Override
public run() {
while (count > 1) {
cal();
}
}
private synchronized void void cal() {
try {
Thread.sleep(20);//释放了cpu的执行权
} catch (Exception e) {
}
count--;
System.out.println(Thread.currentThread().getName() + "," + count);
}
public static void main(String[] args) {
ThreadCount threadCount = new ThreadCount();
Thread thread1 = new Thread(threadCount);
Thread thread2 = new Thread(threadCount);
thread1.start();
thread2.start();
}
}
iii.修饰静态方法
作用于当前类对象(当前类.class)加锁,进入同步代码前要获得当前类对象的锁;默认使用当前类的类名.class 锁
public class ThreadCount implements Runnable {
private static Integer count = 100;
//private String lock = "lock";
@Override
public run() {
while (count > 1) {
cal();
}
}
private void void cal() {
synchronized(ThreadCount.class){
try {
Thread.sleep(20);//释放了cpu的执行权
} catch (Exception e) {
}
count--;
System.out.println(Thread.currentThread().getName() + "," + count);
}
}
public static void main(String[] args) {
ThreadCount threadCount = new ThreadCount();
Thread thread1 = new Thread(threadCount);
Thread thread2 = new Thread(threadCount);
thread1.start();
thread2.start();
}
}
iv.synchronized死锁问题
使用synchronized 需要注意 synchronized锁嵌套的问题 避免死锁的问题发生。
public class DeadlockThread implements Runnable {
private int count = 1;
private String lock = "lock";
@Override
public void run() {
while (true) {
count++;
if (count % 2 == 0) {
//一定要注意,多线程,下面的两个线程是同时执行下面的方法
// 线程1需要获取 lock 在获取 a方法this锁
// 线程2需要获取this 锁在 获取B方法lock锁
synchronized (lock) {
a();
}
} else {
synchronized (this) {
b();
}
}
}
}
public synchronized void a() {
System.out.println(Thread.currentThread().getName() + ",a方法...");
}
public void b() {
synchronized (lock) {
System.out.println(Thread.currentThread().getName() + ",b方法...");
}
}
public static void main(String[] args) {
DeadlockThread deadlockThread = new DeadlockThread();
Thread thread1 = new Thread(deadlockThread);
Thread thread2 = new Thread(deadlockThread);
thread1.start();
thread2.start();
}
}
v.诊断synchronized死锁问题
D:\path\jdk\jdk8\bin\jconsole.exe
vi.springmvc接口中如何使用synchronized锁
即如何在springmvc中保证线程安全问题
Spring MVC Controller默认是单例的 需要注意线程安全问题 单例的原因有二:
1、为了性能。
2、不需要多例。
@Scope(value = "prototype") 设置为多例
举例:如果使用默认的单例的bean对象,多个url请求访问同一个全局共享的变量,每次执行花费3s,那三次请求总共花费多长9s
@RestController
@Slf4j
//@Scope(value = "prototype") //bean改为原型,count就不会被全局共享,因为此时bean对象就成了多例而不是单例
public class CountService {
private int count = 0;
@RequestMapping("/count")
//synchronized加在这里,接口就成了单线程
public synchronized String count() {
try {
log.info(">count<" + count++);
try {
Thread.sleep(3000);
} catch (Exception e) {
}
} catch (Exception e) {
}
return "count";
}
}
II.使用Lock锁(在JUC并发包中)(乐观锁)
需要自己实现锁的升级过程,底层依赖于aqs+cas 实现
III.使用Threadlocal
需要注意内存泄漏的问题
IV.原子类CAS
非阻塞式
<3>lock和synchronized区别
这个问题我从两个个方面来回答,第一个,从功能角度来看,lock和synchronized都是java中用来解决线程安全的工具;第二,从性能上来看,synchronized是java中的同步关键字,而lock是JUC里面提供的一个接口,它包含了很多实现类,比如Reentranlock这样一个重入锁的实现;其中synchronized可以通过两种方式控制锁的力度,一种是修饰在代码块另一种是修饰在方法上,并且可以通过synchronized加锁对象的生命周期来控制锁的作用范围,比如锁对象是静态对象或者类对象,那么这个锁就是全局锁,如果锁是普通实例对象,那么锁的范围取决于实例对象的生命周期;lock锁的力度是通过它提供的方法如lock()和unlock()方法是决定的,而锁的作用域取决于lock实例的生命周期,所以,lock比synchronized的灵活性更高,lock可以自主的决定什么时候加锁以及释放锁,而synchronized的释放是被动的,它只有在同步代码块执行结束或代码出现异常才会释放;所以,性能上两者相差不大,但实现的过程会有一定的区别
4.不加锁如何解决线程安全问题
三个方面,第一个,所谓的线程安全问题,指的是多个线程同时对于某个共享资源的访问,导致的原子性,一致性,有序性的问题,这些问题会导致共享数据存在一个不可预见性,使得程序在执行过程中,会出现一些超出预期的结果;第二个,一般情况下,解决线程安全的方式,是增加同步锁,常见的是synchronized,lock等,由于导致线程安全问题的根本原因是多线程并行访问共享资源,对共享资源加锁之后,多个线程在访问共享资源的时候必须要先获取锁,而同步锁的特征是在同一时刻只允许一个线程访问这样一个资源,直到锁被释放,虽然这样的方式可以解决线程安全性的问题,但同时带来的是加锁和释放锁带来的性能开销,因为加锁会涉及到用户空间到内核空间的一个转换以及上下文切换;第三个,如何在性能和安全性上做一个平衡,这就引出了无锁并发的一个概念,一般来说会有以下几种方式,第一个是通过自选锁(cas),所谓自选锁是指线程在没有抢占锁的情况下,先自旋指定的次数去尝试获得锁,第二个是乐观锁,给每个数据增加一个版本号,一旦数据发生变化,则要去修改这个版本号,在java里面有一个叫cas的机制可以完成乐观锁的这么一个功能,第三个,在程序设计中呢,尽量去减少共享对象的一个使用,从业务上实现隔离避免并发。以上就是我的回答。
5.并发编程中cas机制的原理
cas是java中unsafe类的一个方法,它的全称是compareAndSwap,比较并交换的意思,它的主要功能是去保证最多线程的环境下,对于共享变量修改的一个原子性,我来举个例子,有一个成员变量叫state,它的默认值是0, 其中定义了一个方法叫dosomething(),这个方法的逻辑是先判断state是否为0 ,如果为0就修改为1,这个逻辑在单线程的环境下看起来是没有问题的,但是在多线程的环境下,会存在原子性的问题,因为这是一个典型的read、write的操作,一般情况下,我们会在dosomething()中加一个synchronized同步锁来解决原子性的问题,但是加同步锁会带来性能上的消耗,所以对于这一类的场景,我们可以使用cas机制来进行优化,在dosonmething()中调用了unsafe类里面的compareAndSwapInt()方法,来达到同样的目的,这个方法有四个参数,分别是当前对象实例,成员变量,state在内存地址中的偏移量,预期值0和期望修改之后的值1,cas机制会去比较state内存地址偏移量对应的值,和传入的预期值0是否相等,如果相等呢,就直接修改内存地址中state的值等于1,否则返回false,表示修改失败,这个过程它是一个原子的,不会存在任何线程安全的问题,caopareandswap是一个native方法, 实际上最终还是会面临同样的问题,就是先从内存地址读取state值,然后再去比较,最后再去修改,这个过程不管在什么层面去实现,都会存在原子性问题,所以再compareandswap的底层实现里面,如果是在多核cpu的环境下,会增加一个lock指令,来对缓存或者总线去加锁,从而去保证比较并交换两个操作的原子性, cas主要是用在一些并发场景里,比较典型的使用场景有两个,第一个是JUC里面的Atomic包里面的原子性实现,比如说AtomicInteger,AtomicLong等,第二个是实现多线程对于共享资源竞争的一个互斥特性,比如AQS,ConcurrentHashmap等等。以上就是我对这个问题的全部理解。
6.什么是死锁
四.多线程底层实现线程之间的通讯
1.synchronized方式
I.等待、通知机制
//wait(),notify()需要放到synchronized同步代码块中
public class My {
private Object ObjectLock = new Object();
public static void main(String[] args) throws InterruptedException {
new My().test();
}
public void test() throws InterruptedException {
new Thread(new Runnable() {
public void run() {
synchronized (ObjectLock){
System.out.println("<1>"+Thread.currentThread().getName());
try {
ObjectLock.wait();//this.wait()释放锁资源,同时当前线程会阻塞
} catch (InterruptedException e) {
e.printStackTrace();
}
System.out.println("<2>"+Thread.currentThread().getName());
}
}
}).start();
try {
Thread.sleep(2000); //主线程2s后唤醒子线程
synchronized (ObjectLock){
ObjectLock.notify();
}
}catch (Exception e){
e.printStackTrace();
}
}
}
II.使用wait(),notify()实现生产者与消费者
III.如何安全的停止一个线程
<1>interupt(线程中止)
打断正在阻塞的线程
打断正在运行的线程
<2>标志位(推荐)
正确的线程中止
2.Lock锁方式(jdk1.5之后)
public class Threadlock {
private Lock lock = new ReentrantLock();
private Condition condition =lock.newCondition();
public static void main(String[] args) {
Threadlock my2 = new Threadlock();
my2.test();
try {
Thread.sleep(2000); //主线程2s后唤醒子线程
}catch (Exception e){
e.printStackTrace();
}
my2.signal();
}
public void signal(){
try {
lock.lock();
condition.signal();
} catch (Exception e) {
e.printStackTrace();
} finally {
lock.unlock();
}
}
public void test() {
new Thread(new Runnable() {
public void run() {
try {
lock.lock();//获取锁
System.out.println("<1>");
condition.await();//.await()释放锁资源,同时当前线程会阻塞
System.out.println("<2>");
} catch (InterruptedException e) {
e.printStackTrace();
}finally {
lock.unlock();
}
}
}).start();
}
}
3.面试题
I.wait()和sleep()有什么区别
本质上他们都可以让我们创建的线程从运行状态变为等待状态,但是他们的区别在于,wait()在使用的过程是需要主动释放对象锁的,所以需要我们结合synchronized来使用;而sleep()使用的过程中不需要释放对象锁的
II.wait()和notify()为什么放在synchronized代码块里面
wait()和notify()是用来实现多个线程之间的一个协调,wait表示让线程进入一个阻塞状态,notify表示让阻塞的线程被唤醒,他们两个必然是成对儿出现的,如果一个线程被wait阻塞了,那么必然需要另外一个线程用notify去唤醒,从而去实现多个线程之间的通讯问题,在多线程中要实现通讯,除了管道流以外,只能通过共享变量的方式来实现,也就是说线程t1修改了共享变量s,线程t2获得共享变量修改后s的值,从而去完成数据之间的通讯,但是多线程具有并行执行的特征,所以再这种情况下,线程t2在访问共享变量s之前,必须要知道,线程t1已经修改过了这个共享变量s,否则就需要等待,同时在修改后,还需要把那个处于等待状态下的线程唤醒,所以再这种情况下,想要实现多线程之间的通信,就必须要有一个静态条件,去控制线程什么时候等待,什么时候唤醒,而synchronizded同步关键字就可以实现这样一个互斥的条件,也就是说在多线程通讯这样一个场景里面,参与通信的线程必须要去竞争这个共享变量的锁资源,才能够有资格对这个共享变量进行修改,修改完之后释放锁,让其他的线程去竞争这个共享变量的锁来获取修改之后的数据,这样就可以完成多线程之间的通讯问题,所以这也是为什么wait()和notify()为什么要放在synchronized代码块里面的原因,有了synchronized同步锁,就可以实现对于多个通信线程之间的互斥,从而实现条件等待和条件唤醒。另外,为了避免wait和notify的错误的使用,jdk强制的要求把他两写在同步代码块里面,否则会报异常。以上就是我对这个问题的全部理解(基于wait和notify的特性,适合实现消费者和生产者的一个模型,比如来实现连接池的就绪的等待和就绪之后的唤醒)
III.总结多线程之间如何通讯
1、syncrhoized加锁的线程的Object类的wait()/notify()/notifyAll()
2、ReentrantLock类加锁的线程的Condition类的await()/signal()/signalAll()
IV.线程池是如何实现线程复用的
线程池里面采用了一个生产者和消费者的模式去实现线程的一个复用,那么实际上就是通过一个中间容器来去解耦生产者和消费者的一个任务处理这样的一个过程,生产者不断地生产任务,保存到容器里面,消费者不断地从容器里消费任务。在线程池里面,因为要保证工作线程的一个重复使用,并且这些线程应该是有任务处理的时候执行,没有任务处理的时候等待,并且释放CPU的一个资源,所以他使用了阻塞队列来实现这样一个需求。提交任务到线程池的线程称为生产者线程,它不断地往线程池里传递任务,这些任务会保存到线程池的阻塞队列里面,然后线程池里的工作线程,不断地从阻塞队列获取任务去执行。基于阻塞队列这样的一个特性,使得阻塞队列里面如果任何任务的时候,这些工作线程就会阻塞等待,直到又有新的任务进来的时候,这些工作线程又会被继续唤醒,从而去达到线程复用的一个目的。以上就是我对这个问题的理解。
V.阻塞队列被异步消费如何保证顺序(阻塞队列的实现原理)
从三个方面来回答,首先,阻塞队列是一个符合FIFO特性的队列,也就是说存储进去的元素,它是符合先进先出的规则,其次,在阻塞队列里面,使用了一个condition的条件等待来维护了两个等待队列,一个是队列为空的时候,存储被阻塞的消费者,另一个是队列满了的时候,存储被阻塞的生产者,并且存储在等待队列里面的线程,它是一个符合FIFO特性的一个链表,最后对于阻塞队列的消费过程,有两种情况,第一种是阻塞队列里面已经包含了很多任务,这个时候启动多个消费者线程去消费的时候,它的有序性保证是通过加锁来实现的,也就是说每个消费者线程,去阻塞队列里面获取任务的时候,必须要先去获得排它锁,第二种,如果有多个消费者线程,因为阻塞队列里面没有任务而阻塞,这个时候,这些线程是按照FIFO的一个顺序,存储到了一个condition条件等待队列里面,当主策队列里面,开始有任务要处理的时候,这些阻塞的消费者线程,会严格按照FIFO的一个顺序被唤醒,从而保证了消费者对于任务的处理顺序。以上就是我对这个问题的全部理解
VI.java提供了几种线程池
java官方默认提供了五种不同的线程池的创建方式,下面我介绍一下每种线程池以及它的特点
这是一种不能存储任何元素的阻塞队列,也就是每提交一个任务到这个队列里面,都需要分配一个工作线程来处理,因为它的最大线程数没有限制,所以他可以用来处理大量的一个任务,另外每个工作线程又可以存活60s,所以使得这些工作线程可以缓存起来去应对更多任务的一个处理
这些线程池都是通过JDK里的一个工具叫Executors,线程池里的最终实现,是一个叫ThreadPoolExecutors,以上就是我对这个问题的全部理解