《面试补习》- 多线程知识梳理
一、基本概念
1.1、进程
进程是系统资源分配的最小单位。由 文本区域
,数据区域
和堆栈
组成。
文本区域存储处理器执行的代码
数据区域存储变量和进程执行期间使用的动态分配的内存;
堆栈区域存储着活动过程调用的指令和本地变量。
涉及问题: cpu抢占
,内存分配(虚拟内存/物理内存)
,以及进程间通信
。
1.2、线程
线程是操作系统能够进行运算调度的最小单位。
一个进程可以包括多个线程,线程共用
进程所分配到的资源空间
涉及问题: 线程状态
,并发问题
,锁
1.3、协程
子例程
: 某个主程序的一部分代码,也就是指某个方法,函数。
维基百科:执行过程类似于 子例程
,有自己的上下文,但是其切换由自己控制。
1.4、常见问题
1、进程和线程的区别
2、什么是多线程
二、Thread
2.1、使用多线程
2.1.1、继承 Thread 类
2.1.2、实现 Runnable 接口
2.1.3、实现 Callable 接口
2.1.4、常见问题
1、使用多线程有哪些方式
常用的方式主要由上述 3 种,需要注意的是 使用
,而不是创建线程,从实现的代码我们可以看到,Java 创建线程只有一种方式, 就是通过 new Thread()
的方式进行创建线程。
2、
Thread()
,Runnable()
与Callable()
之间的区别
Thread
需要继承,重写run()
方法,对拓展不友好,一个类即一个线程任务。
Runnbale
通过接口的方式,可以实现多个接口,继承父类。需要创建一个线程进行装载任务执行。
Callable
JDK1.5 后引入, 解决 Runnable 不能返回结果或抛出异常的问题。需要结合 ThreadPoolExecutor
使用。
3、
Thread.run()
和Thread.start()
的区别
Thread.run()
输出结果:
Thread.start()
输出结果:
2.2、线程状态
线程状态,也称为线程的生命周期, 主要可以分为: 新建
,就绪
,运行
,死亡
,堵塞
等五个阶段。
图片引用 芋道源码
2.2.1 新建
新建状态比较好理解, 就是我们调用 new Thread()
的时候所创建的线程类。
2.2.2 就绪
就绪状态指得是:
1、当调用 Thread.start
时,线程可以开始执行, 但是需要等待获取 cpu 资源。区别于 Thread.run
方法,run
方法是直接在当前线程进行执行,沿用其 cpu 资源。
2、运行状态下,cpu 资源
使用完后,重新进入就绪状态,重新等待获取 cpu 资源
. 从图中可以看到,可以直接调用Thread.yield
放弃当前的 cpu 资源,进入就绪状态。让其他优先级更高的任务优先执行。
2.2.3 运行
在步骤2
就绪状态中,获取到 cpu资源
后,进入到运行状态, 执行对应的任务,也就是我们实现的 run()
方法。
2.2.4 结束
1、正常任务执行完成,run() 方法执行完毕
2、异常退出,程序抛出异常,没有捕获
2.2.5 阻塞
阻塞主要分为: io 等待,锁等待,线程等待 这几种方式。通过上述图片可以直观的看到。
io 等待: 等待用户输入,让出 cpu 资源,等用户操作完成后(io 就绪),重新进入就绪状态。
锁等待:同步代码块需要等待获取锁,才能进入就绪状态
线程等待: sleep()
, join()
和 wait()/notify()
方法都是等待线程状态的阻塞(可以理解成当前线程的状态受别的线程影响)
二、线程池
2.1 池化技术
池化技术,主要是为了减少每次资源的创建,销毁所带来的损耗,通过资源的重复利用提高资源利用率而实现的一种技术方案。常见的例如: 数据库连接池,http 连接池以及线程池等。都是通过池同一管理,重复利用,从而提高资源的利用率。
使用线程池的好处:
降低资源消耗:通过重复利用已创建的线程降低线程创建和销毁造成的消耗。
提高响应速度:当任务到达时,任务可以不需要的等到线程创建就能立即执行。
提高线程的可管理性:线程是稀缺资源,如果无限制的创建,不仅会消耗系统资源,还会降低系统的稳定性,使用线程池可以进行统一的分配,调优和监控。
2.2 线程池创建
2.2.1 Executors (不建议)
Executors 可以比较快捷的帮我们创建类似 FixedThreadPool ,CachedThreadPool 等类型的线程池。
存在的弊端:
2.2.2 ThreadPoolExecuotr
构造函数:
几个核心的参数:
1、
corePoolSize
: 核心线程数2、
maximumPoolSize
: 最大线程数3、
keepAliveTime
: 线程空闲存活时间4、
unit
: 时间单位5、
workQueue
: 等待队列6、
threadFactory
: 线程工厂7、
handler
: 拒绝策略
与上述的 ExecutorService.newSingleThreadExecutor
等多个api
进行对比,可以比较容易的区分出底层的实现是依赖于 BlockingQueue
的不同而定义的线程池。
主要由以下几种的阻塞队列:
1、
ArrayBlockingQueue
,队列是有界的,基于数组实现的阻塞队列2、
LinkedBlockingQueue
,队列可以有界,也可以无界。基于链表实现的阻塞队列 对应了:Executors.newFixedThreadPool()
的实现。3、
SynchronousQueue
,不存储元素的阻塞队列,每个插入操作必须等到另一个线程调用移除操作,否则插入操作将一直处于阻塞状态。对应了:Executors.newCachedThreadPool()
的实现。4、
PriorityBlockingQueue
,带优先级的无界阻塞队列
拒绝策略主要有以下 4 种:
1、
CallerRunsPolicy
: 在调用者线程执行2、
AbortPolicy
: 直接抛出 RejectedExecutionException 异常3、
DiscardPolicy
: 任务直接丢弃,不做任何处理4、
DiscardOldestPolicy
: 丢弃队列里最旧的那个任务,再尝试执行当前任务
2.3 线程池提交任务
往线程池中提交任务,主要有两种方法,execute()
和submit()
1、 execute()
无返回结果,直接执行任务
2、submit()
submit()
会返回一个 Future
对象,用于获取返回结果,常用的 api 有 get()
和 get(timeout,unit)
两种方式,常用于做限时处理
三、线程工具类
3.1 ThreadlLocal
ThreadLocal,很多地方叫做线程本地变量,也有些地方叫做线程本地存储,其实意思差不多。可能很多朋友都知道 ThreadLocal 为变量在每个线程中都创建了一个副本,那么每个线程可以访问自己内部的副本变量。
3.2 Semaphore
Semaphore ,是一种新的同步类,它是一个计数信号. 使用示例代码:
3.3 CountDownLatch
可以理解成是一个栅栏,需要等所有的线程都执行完成后,才能继续往下走。
CountDownLatch
默认的构造方法是 CountDownLatch(int count)
,其参数表示需要减少的计数,主线程调用 #await()
方法告诉 CountDownLatch
阻塞等待指定数量的计数被减少,然后其它线程调用 CountDownLatch
的 #countDown()
方法,减小计数(不会阻塞)。等待计数被减少到零,主线程结束阻塞等待,继续往下执行。
3.4 CyclicBarrier
CyclicBarrier
与 CountDownLatch
有点相似, 都是让线程都到达某个点,才能继续往下走, 有所不同的是 CyclicBarrier
是可以多次使用的。 示例代码:
四、总结
最后贴一个新生的公众号 (Java 补习课
),欢迎各位关注,主要会分享一下面试的内容(参考之前博主的文章),阿里的开源技术之类和阿里生活相关。 想要交流面试经验的,可以添加我的个人微信(Jayce-K
)进群学习~
版权声明: 本文为 InfoQ 作者【JayceKong】的原创文章。
原文链接:【http://xie.infoq.cn/article/272558e3c9cab161d5f125bd1】。文章转载请联系作者。
评论