【java面试】线程篇
- 一、基础知识
- 1 线程与进程的区别
- 2 并行与并发的区别
- 3 创建线程的方式
- 4 线程包含了哪些状态,状态之间是如何变化的?
- 5 新建三个线程,如何保证他们按照顺序执行?
- 6、java中的wait和sleep方法的不同
- 7 如何停止一个正在运行的线程
- 二、线程并发安全
- 1 synchronized关键字的底层原理
- 1.1 synchronized关键字的使用
- 1.2 Monitor
- 1.3 总结
- 2 synchronized关键字的底层原理--进阶
- 1.1 Monitor重量级锁
- 1.2 轻量级锁
- 1.3 偏向锁
- 1.4 总结
- 3 JMM(java memory Model--Java内存模型)
- 4 CAS
- 4.1 基本概念
- 4.2 底层实现
- 4.3 乐观锁和悲观锁
- 4.4 总结
- 4 volatile
- 4.1 保证线程间的可见性
- 4.2 volatile禁止指令重排序
- 4.3 总结
- 5 AQS
- 5.1 基本概念
- 5.2 基本工作机制
- 5.3 总结
- 5 ReentrantLock实现原理
- 7 synchronized与Lock有什么区别?
- 8 死锁产生的条件
- 8.1 基本概念
- 8.2 死锁诊断
- 9 ConcurrentHashMap
- 10 导致并发程序出现问题的根本原因
- 三、线程池
- 1 线程池的核心参数
- 1.1 核心参数
- 1.2 执行原理
- 2 线程池中常见阻塞队列
- 3 如何确定核心线程数
- 4 线程池种类
- 5 为什么不建议Executor创建线程池
- 四、使用场景
- 1 线程池使用场景
- 2 es数据批量导入
- 3 数据汇总
- 4 异步调用
- 5 如何控制某个方法允许并发访问的数量
- 6 谈谈对TreadLocal的理解
- 6.1 set方法
- 6.2 get方法/remove方法
- 6.3 ThreadLoca内存泄漏问题

一、基础知识
1 线程与进程的区别
①进程
程序由指令
和数据
组成,但这些指令要运行,数据要读写,就必须将指令加载至CPU,数据加载至内存。在指令运行过程中还需要用到磁盘、网络等设备。进程就是用来加载指令、管理内存、管理IO的。
当一个程序被运行,从磁盘加载这个程序的代码至内存,这时就开启了一个进程
。
②线程
一个线程就是一个指令流,将指令流中的一条条指令以一定的顺序交给CPU 执行,一个进程之内可以分为一到多个线程。
③二者区别
- 进程是正在运行程序的实例,进程中包含了线程,每个线程执行不同的任务
- 不同的进程使用不同的内存空间,在当前进程下的所有线程可以共享内存空间
- 线程更轻量,线程上下文切换成本一般上要比进程上下文切换低(上下文切换指的是从一个线程切换到另一个线程)
2 并行与并发的区别
- 并发(concurrent)是同一时间应对(dealing with)多件事情的能力
- 并行(parallel)是同一时间动手做(doing)多件事情的能力
3 创建线程的方式
①继承Thread类
②实现runable接口
③实现Callable接口(适用于有返回值的)
④线程池创建线程
开启线程的方法?
- 继承Thread类
- 实现runnable接口
- 实现Callable接口
- 线程池创建线程(项目中使用方式)
使用tunable和callable都可以创建线程,它们之间有什么区别?
- Runnable接口run方法没有返回值
- Callable接口call方法有返回值,是个泛型,和Future、FutureTask配合可以用来获取异步执行的结果
- Callable接口的call()方法允许抛出异常;而Runnable接口的run()方法的异常只能在内部消化,不能继续上抛
在启动线程时可以使用run方法吗?run()和start()有什么区别?
- start():用来启动线程,通过该线程调用run方法执行run方法中所定义的逻辑代码。start方法只能被调用一次。(线程只能被开启一次)
- run():封装了要被线程执行的代码,可以被调用多次。
4 线程包含了哪些状态,状态之间是如何变化的?
线程之间包含了那些状态?
新建(NEW) 、可运行(RUNNABLE)、阻塞(BLOCKED)、等待( WAITING )、时间等待(TIMED_WALTING)、终止(TERMINATED)
状态之间如何转换?
5 新建三个线程,如何保证他们按照顺序执行?
join{}:等待线程结束
例如:t.join(),阻塞调用次方的的线程进入time_waiting,直到线程t完成后,此线程在继续执行。
notify()和notifyAll()有什么区别?
- notifyAll():唤醒所有wait的线程
- notify():只随机唤醒一个wait线程
6、java中的wait和sleep方法的不同
7 如何停止一个正在运行的线程
二、线程并发安全
1 synchronized关键字的底层原理
1.1 synchronized关键字的使用
Synchronized【对象锁】采用互斥的方式让同一时刻至多只有一个线程能持有【对象锁】,其它线程再想获取这个【对象锁】时就会阻塞住
未加锁:出现超卖的情况
加锁后:一个线程在扣票的过程中,另一个线程是无法去获取的
1.2 Monitor
- Owner:存储当前获取锁的线程的,只能有一个线程可以获取
- EntryList:关联没有抢到锁的线程,处于Blocked状态的线程
- WaitSet:关联调用了wait方法的线程,处于Waiting状态的线程
1.3 总结
- Synchronized【对象锁】采用互斥的方式让同一时刻至多只有一个线程能持有【对象锁】
- 它的底层由monitor实现的,monitor是jvm级别的对象(C++实现),线程获得锁需要使用对象(锁)关联monitor
- 在monitor内部有三个属性,分别是owner、entrylist、waitset
- 其中owner是关联的获得锁的线程,并且只能关联一个线程;entrylist关联的是处于阻塞状态的线程; waitset关联的是处于Waiting状态的线程
2 synchronized关键字的底层原理–进阶
Monitor实现的锁属于
重量级锁
,你了解过锁升级
码?
- Monitor实现的锁属于重量级锁,里面涉及到了用户态和内核态的切换、进程的上下文切换,成本较高,性能比较低.
- 在JDK 1.6引入了两种新型锁机制:偏向锁和轻量级锁,它们的引入是为了解决在没有多线程竞争或基本没有竞争的场景下因使用传统锁机制带来的性能开销问题。
1.1 Monitor重量级锁
lock对象锁是怎么关联上Monitor的?
每个Java对象都可以关联一个Monitor对象,如果使用synchronized给对象上锁(重量级)之后,该对象头的Mark Word 中就被设置指向Monitor对象的指针
通过java对象的内存结构来说明:
MarkWord
1.2 轻量级锁
在很多的情况下,在Java程序运行时,同步块中的代码都是不存在竞争的,不同的线程交替的执行同步块中的代码。这种情况下,用重量级锁是没必要的。因此JVM引入了轻量级锁的概念。
1.3 偏向锁
轻量级锁在没有竞争时(就自己这个线程),每次重入仍然需要执行CAS操作。Java 6中引入了偏向锁来做进一步优化:只有第一次使用CAS将线程ID设置到对象的Mark Word头,之后发现这个线程ID是自己的就表示没有竞争,不用重新CAS。以以后只要不发生竞争,这个对象就归该线程所有
1.4 总结
Java中的synchronized有偏向锁、轻量级锁、重量级锁三种形式,分别对应了锁只被一个线程持有、不同线程交替持有锁、多线程竞争锁三种情况。
一旦锁发生了竞争都会升级为重量级锁
3 JMM(java memory Model–Java内存模型)
JMM(Java Memory Model)Java内存模型,定义了共享内
中多线程程序读写操作的行为规范
,通过这些规则来规范对内存的读写操作从而保证指令的正确性
- JMM(Java Memory Model)Java内存模型,定义了共享内存中多线程程序读写操作的行为规范,通过这些规则来规范对内存的读写操作从而保证指令的正确性
- JMM把内存分为两块,一块是私有线程的工作区域(工作内存),一块是所有线程的共享区域(主内存)
- 线程跟线程之间是相互隔离,线程跟线程交互需要通过主内存
4 CAS
4.1 基本概念
比对失败,重新读一份V到线程B的A中,设置阈值,多少次之后还没修改成功,就放弃修改。
4.2 底层实现
CAS 底层依赖于一个Unsafe类来直接调用操作系统底层的cAs指令
native修饰的本地方法,是系统提供的,不是java提供的,是由C或者C++实现的。
4.3 乐观锁和悲观锁
CAS是基于乐观锁的思想:最乐观的估计,不怕别的线程来修改共享变量,就算改了也没关系,我吃亏点再重试呗。
synchronized是基于悲观锁的思想:最悲观的估计,得防着其它线程来修改共享变量,我上了锁你们都别想改,我改完了解开锁,你们才有机会。
4.4 总结
4 volatile
请谈谈对volatile的理解?
volatile是一个关键字,一旦一个共享变量(类的成员变量、类的静态成员变量)被volatile修饰之后,那么就具备了两层语义:
①保证线程间的可见性
②禁止进行指令重排序
4.1 保证线程间的可见性
4.2 volatile禁止指令重排序
测试:
a.引入依赖
b.加注解
c.没有main方法无法执行,自己指定
d.查看执行结果
在y
上加volatile关键字运行结果:在X上不行。
4.3 总结
5 AQS
5.1 基本概念
5.2 基本工作机制
多个线程争抢资源如何保证原子性?
AQS是公平锁还是非公平锁?
既可以实现公平锁也可以实现非公平锁。
线程1释放锁之后,新来的线程5和等待队列的队首线程竞争锁
新的线程与队列中的线程共同来抢资源,是非公平锁
新的线程到队列中等待,只让队列中的head线程获取锁,是公平锁
5.3 总结
5 ReentrantLock实现原理
ReentrantLock翻译过来是可重入锁,相对于synchronized它具备以下特点:
- 可中断:
- 可以设置超时时间:超过时间没有获取到锁可以放弃获取锁
- 可以设置公平锁
- 支持多个条件变量:可以设置多个条件让线程进入等待状态
- 与synchronized一样,都支持重入
总结:
7 synchronized与Lock有什么区别?
8 死锁产生的条件
8.1 基本概念
8.2 死锁诊断
9 ConcurrentHashMap
10 导致并发程序出现问题的根本原因
导致并发程序出现问题的根本原因
(或者说:java程序中怎么保证多线程的执行安全)
java并发编程的三大特性:
- 原子性
- 可见性
- 有序性
三、线程池
1 线程池的核心参数
说一下线程池的核心参数
(线程池的执行原理知道吗)
1.1 核心参数
1.2 执行原理
2 线程池中常见阻塞队列
3 如何确定核心线程数
4 线程池种类
5 为什么不建议Executor创建线程池
四、使用场景
1 线程池使用场景
线程池使用场景(CountDownLatch、future)
你做过的项目中哪里用到了多线程
2 es数据批量导入
3 数据汇总
4 异步调用
保存搜索记录,不能影响当前搜索的搜索结果。
5 如何控制某个方法允许并发访问的数量
6 谈谈对TreadLocal的理解
6.1 set方法
6.2 get方法/remove方法
6.3 ThreadLoca内存泄漏问题
java对象中四种引用类型:强引用、软引用、弱引用、虚引用