线程中的生消模式和线程池
发布日期:2024-04-25 21:47:13 浏览次数:1 分类:技术文章

本文共 13781 字,大约阅读时间需要 45 分钟。

守护线程:

守护线程是用来守护非守护线程的。在后台为其他线程服务的。

如果非守护线程消亡,守护线程随之消亡。

isDaemon();判断一个线程是否为守护线程setDaemon();设置一个线程为守护线程

死锁:

死锁:指的是两个或两个以上的线程在执行过程中,由于竞争资源或者彼此通信而造成的一种阻塞现象,若无外力作用,它们都将无法推进下去。这些永远在互相等待的进程称为死锁线程。

手写一个死锁案例:

class DeaLock implements Runnable{
private boolean flag;//标记属性 private Object obj1;//锁住的对象 private Object obj2;//锁住的对象 public DeadLock (boolean flag,Object obj1,Object obj2){
this.flag = flag; this.obj1 = obj1; this.obj2 = obj2; } @Override public void run() {
//true if (flag) {
//如果设置为true,就让线程1进入到if语句中 synchronized (obj1) {
//锁住的是obj1对象 //线程1持有obj1锁 System.out.println(Thread.currentThread().getName() + "拿到了锁1"); try {
Thread.sleep(1000); } catch (InterruptedException e) {
e.printStackTrace(); } System.out.println("等待锁2的释......"); //我想在线程1中去使用线程2中的那个锁2 obj2 //线程1里面想用obj2锁对象 //也走不下去了 //线程1也没有释放obj1 synchronized (obj2) {
System.out.println("123"); System.out.println(Thread.currentThread().getName() + "拿到了锁1"); } } } if (!flag) {
//如果设置为false,就让线程2进入到if语句中 synchronized (obj2) {
//锁住的是obj2对象 //线程2持有obj2这个锁 System.out.println(Thread.currentThread().getName() + "拿到了锁2"); try {
Thread.sleep(1000); } catch (InterruptedException e) {
e.printStackTrace(); } System.out.println("等待锁1的释......"); //只有obj1释放掉以后,才能在线程2中对obj1加锁 //想一个问题,如果obj1锁对象没有被释放,那么下面这个代码 //线程2中去锁obj1 //在这等着呢 往下走不下去了 线程2没有释放obj2对象 synchronized (obj1) {
System.out.println("456"); System.out.println(Thread.currentThread().getName() + "拿到了锁1"); } } }}

线程的生命周期【重点】

1.新建:当一个Thread类或者子类的对象被声明并创建时,新生的线程对象属于新建状态。

2.可运行状态(就绪):处于新建状态的线程执行start()方法后,进入线程队列等待cpu时间片,此时具备了运行的状态只是还没有分配到cpu资源

3.运行:分配到cpu资源进入运行状态,run()方法定义了线程的操作。

4.阻塞:在某种特殊情况下,被人为挂起或执行输入输出操作时,让出cpu并临时终止自己的操作,进入阻塞状态

5.死亡:当线程执行玩自己的操作或提前被强制行的终止或出现异常导致结束,会进入死亡状态。

和线程相关的Object类下面的方法:

public final void wait()     throws InterruptedException    notify();唤醒正在等待对象的单个线程    notifyAll();唤醒正在等待对象的所有线程。

总结:最少两个线程,其中一个线程中使用 对象.wait(),这个线程就会阻塞代码不会往下执行。另外一个线程使用 对象.notify()去唤醒那个阻塞的线程。他们用的是同一个对象。

案例:

package com.qfen.wss;//创建这个类的目的,就是实例化出来对象,然后拿这个对象//调用wait方法和notify方法//wait方法和notify方法是object对象的方法class Message {
private String message; public Message(String message) {
this.message = message; } public String getMessage() {
return message; } public void setMessage(String message) {
this.message = message; } @Override public String toString() {
return "Message{" + "message='" + message + '\'' + '}'; }}//等待线程class WaiterThread implements Runnable {
private Message msg; public WaiterThread(Message msg) {
this.msg = msg; } @Override public void run() {
String name = Thread.currentThread().getName();//先获取当前线程名字 System.out.println(name + "等待唤醒时间:" + System.currentTimeMillis()); synchronized (msg) {
//对对像上锁 try {
msg.wait();//代码走到这,就不往下走了 //在另外一个线程中去调用notify方法去唤醒它让代码执行下去 } catch (InterruptedException e) {
e.printStackTrace(); } System.out.println("哈哈哈"); System.out.println(name + "被唤醒的时间:" + System.currentTimeMillis()); } }}//唤醒线程class NotifyThread implements Runnable {
//也要用同一个对象是WaiterThread线程中同一个对象调用notify()方法 private Message msg; public NotifyThread(Message msg) {
this.msg = msg; } @Override public void run() {
try {
//不能先让唤醒线程执行,一旦唤醒线程先执行了,等待线程就醒不过来了 Thread.sleep(1000); } catch (InterruptedException e) {
e.printStackTrace(); } String name = Thread.currentThread().getName(); System.out.println(name + "开始唤醒等待线程"); synchronized (msg) {
msg.setMessage("我是现在的"); //msg.notify();//随机唤醒一个线程 msg.notifyAll();//唤醒所有线程 } }}public class Lian {
public static void main(String[] args) {
Message mas = new Message("我是原来的"); WaiterThread waiterThread = new WaiterThread(mas); NotifyThread notifyThread = new NotifyThread(mas); new Thread(waiterThread, "等待线程1").start(); new Thread(waiterThread, "等待线程2").start(); new Thread(waiterThread, "等待线程3").start(); new Thread(notifyThread, "唤醒线程").start(); }}
Thread类join方法join()方法,join()方法底层是通过wait()方法实现的。让"主线程"等待,一直等到其他线程不再活动为止,然后"主线程"再执行join在英语中是“加入”的意思,join()方法要做的事就是,当有新的线程加入时,主线程会进入等待状态,一直到调用join()方法的线程执行结束为止。面试题:	如何先让所有的子线程执行,最后再执行主线程,咋解决?		join!!!

生产者消费者模式:【重点】

思路:

如果产品需要生产的话,消费者进入到阻塞状态 wait

如果产品不需要生产的话,消费者直接购买

package com.qfen.wss;//为啥要新建这类Goods?//因为两个线程要通信。这个Goods就是通信的桥梁!!!//goods.wait()  消费者等待//goods.notoify()  生产者唤醒消费者class Goods {
private String name;//商品名字 private double price;//商品价格 private boolean isProduct;// //isProduct是否有这个商品, true 没有这个产品需要生产 //false 有这个产品,不需要生产 //有参构造 public Goods(String name, double price, boolean isProduct) {
this.name = name; this.price = price; this.isProduct = isProduct; } public String getName() {
return name; } public void setName(String name) {
this.name = name; } public double getPrice() {
return price; } public void setPrice(double price) {
this.price = price; } public boolean isProduct() {
return isProduct; } public void setProduct(boolean product) {
isProduct = product; } @Override public String toString() {
return "Goods{" + "name='" + name + '\'' + ", price=" + price + ", isProduct=" + isProduct + '}'; }}//接下来搞两个线程?一个消费者线程 一个是生产者线程class Customer implements Runnable {
//为啥要定义这个goods变量? 因为两个线程要共享同一个资源!!! private Goods goods; public Customer(Goods goods) {
this.goods = goods; } @Override public void run() {
//写业务逻辑 while (true) {
//一直消费 synchronized (goods) {
//goods.isProduct true 需要生产,没有商品 false 不需要生产 if (!goods.isProduct()) {
//不需要生产的,消费者直接购买 System.out.println("消费者购买了:" + goods.getName() + ",价格为:" + goods.getPrice()); //购买完以后 商品没有了 将isProduct这个变量修改为true goods.setProduct(true); //消费者在购买的时候,生产者等待 //唤醒生产者去生产车了 goods.notify(); } else {
//没商品消费者进入到阻塞状态!!! try {
goods.wait(); } catch (InterruptedException e) {
e.printStackTrace(); } } } } }}class Producter implements Runnable {
//为啥要定义这个goods变量? private Goods goods; public Producter(Goods goods) {
this.goods = goods; } @Override public void run() {
int count = 0; //生产者 while (true) {
//你一直消费,我一直生产 synchronized (goods) {
if (goods.isProduct()) {
//true 需要生产的!! //造车,就是赋值 对goods对象进行赋值 //奇数造一种车, 偶数造另外一种车 if (count % 2 == 0) {
//偶数 goods.setName("商务车"); goods.setPrice(78.9); } else {
//奇数 goods.setName("小轿车"); goods.setPrice(12.9); } //生产一辆车,一定要记得有车了 //标记就改为 false 就证明不需要再生产了 goods.setProduct(false); System.out.println("生产者生产了:" + goods.getName() + ",价格为:" + goods.getPrice()); count++; //生产完以后,唤醒消费者。让消费者去提车 goods.notify(); } else {
//不需要生产 有货,生产着歇着,阻塞 try {
goods.wait(); } catch (InterruptedException e) {
e.printStackTrace(); } } } } }}public class Demo1 {
public static void main(String[] args) {
Goods goods = new Goods("小汽车", 12.9, false); Producter producter = new Producter(goods); new Thread(producter).start(); Customer customer = new Customer(goods); new Thread(customer).start(); /** * 谁先抢到线程?消费者?还是生产者? * //如果是消费者抢到执行权,不用说!!!直接打印消费者购买了东风 * //如果生产者抢到执行权,生产者wait,那就意味着必须去执行消费者线程 * 消费者购买了:小汽车,价格为:67.8 * //此时isProduct是true 需要时生产 * 还是消费者和生产者抢这个执行权 * //假如生产者抢到了 就会打印生产者生产了啥。 * //假如消费者抢到了执行权,消费者进入倒阻塞状态!!! * //消费者进入倒阻塞,那么肯定生产者得执行了 * 生产者生产了:商务车,价格为:78.9 * * 还是两个线程抢这个执行权 * 消费者购买了:商务车,价格为:78.9 * 生产者生产了:小汽车,价格为:12.9 * 消费者购买了:小汽车,价格为:12.9 * 生产者生产了:商务车,价格为:78.9 * 消费者购买了:商务车,价格为:78.9 * 生产者生产了:小汽车,价格为:12.9 * 消费者购买了:小汽车,价格为:12.9 * 生产者生产了:商务车,价格为:78.9 * 消费者购买了:商务车,价格为:78.9 */ }}

线程池 Pool:

线程池:一个容纳了多个线程的容器,其中的线程可以反复的使用。

线程池是一种多线程处理形式,处理过程中将任务添加到队列,然后在创建线程后自动启动这些任务。

在这里插入图片描述

总体来说线程可以分为两类:

通过ThreadPoolExecutor 手动创建线程池

通过Executors 执行器自动创建线程池

创建线程的7种实现方法:

1.Executors.newCachedThreadPool:创建一个可缓存的线程池,若线程数超过处理所需,缓存一段时间后会回收,若线程数不够,则新建线程。2.Executors.newSingleThreadExecutor:创建单个线程数的线程池,它可以保证先进先出的执行顺序。3.Executors.newScheduledThreadPool:创建一个可以执行延迟任务的线程池。4.Executors.newSingleThreadScheduledExecutor:创建一个单线程的可以执行延迟任务的线程池。5.Executors.newWorkStealingPool:创建一个抢占式执行的线程池(任务执行顺序不确定)【JDK 1.8 添加】。6.ThreadPoolExecutor:手动创建线程池的方式,它创建时最多可以设置 7 个参数。

7.ThreadPoolExecutor【重要】

线程池的创建推荐使用最后一种 ThreadPoolExecutor 的方式来创建,相比于其他创建线程池的优势在于,它可以通过参数来控制最大任务数和拒绝策略,让线程池的执行更加透明和可控的风险。创建时最多提供 7 个参数可供设置。
ThreadPoolExecutor的创建主要参数有7个,接下来将进行一一介绍。最大线程数(MaximumPoolSize)和核心线程数(CorePoolSize)最大线程数(MaximumPoolSize):线程池运行的最大线程数量,受属性CAPACITY的限制,最大为(2^29)-1(约5亿)核心线程数(CorePoolSize):线程池中保持最小活动数的线程数量,并且不允许超时,除非调用allowCoreThreadTimeOut方法,这个时候最小值是0。当线程池线程数量小于核心线程数时,一个新的任务请求被提交上来时,不管其他线程是否处于空闲状态,都会新建一个线程来处理这个请求。如果在运行的线程数数量超过核心线程数但是小于最大线程数,并且工作队列已满,将创建一个线程处理这个请求。默认情况下,当一个任务请求时,核心线程数才会被创建和启动,但是也可以通过prestartCoreThread启动一个核心线程或者prestartAllCoreThread启动所有核心线程。创建新的线程(ThreadFactory)ThreadFactory用来创建线程。如果没有指定ThreadFactory的话,默认会使用Executors#defaultThreadFactory 来创建线程,并且这些线程都是在同一个ThreadGroup并且都是非守护线程状态(non-daemon status)并且拥有相同的优先级(NORM_PRIORITY)。 如果指定了ThreadFactory 可以修改ThreadGroup和线程的名称、守护状态、优先级等。ThreadFactory如果调用newThread(Runnable r)方法返回null则创建线程失败,线程池会继续运行但可能不会执行任何任务。线程应该拥有"modifyThread"权限,如果工作线程或者其他线程没有拥有这个权限,服务可能会降级,配置更改可能不会及时生效,关闭线程池可能保持在可能终止但未完成的状态。存活时间(Keep-alive times)存活时间(Keep-alive times):空闲线程等待工作的超时时间(以纳秒为单位)如果当前线程池中的线程数超过了核心线程数,超出的部分线程如果空闲的时长大于存活时长,那么他们将会被终止运行。当线程池不被频繁使用的时候,这提供了一种减少资源消耗的方法。存活时间可以通过setKeepAliveTime(long, TimeUnit)进行修改,使用 setKeepAliveTime(Long.MAX_VALUE, NANOSECONDS)有效地禁止空闲线程在关闭之前终止。默认情况下,存活策略只适用于当前线程数超过核心线程数的情况下。但是使用方法allowCoreThreadTimeOut(boolean)也可以将这个超时策略应用到核心线程,只要keepAliveTime值不为零。时间单位(TimeUnit)TimeUnit 是存活时间的单位。阻塞队列(BlockingQueue)任何实现了BlockingQueue接口的实现类都可以用来传输和保存提交的任务,阻塞队列的使用和线程池大小相关:如果运行的线程少于核心线程数, Executor总是倾向于添加一个新线程而不是排队如果核心线程数(5个)或更多线程正在运行(不超过最大线程数)(10个),Executor总是倾向于排队请求(从5个核心线程中去拿线程排队拿),而不是添加一个新线程如果没有达到最大线程数并且队列未满,将创建新的线程执行任务,如果线程数大于最大线程数,任务将会被拒绝三种排队策略直接传递工作队列的一个很好的默认选择是 SynchronousQueue,它将任务交给线程而不用其他方式持有它们。一个新的任务尝试排队时,如果没有可供使用的线程运行它时将会创建一个新的线程。该策略避免了锁定处理可能具有内部依赖关系的请求集,直接传递通常需要无界的最大线程池来避免新的任务提交。这反过来又承认了当命令的平均到达速度快于它们的处理速度时,线程无限增长的可能性。无界队列无界队列是一个没有预定义容量的队列,使用无界队列例如LinkedBlockingQueue将导致新任务一直在等待,当核心线程数(5个)的线程处于工作状态时。因此,不会有超过核心线程数的线程被创建(),也就是说最大线程数是不起作用的。当任务之间互相独立,互不影响的时候这个选择可能是挺合适的。例如,在web服务器中,这种队列在消除短暂的高并发方面很有作用,它允许无界队列增长的平均速度比处理的平均速度快。有界队列无界队列例如ArrayBlockingQueue,它能在有限的最大线程数内防止资源耗尽,但是它也更难调整和控制。队列的大小和最大线程数可以互相替换:使用更大的队列数量和小的线程池数量能够最小化CPU的使用、系统资源和上下文切换的开销,但也人为的导致了低吞吐量。如果一个任务频繁的阻塞,例如频繁I/O,系统更多的时间是在频繁的调度而不是运行任务。使用小的队列通常需要大的线程池数量,这会让CPU更能充分利用,但是也会遇到不可接受的调度开销,也会降低吞吐量。拒绝任务在调用execute(Runnable)提交任务时,在Executor已经关闭或者有界队列的最大线程数和队列满的情况下任务会被拒绝。不论在什么情况下,execute方法调用RejectedExecutionHandler#rejectedExecution(Runnable, ThreadPoolExecutor)任务都会根据拒绝策略被拒绝。四种拒绝策略ThreadPoolExecutor预定义了四种拒绝策略:ThreadPoolExecutor.AbortPolicy,默认的拒绝策略,简单粗暴,拒绝的时候直接抛RejectedExecutionException异常ThreadPoolExecutor.CallerRunsPolicy,由调用者执行自身execute方法来运行提交进来的任务,从名字CallerRuns(调用者运行)中就可以看出。它会提供一个简单的反馈控制机制,这种策略将降低新任务被提交上来的速度。ThreadPoolExecutor.DiscardPolicy,也很简单粗暴,直接丢弃任务,不抛异常。ThreadPoolExecutor.DiscardOldestPolicy,DiscardOldest丢弃最早的任务,在队列头部也就是最新进入队列的任务会被丢弃,然后尝试提交新任务,如果提交失败会继续重复以上步骤。也可以自己实现RejectedExecutionHandler接口,并重写rejectedExecution方法来自定义拒绝策略。

Executors 返回的线程池对象的弊端如下:

1) FixedThreadPool 和 SingleThreadPool:允许的请求队列长度为 Integer.MAX_VALUE,可能会堆积大量的请求,从而导致 OOM。

hreadPoolExecutor.CallerRunsPolicy,由调用者执行自身execute方法来运行提交进来的任务,从名字CallerRuns(调用者运行)中就可以看出。它会提供一个简单的反馈控制机制,这种策略将降低新任务被提交上来的速度。

ThreadPoolExecutor.DiscardPolicy,也很简单粗暴,直接丢弃任务,不抛异常。
ThreadPoolExecutor.DiscardOldestPolicy,DiscardOldest丢弃最早的任务,在队列头部也就是最新进入队列的任务会被丢弃,然后尝试提交新任务,如果提交失败会继续重复以上步骤。
也可以自己实现RejectedExecutionHandler接口,并重写rejectedExecution方法来自定义拒绝策略。

Executors 返回的线程池对象的弊端如下:1) FixedThreadPool 和 SingleThreadPool:允许的请求队列长度为 Integer.MAX_VALUE,可能会堆积大量的请求,从而导致 OOM。2)CachedThreadPool:允许的创建线程数量为 Integer.MAX_VALUE,可能会创建大量的线程,从而导致 OOM。

转载地址:https://blog.csdn.net/wssjiayou/article/details/126290991 如侵犯您的版权,请留言回复原文章的地址,我们会给您删除此文章,给您带来不便请您谅解!

上一篇:线程中,start和run的区别,以及为什么不能调用两次start
下一篇:线程中的无名管道

发表评论

最新留言

哈哈,博客排版真的漂亮呢~
[***.90.31.176]2024年04月11日 17时11分27秒