面试【JAVA基础】多线程
本次整理的内容如下:
1、进程与线程的区别
进程是一个可执行的程序,是系统资源分配的基本单位;线程是进程内相对独立的可执行单元,是操作系统进行任务调度的基本单位。
2、进程间的通信方式
2.1、操作系统内核缓冲区
由于每个进程都有独立的内存空间,进程之间的数据交换需要通过操作系统内核。需要在操作系统内核中开辟一块缓冲区,进程 A 将需要将数据拷贝到缓冲区中,进程 B 从缓冲区中读取数据。因为共享内存没有互斥访问的功能,需配合信号量进行互斥访问。
2.2、管道
管道的实现方式:
- 父进程创建管道,得到两个描述文件指向管道的两端。
- 父进程 fork 出子进程,子进程也拥有两个描述文件,指向同一个管道的两端。
- 父进程关闭读端(fd(0)),子进程关闭写端(fd(1))。父进程往管道里面写,子进程从管道里面读。
管道的特点:
只允许具有血缘关系的进程间通讯,只允许单向通讯,进程在管道在,进程消失管道消失。管道内部通过环形队列实现。
有名管道(命名管道):
通过文件的方式实现进程间的通信。允许无血缘关系的进程间的通信
2.3、消息队列
由消息组成的链表,存在系统内核中。克服了信号量传递的信息少,管道只能承载无格式的字符流及缓冲区的大小受限等特点。通过消息类型区分消息。
2.4、信号量
本质是一个计数器,不以传送数据为目的,主要用来保护共享资源,使得资源在一个时刻只有一个进程独享。
2.5、套接字
可用于不同机器间进程的通信。
套接字包括 3 个属性:域、类型、 协议。
- 域包括 ip 端口
- 类型指的是两种通信机制:流(stream)和数据报(datagram)
- 协议指 TCP/UDP 底层传输协议
创建 socket 通过 bind 命名绑定端口,listen 创建队列保存未处理的客户端请求,accept 等待客户端的连接,connect 服务端连接客户端 socket,close 关闭服务端客户端的连接。
stream 和 datagram 的区别:
stream 能提供有序的、可靠的、双向的、基于连接的字节流(TCP),会有拆包粘包问题。
datagram 是无连接、不可靠、使用固定大小的缓冲区的数据报服务(UDP),因为基于数据报,且有固定的大小,所以不会有拆包粘包问题。
详细请参考:进程间的五种通信方式介绍
3、线程间的通信方式
共享内存:
Java 采用的就是共享内存,内存共享方式必须通过锁或者 CAS 技术来获取或者修改共享的变量,看起来比较简单,但是锁的使用难度比较大,业务复杂的话还有可能发生死锁。
消息传递:
Actor 模型即是一个异步的、非阻塞的消息传递机制。Akka 是对于 Java 的 Actor 模型库,用于构建高并发、分布式、可容错、事件驱动的基于 JVM 的应用。消息传递方式就是显示的通过发送消息来进行线程间通信,对于大型复杂的系统,可能优势更足。
详细请参考:Java 内存模型分析
4、多线程的优缺点
优点:
充分利用 cpu 的资源,提高 cpu 的使用率,使程序的运行效率提高。
缺点:
有大量的线程会影响性能,操作系统会在线程之间切换,会增加内存的开销。可能会产生死锁、存在线程之间的并发问题。
5、创建线程的方法
- 集成 Thread 类,重写 run 方法,利用 start 启动线程。
- 实现 Runable 接口创建线程,重写 run 方法,通过 new Thread 方式创建线程。
- 通过 callable 和 futuretask 创建线程,实现 callable 接口,重写 call 方法,使用 future 对象包装 callable 实例,通过 new Thread 方式创建线程。
- 通过线程池创建线程。
6、runable 和 callable 区别
- runable 是重写 run 方法,callable 重写 call 方法。
- runable 没有返回值,callable 有返回值。
- callable 中的 call 方法可以抛出异常,runable 中的 run 方法不能向外界抛出异常。
- 加入线程池运行 runable 使用 execute 运行,callable 使用 submit 方法。
7、sleep 和 wait 区别
- wait 只能在 synchronized 块中调用,属于对象级别的方法,sleep 不需要,属于 Thread 的方法。
- 调用 wait 方法时候会释放锁,sleep 不会释放锁。
- wait 超时之后线程进入就绪状态,等待获取 cpu 继续执行。
8、yield 和 join 区别
- yield 释放 cpu 资源,让线程进入就绪状态,属于 Thread 的静态方法,不会释放锁,只能使同优先级或更高优先级的线程有执行的机会。
- join 等待调用 join 方法的线程执行完成之后再继续执行。join 会释放锁和 cpu 的资源,底层是通过 wait 方法实现的。
9、死锁的产生条件
- 互斥条件。
- 请求与保持条件。
- 不可剥夺条件。
- 循环等待条件。
详细请参考:并发编程挑战:死锁与上下文切换
10、如何解决死锁
- 破坏请求与保持条件
静态分配,每个线程开始前就获取需要的所有资源。
动态分配,每个线程请求获取资源时本身不占有资源。 - 破坏不可剥夺条件
当一个线程不能获取所有的资源时,进入等待状态,其已经获取的资源被隐式释放,重新加入到系统的资源列表中,可被其他线程使用。 - 死锁检测:银行家算法
11、threadLocal 的实现
- ThreadLocal 用于提供线程局部变量在多线程环境下可以保证各个线程里面的变量独立于其他线程里的变量。
- 底层使用 ThreadLocalMap 实现,每个线程都拥有自己的 ThreadLocalMap,内部为继承了 WeakReference 的 Entry 数组,包含的 Key 为 ThreadLocal,值为 Object。
详细请参考:【SharingObjects】ThreadLocal
12、threadLocal 什么时候会发生内存泄漏
java.lang.ThreadLocal.ThreadLocalMap.Entry:
static class Entry extends WeakReference<ThreadLocal<?>> {
/** The value associated with this ThreadLocal. */
Object value;
Entry(ThreadLocal<?> k, Object v) {
//重点!!!!!
super(k);
value = v;
}
}
因为 ThreadLocalMap 中的 key 是弱引用,而 key 指向的对象是 threadLocal,一旦把 threadLocal 实例置为 null 之后,没有任何强引用的对象指向 threadLocal 对象,因此 threadLocal 对象会被 Gc 回收,但与之关联的 value 却不能被回收,只有当前线程结束后,对应的 map value 才会被回收。如果当前线程没结束,可能会导致内存泄漏。
如线程池的场景,在线程中将 threadlocal 置为 null,但线程没被销毁且一直不被使用,就可能会导致内存泄漏
在调用 get、set、remove 方法时,会清除线程 map 中所有 key 为 null 的 value。所以在不使用 threadLocal 时调用 remove 移除对应的对象。
13、线程池
13.1、线程池类结构
ThreadPoolExecutor 继承关系图:
13.2、shutDown 和 shutDownNow 的区别、
shutDown 方法执行之后会变成 SHUTDOWN 状态,无法接受新任务,随后等待已提交的任务执行完成。
shutDownNow 方法执行之后变成 STOP 状态,无法接受新任务。并对执行中的线程执行 Thread.interrupt()方法。
- SHUTDOWN:不接受新任务提交,但是会继续处理等待队列中的任务。
- STOP:不接受新任务提交,不再处理等待队列中的任务,中断正在执行任务的线程。
13.3、线程池的参数
- CorePoolSize 核心线程数
- MaximumPoolSize 最大线程数,线程池允许创建的最大线程数
- keepAliveTime 空闲线程的存活时间
- wokeQueue 任务队列
- handler 饱和策略
- threadFactory 用于生成线程。
当任务来时,如果当前的线程数到达核心线程数,会将任务加入阻塞队列中,如果阻塞队列满了之后,会继续创建线程直到线程数量达到最大线程数,如果线程数量已经达到最大线程数量,且任务队列满了之后,会执行拒绝策略。
如果想让核心线程被回收,可以使用 allowCoreThreadTimeOut 参数,如果为 false(默认值),核心线程即使在空闲时也保持活动状态。如果 true,核心线程使用 keepAliveTime 来超时等待工作。
13.4、线程池的饱和策略
- CallerRunsPolicy:由提交任务的线程自己执行这个任。
- AbortPolicy (默认): 直接抛出 RejectExecutionException 异常。
- DisCardPolicy:不做处理,抛弃掉当前任务。
- DiscardOldestPolicy: 把队列队头的任务直接扔掉,提交当前任务进阻塞队列。
13.5、线程池分类
java.util.concurrent.Executors 类:
- newFixedThreadPool
public static ExecutorService newFixedThreadPool(int nThreads) {
return new ThreadPoolExecutor(nThreads, nThreads,
0L, TimeUnit.MILLISECONDS,
new LinkedBlockingQueue<Runnable>());
}
生成一个固定大小的线程池,此时核心线程数和最大线程数相等,keepAliveTime = 0 ,任务队列采取 LinkedBlockingQueue 无界队列(也可设置为有界队列)。
适用于为了满足资源管理需求,而需要限制当前线程数量的应用场景,比如负载比较重的服务器。
- newSingleThreadExecutor
public static ExecutorService newSingleThreadExecutor() {
return new FinalizableDelegatedExecutorService
(new ThreadPoolExecutor(1, 1,
0L, TimeUnit.MILLISECONDS,
new LinkedBlockingQueue<Runnable>()));
生成只有一个线程的线程池,核心线程数与最大线程数都是 1,keepAliveTime = 0,任务队列采取 LinkedBlockingQueue,适用于需要保证顺序地执行各个任务,并且在任意时间点不会有多个线程是活动的应用场景。
- newCachedThreadPool
public static ExecutorService newCachedThreadPool() {
return new ThreadPoolExecutor(0, Integer.MAX_VALUE,
60L, TimeUnit.SECONDS,
new SynchronousQueue<Runnable>());
}
核心线程数是 0,最大线程数是 int 最大值,keepaliveTime 为 60 秒,任务队列采取 SynchronousQueue,适用于执行很多的短期异步任务的小程序,或者是负载较轻的服务器。
- newScheduledThreadPool
public static ScheduledExecutorService newSingleThreadScheduledExecutor() {
return new DelegatedScheduledExecutorService
(new ScheduledThreadPoolExecutor(1));
}
public ScheduledThreadPoolExecutor(int corePoolSize) {
super(corePoolSize, Integer.MAX_VALUE, 0, NANOSECONDS,
new DelayedWorkQueue());
}
定长的线程池,支持周期性任务,最大线程数是 int 最大值,超时时间为 0,任务队列使用 DelayedWorkQueue,适用于需要多个后台执行周期任务,同时为了满足资源管理需求而需要限制后台线程的数量的应用场景。
13.6、任务执行过程中出现异常会怎么样?
任务执行失败后,只会影响到当前执行任务的线程,对于整个线程池是没有影响的。
详细请参考:ThreadPoolExecutor 线程池任务执行失败的时候会怎样
13.7、线程池的底层实现
- 使用 hashSet 存储 worker
- 每个 woker 控制自己的状态
- 执行完任务之后循环获取任务队列中的任务
13.8、重启服务、如何优雅停机关闭线程池
kill -9 pid 操作系统内核级别强行杀死某个进程。
kill -15 pid 发送一个通知,告知应用主动关闭。
ApplicationContext 接受到通知之后,会执行 DisposableBean 中的 destroy 方法。
一般我们在 destroy 方法中做一些善后逻辑。
调用 shutdown 方法,进行关闭。
13.9、为什么使用线程池
- 降低资源消耗,减少创建销毁线程的成本。
- 提高响应速度。
- 提高线程的可管理性,线程的无限制的创建,消耗系统资源,降低系统的稳定性。
面试【JAVA基础】多线程的更多相关文章
- 备战金三银四!一线互联网公司java岗面试题整理:Java基础+多线程+集合+JVM合集!
前言 回首来看2020年,真的是印象中过的最快的一年了,真的是时间过的飞快,还没反应过来年就夸完了,相信大家也已经开始上班了!俗话说新年新气象,马上就要到了一年之中最重要的金三银四,之前一直有粉丝要求 ...
- Java基础-多线程-③线程同步之synchronized
使用线程同步解决多线程安全问题 上一篇 Java基础-多线程-②多线程的安全问题 中我们说到多线程可能引发的安全问题,原因在于多个线程共享了数据,且一个线程在操作(多为写操作)数据的过程中,另一个线程 ...
- Java基础-多线程-②多线程安全问题
什么是线程的安全问题? 上一篇 Java基础-多线程-①线程的创建和启动 我们说使用实现Runnable接口的方式来创建线程,可以实现多个线程共享资源: class Dog implements Ru ...
- java基础-多线程应用案例展示
java基础-多线程应用案例展示 作者:尹正杰 版权声明:原创作品,谢绝转载!否则将追究法律责任. 一.两只熊,100只蜜蜂,蜜蜂每次生产的蜂蜜量是1,罐子的容量是30,熊在罐子的蜂蜜量达到20的时候 ...
- java基础-多线程二
java基础-多线程二 继承thread和实现Runnable的多线程每次都需要经历创建和销毁的过程,频繁的创建和销毁大大影响效率,线程池的诞生就可以很好的解决这一个问题,线程池可以充分的利用线程进行 ...
- 细节!重点!易错点!--面试java基础篇(二)
今天来给大家分享一下java的重点易错点第二部分,也是各位同学面试需要准备的,欢迎大家交流指正. 1.字符串创建与存储机制:当创建一个字符串时,首先会在常量池中查找是否已经有相同的字符串被定义,其判断 ...
- java基础多线程之共享数据
java基础巩固笔记5-多线程之共享数据 线程范围内共享数据 ThreadLocal类 多线程访问共享数据 几种方式 本文主要总结线程共享数据的相关知识,主要包括两方面:一是某个线程内如何共享数据,保 ...
- Java基础——多线程
Java中多线程的应用是非常多的,我们在Java中又该如何去创建线程呢? http://www.jianshu.com/p/40d4c7aebd66 一.常用的有三种方法来创建多线程 新建一个类继承自 ...
- Java基础--多线程的方方面面
1,什么是线程?线程和进程的区别是什么? 2,什么是多线程?为什么设计多线程? 3,Java种多线程的实现方式是什么?有什么区别? 4,线程的状态控制有哪些方法? 5,线程安全.死锁和生产者--消费者 ...
- AJPFX: Java基础多线程(一)
多线程是Java学习的非常重要的方面,是每个Java程序员必须掌握的基本技能.本文只是多线程细节.本质的总结,并无代码例子入门,不适合初学者理解.初学者学习多线程,建议一边看书.看博文,以便写代码尝试 ...
随机推荐
- Junit4 测试代码
Junit4 测试代码 import org.junit.Test; import org.junit.runner.RunWith; @RunWith(SpringJUnit4ClassRunner ...
- Android布局——单复选框(今天上课的内容总结下)
怎么感觉最近补充的都是监听器的内容,今天学长提了一个新的监听器,看起来很牛批(因为很长) // 添加文本更改的监听器, TextWatcher是监听器的回调接口 text.addTextChanged ...
- 023_go语言中的通道
代码演示 package main import "fmt" func main() { messages := make(chan string) go func() { mes ...
- 07-NABCD项目分析
时 间:2020.3.31 参加人员:向瑜.赵常恒.刘志霄 讨论记录内容: NABCD模型 ·N(need)-向瑜 你的创意解决了用户的什么需求? 1. 随时随地记录个人收支的明细,清楚明白的知 ...
- 通过java程序(JSch)运行远程linux主机上的shell脚本
如果您看完文章之后,觉得对您有帮助,请帮我点个赞,您的支持是我不竭的创作动力! 如果您看完文章之后,觉得对您有帮助,请帮我点个赞,您的支持是我不竭的创作动力! 如果您看完文章之后,觉得对您有帮助,请帮 ...
- java_内部类、匿名内部类的使用
内部类 将一个类A定义在另一个类B里面,里面的那个类A就称为内部类,B则称为外部类. 内部类的分类 成员内部类,类定义在了成员位置 (类中方法外称为成员位置) 局部内部类,类定义在方法内 成员内部类 ...
- 2020-04-07:假如你们系统接收十几种报文,用什么方式对应的各自的service,总不能都用if-else判断吧
福哥答案2020-04-08: 策略,工厂.
- vue跳转页面问题记录
跳转到别的页面带参数 const space = this.pageHelperspace['search'] = this.searchconst query_params = Object.ass ...
- webpack打包原理
什么是 webpack ? 本质上,webpack 是一个现代 JavaScript 应用程序的静态模块打包器(module bundler).当 webpack 处理应用程序时,它会递归地构建一个依 ...
- 【期外】(二)还是N皇后动画演示
题目:n皇后题目 题解:n皇后题解 演示: