线程池-参数篇:2.队列

在多线程编程的世界里,队列可是个不可或缺的好帮手,它让不同线程之间共享数据变得轻而易举。
就拿那个老掉牙的“生产者”和“消费者”模型来说吧,队列就能让这两个角色之间的数据传递变得特别顺畅。
更棒的是,作为队列的使用者,你根本不用操心线程该什么时候停下来等一等,什么时候又该被叫醒继续干活,因为这一切都被队列的内部机制给搞定啦!
咱们先来看看基于数组的阻塞队列,比如ArrayBlockingQueue。
这个家伙内部有个固定大小的数组,用来存放队列里的数据,同时还有两个整型变量,分别标记着队列头和尾的位置。
在生产者和消费者交互的时候,他们可是共享同一个锁,这就意味着这两个角色不能同时操作队列,有点像排队买票,你得等前面的人买完才能轮到你。
不过,在创建ArrayBlockingQueue的时候,你可以选择用公平锁还是非公平锁,默认是后者。
其实,ArrayBlockingQueue完全可以采用分离锁,让生产者和消费者并行工作,但似乎并没有这么做。

接下来是基于链表的阻塞队列,LinkedBlockingQueue就是它的代表。
这个队列内部维护着一个由链表构成的数据缓冲区。
当生产者往队列里放数据时,队列会先把数据收进来,然后生产者就拍拍屁股走人了;只有当队列满了,生产者才会被叫住,直到消费者把数据取走,生产者才能继续放数据。
而且,生产者和消费者用的是不同的锁,这就意味着在高并发的情况下,他们可以同时操作队列,提高效率。
不过,LinkedBlockingQueue在插入或删除元素时会产生额外的对象实例,这在处理大量数据时可能会对垃圾回收造成影响。
而且,如果没有指定容量,LinkedBlockingQueue会默认是个“无底洞”,可能会导致内存溢出。

总的来说,ArrayBlockingQueue和LinkedBlockingQueue是多线程编程中常用的两种阻塞队列,对付大多数生产者消费者问题都足够了。

再来看看DelayQueue,这是一个特殊的队列,只有当队列里的元素到达指定的延迟时间后,才能被取走。
DelayQueue没有大小限制,所以生产者永远不会被阻塞,只有消费者在队列空时会等待。
DelayQueue里存放的是实现了Delayed接口的对象,这些对象只有在到期后才能被使用。
DelayQueue是有序的,元素按照延迟时间排序,而且不能放null进去。
Delayed接口继承了Comparable接口,比较的是延迟时间。
DelayQueue内部是用PriorityQueue实现的。
在某些需要按时间顺序处理任务的场景下,DelayQueue是个不错的选择。

PriorityBlockingQueue则是一个基于优先级的队列,元素的优先级由构造函数传入的Comparator决定。
不过,它并不会阻塞生产者,只有消费者在队列为空时会等待。
如果生产者太快,消费者太慢,长时间运行可能会导致内存溢出。
PriorityBlockingQueue使用的是公平锁来控制线程同步。

最后是SynchronousQueue,这个队列里最多只能有一个元素。
如果你往里面放元素,得等到另一个线程来取,否则就会阻塞;如果你尝试取元素,但里面没有,也会阻塞,直到另一个线程放进来。
SynchronousQueue有公平和非公平两种模式,区别在于线程获取元素的顺序。

以上就是几种常见的阻塞队列,各有各的特点,适用于不同的场景。
在实际开发中,根据需求选择合适的队列,可以让多线程编程变得更加简单高效。

JAVA线程池shutdown和shutdownNow的区别

嘿,咱们聊聊线程池那些事儿!知道吗,线程池其实就是一个团队合作的模式,任务来了先放队列里,然后后台的线程们就会自动把它们一个个执行掉。
这些线程就像小助手一样,默默地工作,用默认的堆栈大小,默认的优先级,大家在一个大单元里并肩作战。

有时候,某个小助手可能在等待某个任务(就像等人一样),这时候线程池就会派来另一个小助手来帮忙,确保处理器们不会闲着。
不过,大家都在忙的时候,如果任务还没完成,线程池还会继续招募小助手,但小助手们的数量是有限制的,不会超过设定的最大值。

要是超出了最大值,剩下的小助手们就得排队等着,等到前面的小助手完成任务才能轮到自己上场。

说到线程池的状态,我来给你说说两个方法。
shutDown()这个方法一叫,线程池就直接进入休息状态(SHUTDOWN),这时候就不能再接新任务了,要是硬塞进去,就会抛出个RejectedExecutionException的小脾气。
不过,别担心,线程池会慢慢悠悠地把现有的任务都做完,才正式下班。

再来说说shutdownNow(),这可是个急刹车的方法。
它能让线程池立刻进入停车状态(STOP),试图让所有的小助手都停下来,不再处理队列中的任务,并把还没开始的任务都返给你。
但是呢,这并不是说线程池马上就能走人,如果小助手们正在沉睡、等待或者被锁住,interrupt()这个唤醒方式效果可能就不怎么好了。
所以,shutdownNow()也不是说线程池一叫就能立刻退出,可能还得等所有任务都完成了,它才能安心地离开。

线程池连环问,你顶得住吗?

大家好,今天想跟大家聊聊线程池这个话题。
简单来说,线程池就是提前准备好一堆线程,用的时候直接从池子里拿,这样就不用老去创建新线程了,能省不少事儿,代码执行也更快。

为什么不用new Thread,反而要用线程池呢?
1 . 控制风险:电脑资源是有限的,手动创建线程没啥统一标准(比如线程命名、资源分配这些),搞得线程们乱抢资源,系统都管不过来了。
2 . 省事儿:频繁创建线程是个挺麻烦的事儿,得分配内存、初始化栈帧、分配程序计数器等等,每一步都得来,特别耗时。
而线程池通过复用线程,就省去了这些麻烦。

那为啥要用线程池呢?
1 . 省资源:线程池能复用线程,不用老创建新线程,这样就省了不少资源。
2 . 快:任务来了,不用等新线程创建,直接从池子里拿一个来用,响应速度自然就快了。
3 . 好管理:统一管理线程,防止系统因为创建太多线程而崩溃。

线程池是怎么工作的呢?
1 . 核心线程数(corePoolSize):当活跃的线程数小于这个值时,新任务会直接创建新线程来执行。
如果活跃线程数已经达到这个值,那空闲的线程也不会被销毁,会继续等待任务。
2 . 任务队列(workQueue):当活跃线程数等于核心线程数时,新任务会进队列排队等待。
3 . 最大线程数(maximumPoolSize):当队列满了,而且活跃线程数还没达到最大值时,会继续创建新线程来处理任务,直到达到最大值。
4 . 拒绝策略:当队列和线程池都满了,新任务来了咋办?那就根据拒绝策略来处理了,默认是抛出异常。

线程池的参数有哪些?
1 . corePoolSize:核心线程数,线程池的基本大小。
2 . maximumPoolSize:最大线程数,超过核心线程数的线程在空闲一段时间后会被回收。
3 . BlockingQueue:任务队列,用来存储等待执行的任务。
4 . keepAliveTime:非核心线程空闲时的存活时间。
5 . TimeUnit:时间单位,比如秒、毫秒等。
6 . ThreadFactory:线程工厂,用来创建新线程。
7 . RejectedExecutionHandler:拒绝策略,用来处理队列和线程池都满时的新任务。

线程池大小怎么设置呢?
1 . CPU密集型任务(N+1 ):线程数设置为N(CPU核心数)+1 ,多出的线程用来应对阻塞。
2 . I/O密集型任务(2 N):线程数可以设置为2 N,计算公式是:最佳线程数=CPU核心数(1 +(IO耗时/CPU耗时))。

线程池的类型及适用场景?
1 . FixedThreadPool:固定线程数的线程池,使用无界队列。
适合CPU密集型任务,长期执行的任务。
注意:任务太多可能导致内存溢出。
2 . SingleThreadExecutor:单线程的线程池,使用无界队列。
适合串行执行任务。
注意:任务太多也可能导致内存溢出。
3 . CachedThreadPool:根据需要创建新线程的线程池,使用SynchronousQueue。
适合并发执行大量短期小任务。
注意:可能创建大量线程导致内存溢出。
4 . ScheduledThreadPoolExecutor:支持延迟或定期执行任务的线程池,使用DelayQueue。
适合周期性执行任务,需要限制线程数量的场景。

如何判断线程池的任务是否执行完毕?
1 . isTerminated():线程池的原生方法,返回true表示所有任务都完成了。
2 . 重入锁+公共计数器:用锁维护一个计数器,任务完成时计数器加一,当计数器等于任务数时表示全部完成。
3 . CountDownLatch:初始化时设置一个计数值,任务完成时调用countDown()减一,阻塞直到计数值为零。
缺点是需要提前知道任务数量。
4 . Future判断状态:通过submit提交任务获取Future对象,调用future.isDone()判断任务是否完成。

为什么要用Executor线程池框架?
1 . 性能优化:避免频繁创建线程的开销。
2 . 资源管控:防止无限制创建线程导致系统资源耗尽。
3 . 扩展性:支持定时、定期执行任务,以及线程中断等高级功能。

希望这些内容能帮助大家更好地理解线程池,提高代码的执行效率!

腾讯面试官:说说多线程问题

嘿,面试官们喜欢问多线程的问题,所以咱们得好好准备一下。
下面是一些常见的问题和简单的解释,让你在面试中轻松应对:
1 . 进程和线程啥区别? 进程是程序运行的实例,就像是系统中的一个个独立的小宇宙。
每个进程都有自己的资源,比如内存和文件句柄,但启动和关闭它们比较耗费资源。
线程是进程内的更小的执行单位,一个进程可以有很多线程。
线程共享一些资源,但有自己的计数器和栈空间,创建和切换它们更快,适合处理多个任务。

2 . 线程有几种状态? Java的线程可以在6 种状态中切换:新创建、可运行、阻塞、等待、超时等待和终止。
就像人的一生,线程也有自己的生命周期。

3 . 上下文切换是啥? 想象一下CPU像个时间片分配官,它给每个线程一点时间来执行。
当线程的时间片用完了,CPU就会保存这个线程的状态,然后切换到另一个线程。
这个过程叫上下文切换,虽然快,但频繁切换会增加CPU的负担。

4 . 怎么创建线程? 有几种方法:可以继承Thread类,也可以实现Runnable接口,或者使用Callable接口来获取返回值。
还有更方便的线程池,可以复用线程资源。

5 . synchronized是啥? synchronized就像一把锁,用来防止多个线程同时访问同一个资源。
它能保证代码块或方法在同一时间只被一个线程执行。

6 . 线程池咋配置? 线程池有7 个核心参数,比如核心线程数、最大线程数、空闲线程的存活时间等。
你可以通过这些参数来控制线程池的行为。

希望这些信息能帮你在面试中表现得更好!

美团|后端开发日常实习|一二面(OC)

在美团后端开发日常实习的面试中,一、二面主要聚焦在实习项目经历、Java后端技术栈(涵盖并发编程、JVM、线程池等)、Redis使用、数据库知识(如锁机制、事务、范式)以及算法题上。
面试体验挺不错的,交流挺多的。

一面主要聊了实习和项目经验,看中你实际操作的能力。
然后是一些基础问题,比如内存队列可能会遇到的内存溢出问题,还有对Redis的zset(有序集合)数据结构的理解,包括跳表和哈希表的实现,以及Redis如何实现排序、锁和延时消息等功能。

接着聊到了线程池,考察了你对于线程池那七个关键参数的了解,以及在实际项目中如何设置这些参数。
还讨论了线程池的丢弃策略和线程安全问题,以及CompletableFuture的使用和底层原理。

至于算法题,要求找出二叉搜索树的第k个最小值,这是一个典型的中序遍历问题。

二面则继续围绕实习经历,对CompletableFuture的底层实现和线程切换机制进行了深入探讨。
还考察了MySQL中的行锁、间隙锁、临键锁,以及乐观锁的实现和volatile关键字的作用。
此外,还涉及了Spring事务和数据库范式的理解,以及为什么在实际开发中有时会反范式设计。
最后,还有一个算法题,是要求循环打印ABC1 00次,可以通过多线程或循环控制方法完成。