线程基础、线程安全、线程池
文章平均质量分 94
# 线程基础、线程安全、线程池
Ssssongsmith 奕飞
这个作者很懒,什么都没留下…
展开
-
为什么加了 final 却依然无法拥有“不变性”?
什么是不变性要想回答上面的问题,我们首先得知道什么是不变性(Immutable)。如果对象在被创建之后,其状态就不能修改了,那么它就具备“不变性”。我们举个例子,比如下面这个 Person 类:public class Person { final int id = 1; final int age = 18;}如果我们创建一个 person 对象,那么里面的属性会有两个,即 id 和 age,并且由于它们都是被 final 修饰的,所以一旦这个 person 对象被创建好,那么原创 2021-02-21 16:04:35 · 133 阅读 · 3 评论 -
-final 的三种用法是什么?
final 的作用final 是 Java 中的一个关键字,简而言之,final 的作用意味着“这是无法改变的”。不过由于 final 关键字一共有三种用法,它可以用来修饰变量、方法或者类,而且在修饰不同的地方时,效果、含义和侧重点也会有所不同,所以我们需要把这三种情况分开介绍。我们先来看一下 final 修饰变量的情况。final 修饰变量作用关键字 final 修饰变量的作用是很明确的,那就是意味着这个变量一旦被赋值就不能被修改了,也就是说只能被赋值一次,直到天涯海角也不会“变心”。如果我们尝原创 2021-02-20 22:14:57 · 371 阅读 · 2 评论 -
19-你知道哪几种锁?分别有什么特点?
首先会对锁的分类有一个整体的概念,了解锁究竟有哪些分类标准。锁的 7 大分类需要首先指出的是,这些多种多样的分类,是评价一个事物的多种标准,比如评价一个城市,标准有人口多少、经济发达与否、城市面积大小等。而一个城市可能同时占据多个标准,以北京而言,人口多,经济发达,同时城市面积还很大。同理,对于 Java 中的锁而言,一把锁也有可能同时占有多个标准,符合多种分类,比如 ReentrantLock 既是可中断锁,又是可重入锁。根据分类标准我们把锁分为以下 7 大类别,分别是:偏向锁/轻量级锁/重原创 2020-12-19 00:22:32 · 748 阅读 · 12 评论 -
18::线程池实现“线程复用”的原理?
线程复用的原理,以及对线程池的 execute 这个非常重要的方法进行源码解析。线程复用原理我们知道线程池会使用固定数量或可变数量的线程来执行任务,但无论是固定数量或可变数量的线程,其线程数量都远远小于任务数量,面对这种情况线程池可以通过线程复用让同一个线程去执行不同的任务,那么线程复用背后的原理是什么呢?线程池可以把线程和任务进行解耦,线程归线程,任务归任务,摆脱了之前通过 Thread 创建线程时的一个线程必须对应一个任务的限制。在线程池中,同一个线程可以从 BlockingQueue 中不断提取原创 2020-12-18 08:51:07 · 113 阅读 · 7 评论 -
17——如何正确关闭线程池?shutdown 和 shutdownNow 的区别?
首先,我们创建一个线程数固定为 10 的线程池,并且往线程池中提交 100 个任务,如代码所示。ExecutorService service = Executors.newFixedThreadPool(10); for (int i = 0; i < 100; i++) { service.execute(new Task()); }那么如果现在我们想关闭该线程池该如何做呢?主要介绍 5 种在 ThreadPoolExecutor 中涉及关闭线程池的方法,如下所示。voi原创 2020-12-17 23:38:59 · 253 阅读 · 8 评论 -
16-如何根据实际需要,定制自己的线程池?
核心线程数第一个需要设置的参数往往是 corePoolSize 核心线程数,在上一课时我们讲过,合理的线程数量和任务类型,以及 CPU 核心数都有关系,基本结论是线程的平均工作时间所占比例越高,就需要越少的线程;线程的平均等待时间所占比例越高,就需要越多的线程。而对于最大线程数而言,如果我们执行的任务类型不是固定的,比如可能一段时间是 CPU 密集型,另一段时间是 IO 密集型,或是同时有两种任务相互混搭。那么在这种情况下,我们可以把最大线程数设置成核心线程数的几倍,以便应对任务突发情况。当然更好的办法是原创 2020-12-17 08:33:34 · 151 阅读 · 5 评论 -
15::合适的线程数量是多少?CPU 核心数和线程数的关系?
你可能经常在面试中被问到这两个问题,如果想要很好地回答它们首先你需要了解,我们调整线程池中的线程数量的最主要的目的是为了充分并合理地使用 CPU 和内存等资源,从而最大限度地提高程序的性能。在实际工作中,我们需要根据任务类型的不同选择对应的策略。CPU 密集型任务首先,我们来看 CPU 密集型任务,比如加密、解密、压缩、计算等一系列需要大量耗费 CPU 资源的任务。对于这样的任务最佳的线程数为 CPU 核心数的 1~2 倍,如果设置过多的线程数,实际上并不会起到很好的效果。此时假设我们设置的线程数量是原创 2020-12-16 23:55:38 · 602 阅读 · 0 评论 -
14::为什么不应该自动创建线程池?
为什么不应该自动创建线程池,所谓的自动创建线程池就是直接调用 Executors 的各种方法来生成前面了解过的常见的线程池,例如 Executors.newCachedThreadPool()。但这样做是有一定风险的,接下来我们就来逐一分析自动创建线程池可能带来哪些问题。FixedThreadPool首先我们来看第一种线程池 FixedThreadPool, 它是线程数量固定的线程池,如源码所示,newFixedThreadPool 内部实际还是调用了 ThreadPoolExecutor 构造函数。原创 2020-12-16 08:30:57 · 131 阅读 · 0 评论 -
13-线程池常用的阻塞队列有哪些?
线程池内部结构线程池的内部结构主要由四部分组成,如图所示。List item第一部分是线程池管理器,它主要负责管理线程池的创建、销毁、添加任务等管理操作,它是整个线程池的管家。List item第二部分是工作线程,也就是图中的线程 t0~t9,这些线程勤勤恳恳地从任务队列中获取任务并执行。List item第三部分是任务队列,作为一种缓冲机制,线程池会把当下没有处理的任务放入任务队列中,由于多线程同时从任务队列中获取任务是并发场景,此时就需要任务队列满足线程安全的要求,所以线程池中任务原创 2020-12-15 22:37:43 · 626 阅读 · 6 评论 -
12-有哪 6 种常见的线程池?什么是 Java8 的 ForkJoinPool?
Java 8 新增的 ForkJoinPool 线程池,6 种常见的线程池如下。FixedThreadPoolCachedThreadPoolScheduledThreadPoolSingleThreadExecutorSingleThreadScheduledExecutorForkJoinPoolFixedThreadPool第一种线程池叫作 FixedThreadPool,它的核心线程数和最大线程数是一样的,所以可以把它看作是固定线程数的线程池,它的特点是线原创 2020-12-15 09:39:00 · 472 阅读 · 5 评论 -
11-线程池有哪 4 种拒绝策略?
拒绝时机首先,新建线程池时可以指定它的任务拒绝策略,例如:newThreadPoolExecutor(5, 10, 5, TimeUnit.SECONDS, new LinkedBlockingQueue<>(),new ThreadPoolExecutor.DiscardOldestPolicy());以便在必要的时候按照我们的策略来拒绝任务,那么拒绝任务的时机是什么呢?线程池会在以下两种情况下会拒绝新提交的任务。第一种情况是当我们调用 shutdown 等方法关闭线程池后,即原创 2020-12-15 00:12:38 · 358 阅读 · 7 评论 -
10::线程池的各个参数的含义?
线程池的参数首先,我们来看下线程池中各个参数的含义,如表所示线程池主要有 6 个参数,其中第 3 个参数由 keepAliveTime + 时间单位组成。我们逐一看下它们各自的含义,corePoolSize 是核心线程数,也就是常驻线程池的线程数量,与它对应的是 maximumPoolSize,表示线程池最大线程数量,当我们的任务特别多而 corePoolSize 核心线程数无法满足需求的时候,就会向线程池中增加线程,以便应对任务突增的情况。线程创建的时机接下来,我们来具体看下这两个参数所代表的含原创 2020-12-14 08:41:38 · 293 阅读 · 11 评论 -
9:: 使用线程池比手动创建线程好在哪里?
为什么要使用线程池首先,回顾线程池的相关知识,在 Java 诞生之初是没有线程池的概念的,而是先有线程,随着线程数的不断增加,人们发现需要一个专门的类来管理它们,于是才诞生了线程池。没有线程池的时候,每发布一个任务就需要创建一个新的线程,这样在任务少时是没有问题的,如代码所示。/** * 描述: 单个任务的时候,新建线程来执行 */ public class OneTask { public static void main(String[] args) {原创 2020-12-13 23:23:54 · 337 阅读 · 5 评论 -
8::为什么多线程会带来性能问题?
什么是性能问题在上次时我们已经知道多线程带来的线程安全问题,但对于多线程而言,它不仅可能会带来线程安全问题,还有可能会带来性能问题,也许你会奇怪,我们使用多线程的最大目的不就是为了提高性能吗?让多个线程同时工作,加快程序运行速度,为什么反而会带来性能问题呢?这是因为单线程程序是独立工作的,不需要与其他线程进行交互,但多线程之间则需要调度以及合作,调度与合作就会带来性能开销从而产生性能问题。首先,我们来了解究竟什么是性能问题?其实性能问题有许多的表现形式,比如服务器的响应慢、吞吐量低、内存占用过多就属于性原创 2020-12-13 14:01:29 · 398 阅读 · 3 评论 -
7-哪些场景需要额外注意线程安全问题?
访问共享变量或资源第一种场景是访问共享变量或共享资源的时候,典型的场景有访问共享对象的属性,访问 static 静态变量,访问共享的缓存,等等。因为这些信息不仅会被一个线程访问到,还有可能被多个线程同时访问,那么就有可能在并发读写的情况下发生线程安全问题。比如我们上次说过的多线程同时 i++ 的例子:/** * 描述: 共享的变量或资源带来的线程安全问题 */public class ThreadNotSafe1 { static int i; public static原创 2020-12-13 00:05:58 · 325 阅读 · 5 评论 -
6 : :一共有哪 3 类线程安全问题?
什么是线程安全要想弄清楚有哪 3 类线程安全问题,首先需要了解什么是线程安全,线程安全经常在工作中被提到,比如:你的对象不是线程安全的,你的线程发生了安全错误,虽然线程安全经常被提到,但我们可能对线程安全并没有一个明确的定义。《Java Concurrency In Practice》的作者 Brian Goetz 对线程安全是这样理解的,当多个线程访问一个对象时,如果不用考虑这些线程在运行时环境下的调度和交替执行问题,也不需要进行额外的同步,而调用这个对象的行为都可以获得正确的结果,那这个对象便是线程原创 2020-12-12 22:21:49 · 108 阅读 · 2 评论 -
5-有哪几种实现生产者消费者模式的方法?如何用 wait(等待)/notify/(通知)Condition(..条件..)/BlockingQueue(阻塞队列) 实现生产者消费者模式。
生产者消费者模式我们先来看看什么是生产者消费者模式,生产者消费者模式是程序设计中非常常见的一种设计模式,被广泛运用在解耦、消息队列等场景。在现实世界中,我们把生产商品的一方称为生产者,把消费商品的一方称为消费者,有时生产者的生产速度特别快,但消费者的消费速度跟不上,俗称“产能过剩”,又或是多个生产者对应多个消费者时,大家可能会手忙脚乱。如何才能让大家更好地配合呢?这时在生产者和消费者之间就需要一个中介来进行调度,于是便诞生了生产者消费者模式。使用生产者消费者模式通常需要在两者之间增加一个阻塞队列作为媒原创 2020-12-12 08:04:41 · 153 阅读 · 10 评论 -
4-wait(等待)/notify(通知)/notifyAll 方法的使用注意事项?
我们主要从三个问题入手:为什么 wait 方法必须在 synchronized 保护的同步代码中使用?为什么 wait/notify/notifyAll 被定义在 Object 类中,而 sleep 定义在 Thread 类中?wait/notify 和 sleep 方法的异同?为什么 wait 必须在 synchronized 保护的同步代码中使用?首先,我们来看第一个问题,为什么 wait 方法必须在 synchronized 保护的同步代码中使用?我们先来看看 wait 方法的源码注释原创 2020-12-12 00:02:48 · 124 阅读 · 7 评论 -
3-线程是如何在 6 种状态之间转换的?
线程的 6 种状态就像生物从出生到长大、最终死亡的过程一样,线程也有自己的生命周期,在 Java 中线程的生命周期中一共有 6 种状态。New(新创建)Runnable(可运行)Blocked(被阻塞)Waiting(等待)Timed Waiting(计时等待)Terminated(被终止)如果想要确定线程当前的状态,可以通过 getState() 方法,并且线程在任何时刻只可能处于 1 种状态。New 新创建下面我们逐个介绍线程的 6 种状态,如图所示,首先来看下左上角的 New原创 2020-12-11 08:17:30 · 301 阅读 · 14 评论 -
2-如何正确停止线程?为什么 volatile 标记位的停止方法是错误的?
首先,如何启动一个线程,想要启动线程需要调用 Thread 类的 start() 方法,并在 run() 方法中定义需要执行的任务。启动一个线程非常简单,但如果想要正确停止它就没那么容易了。原理介绍通常情况下,我们不会手动停止一个线程,而是允许线程运行到结束,然后让它自然停止。但是依然会有许多特殊的情况需要我们提前停止线程,比如:用户突然关闭程序,或程序运行出错重启等。在这种情况下,即将停止的线程在很多业务场景下仍然很有价值。尤其是我们想写一个健壮性很好,能够安全应对各种场景的程序时,正确停止线程就显原创 2020-12-11 00:11:05 · 289 阅读 · 5 评论 -
<< 1-为何说只有 1 种实现线程的方法?-解你的疑惑
为什么说本质上只有一种实现线程的方式?实现 Runnable 接口究竟比继承 Thread 类实现线程好在哪里?实现线程的方式到底有几种?大部分人会说有 2 种、3 种或是 4 种,很少有人会说有 1 种。我们接下来看看它们具体指什么?2 种实现方式的描述是最基本的,也是最为大家熟知的,我们就先来看看 2 种线程实现方式的源码。实现 Runnable 接口...原创 2020-12-10 18:08:17 · 442 阅读 · 0 评论