![在这里插入图片描述](https://img-blog.csdnimg.cn/0c78fe7f574846f1bc6e3eeb5f58301a.jpeg)
一、几种多线程的创建方式
题外话:线程生命周期(新建、就绪、运行、阻塞和死亡)
官方承认的只有两种,Thread和Runnable,其他都是设计模式。
1.继承Thread类创建线程类
(1)定义Thread类的子类,并重写该类的run方法,该run方法的方法体就代表了线程要完成的任务。因此把run()方法称为执行体。
(2)创建Thread子类的实例,即创建了线程对象。
(3)调用线程对象的start()方法来启动该线程。
2.通过Runnable接口创建线程类
(1)定义runnable接口的实现类,并重写该接口的run()方法,该run()方法的方法体同样是该线程的线程执行体。
(2)创建 Runnable实现类的实例,并依此实例作为Thread的target来创建Thread对象,该Thread对象才是真正的线程对象。
(3)调用线程对象的start()方法来启动该线程。
3.通过Callable和Future创建线程
(1)创建Callable接口的实现类,并实现call()方法,该call()方法将作为线程执行体,并且有返回值。
(2)创建Callable实现类的实例,使用FutureTask类来包装Callable对象,该FutureTask对象封装了该Callable对象的call()方法的返回值。注释:FutureTask是一个包装器,它通过接受Callable来创建,它同时实现了Future和Runnable接口。
(3)使用FutureTask对象作为Thread对象的target创建并启动新线程。
(4)调用FutureTask对象的get()方法来获得子线程执行结束后的返回值
4.Excutor线程池
线程池提供了一个线程队列,队列中保存所有等待状态的线程,避免创建与销毁额外开销,提高了响应速度。
4.1.不推荐,Executors自动创建
1) FixedThreadPool和SingleThreadPool
允许的请求队列长度为Integer.MAX_VALUE,可能会堆积大量的请求,从而导致OOM(内存不足OutOfMemoryError)
2) CachedThreadPool
允许的创建线程数量为Integer.MAX_VALUE,可能会创建大量的线程,从而导致OOM
4.2.推荐,手动创建ThreadPoolExcutor
不允许使用Executors自动创建,而是通过ThreadPoolExecutor的方式,这样处理可以让程序员更加明确线程池的运行规则,规避资源耗尽的风险。
举例:
4.2.1.核心线程数如何设置?
- CPU密集型:CPU核数 + 1
- IO密集型:
等待时间多:(线程等待时间/CPU时间 + 1) * CPU核数
等待时间少:CPU核数 * 2 - 混合型:划分为CPU和IO类型,用不同的线程池
5.创建线程的三种方式的对比
5.1.采用实现Runnable、Callable接口的方式创见多线程时
优势是:
(1)线程类只是实现了Runnable接口或Callable接口,还可以继承其他类。
(2)在这种方式下,多个线程可以共享同一个target(无特殊含义,就是Runnable、Callable的变量名,一般叫做target)对象,所以非常适合多个相同线程来处理同一份资源的情况,从而可以将CPU、代码和数据分开,形成清晰的模型,较好地体现了面向对象的思想。
劣势是:
(1)编程稍微复杂,如果要访问当前线程,则必须使用Thread.currentThread()方法。
5.2.使用继承Thread类的方式创建多线程时
优势是:
(1)编写简单,如果需要访问当前线程,则无需使用Thread.currentThread()方法,直接使用this即可获得当前线程。
劣势是:
(1) 线程类已经继承了Thread类,所以不能再继承其他父类。
二、多线程之间如何通信?
两种模式:对象共享、消息传递
对象共享:volatile,多个线程监听同一个对象
消息传递:Object的wait与notify;LockSupprot的park与unpark
注意:
wait会释放锁;notify不会释放锁,当前逻辑执行完成才会唤醒
wait最好放在while循环中,阻塞前后都会做条件判断
unpark、park忽略顺序,都可以唤醒,原理获取凭证(只有一个)
代码实现,参考地址https://blog.csdn.net/zhaomengxia123/article/details/126598141
1.LockSupprot阻塞与唤醒顺序没有要求吗?为什么连续唤醒两次,阻塞两次,最后仍是阻塞?
因为unpark获取了一个凭证,之后再调用park方法,就可以名正言顺的凭证消费,故不会阻塞。
因为凭证的数量最多为1,连续调用两次unpark和调用一次unpark效果一样,只会得到一个凭证;而调用两次park却需要消费两个凭证,证不够,不能放行。
三、互斥锁、信号量、多线程等待机制
1) 互斥锁:一个资源只能被一个线程访问,ReentranLock使用tryLock()获取锁,失败返回false;unlock()释放锁
2) 信号量:指定个线程同时访问某个资源。相当于计数器,获取这个资源-1. 创建new Semaphore(n),semaphore.acquire()获取许可,semaphore.release()释放许可
3) 等待机制:等待指定个线程达到某些条件后,进行下一步,类似开会。创建new CountDownLatch(n),countDown()计数器减一,await()主线程唤醒。
代码实现,参考地址https://www.php1.cn/detail/XiangJie_java_Zh_07a2d934.html