基础问题
- 程序、进程 和线程
程序启动了就是一个进程
进程是系统资源分配和调度的独立单位
线程是进程的一个实体,是cpu分配和调度的基本单位,一个进程有多个线程,线程组之间共享进程的数据。
进程切换开销大,线程切换开销小
- 并发编程的三要素
可见性、原子性、有序性
- 并行和并发
并行:单位时间内,多个处理器或多核处理器同时处理多个任务
并发:一个时间段内,多个任务在同一个CPU核上,按细分的时间片交替执行
- 什么是多线程?多线程的优势和劣势
多个不同线程执行不同任务
优势:充分利用CPU,减少阻塞时间,提高系统吞吐率
劣势:带来线程不安全问题:脏读、幻读、数据修改丢失;上下文切换、内存泄露等问题;线程多,占用内存也多
- 什么是上下文切换
一个线程切换到另一个线程。需要保存上一个线程的执行信息,以便下次切换回来
- 线程的实现方式
1.继承Thread 2.实现Runnable 3.实现Callable 4.线程池
- 守护线程和用户线程的区别
用户线程还在执行,jvm就不会退出。守护线程随着jvm的退出而退出。
使用场景:后台任务可作为守护线程,需要jvm退出时跟着一起退出
典例:垃圾回收线程
- . windows和Linux如何查找哪个线程CPU利用率最高
linux:
1)ps -cf|grep java 找到CPU耗用最高的进程pid
2)top -H -p pid
windows看任务管理器
- 如何获取线程dump文件,如何在java中获取线程堆栈
js -pf|grep java获取进程id
jmap命令获取dump信息
jstack分析dump文件
- java如何保证多线程运行安全?
synchronized关键字、Lock锁实现、分布式锁实现
- 同步方法和同步块哪个更好?
同步块。同步块以外的代码可以异步执行
- 线程优先级
操作系统中线程分优先等级。优先级高的线程获得CPU资源概率大。数字越大,优先级越高。优先级具有继承性
- 线程运行时发生异常会怎么样
线程结束
- java线程数过多会怎么样
占用CPU内存多
大量线程竞争CPU资源,开销大
稳定性下降
- 线程调度策略
抢占式调度模型。优先级别高的线程先占用CPU
- 线程调度算法
优先级高的线程先运行,以下情况终止运行
调用wait或者yield
调用sleep方法
IO操作阻塞
时间片用完
- 线程调度器和时间分片
线程调度器:操作系统服务,负责给Runnable状态的线程分配CPU
时间分片:过程,把CPU分配给Runnable状态的线程的过程
线程创建
- 如何创建线程
1.继承Thread 2.实现Runnable 3.实现Callable 4.线程池
- 什么是Callable和Future
Callable实现线程,创建线程的接口。实现Callable接口的线程执行会有返回值,返回值由通过Future接口获取。
- Callable和Runnable区别
Callable 实现的线程,允许抛出异常,获取异常信息。有返回值。配合Future和FutureTask可以获取异步任务执行的结果
Runnable实现的线程没有返回值,只能运行时异常,无法捕获异常
- 线程的start和run方法的区别
run方法是线程体。线程里的方法。线程执行时运行的代码
通过start去启动线程。
run方法可重复调用,start只能调用一次
start无需等run方法里的代码执行完毕,可以去执行其他代码
- 什么是futureTask
表示异步运算的任务。FutureTask实现了Runnable接口,构造方法参数有Callable类型的参数,从而可以获取这个异步运算任务的执行结果,判断是否完成,取消任务等
线程状态
- 线程的几个状态
创建、就绪、执行、阻塞、消亡
创建线程,调用start方法,线程进入就绪状态;线程时间片用完也会进入就绪状态
线程拥有CPU控制权,开始执行
阻塞分几种:同步阻塞、等待阻塞和其他阻塞
同步阻塞:其他线程抢占CPU失败,CPU已被其他线程占用
等待阻塞:线程调用wait方法,放入等待队列,进入阻塞状态,被唤醒后要抢占CPU
其他阻塞:线程调用join方法,sleep方法,进入阻塞状态。等其他线程执行完,sleep时间到,进入就绪状态
线程通信问题
- sleep()和wait()的区别
sleep是Thread的方法,wait是Object的方法
sleep抱着资源睡觉,时间到了之后就会自动苏醒。wait需要用notify方法唤醒
用途:sleep用于暂停线程执行;wait被用于线程通信
sleep不释放锁,wait是否锁
- 为什么多线程需要用while不用if
因为if只会执行一次。会导致虚假唤醒
什么是虚假唤醒?只有部分线程唤醒有用,其他线程唤醒没用
- 为什么wait、notify要被定义在Object里面
java中任何对象都能作锁。方法用户等待或唤醒对象的锁或者唤醒线程
- 为什么wait、notify方法只能在同步方法或块里使用?
因为这两个方法是Object类的。这些方法需要获得对象的锁才能使用,因此只能通过同步去实现
- notify和notifyAll的区别
notify唤醒一个线程
notifyAll中唤醒所有线程
HotSpot虚拟机中notify唤醒第一个等待 的线程,notifyAll先唤醒最后一个等待的线程
- 为什么Thread的sleep、join、yield方法是静态的
如果不是静态的,实例方法可以在一个线程内强制使另一个线程sleep,yield
- 代码实现:两个线程,t1线程打印5次,t2线程打印10次。接着t1线程打印5次,t2线程打印10次。这样循环10次
封装打印5次和10次的方法
1)t1线程for循环10次,调用print5方法。每次执行完print5,就调用notify,再调用wait阻塞自己
t2线程一开始先调用wait阻塞自己。for循环10次,调用print10方法,每次执行完就调用notify,然后再调用wait阻塞自己。
注意!t1for循环完,要再调用一个notify,否则,最后一次打印没人唤醒,程序无法停止
2)用LockSupport,t1线程先打印完unpark t2线程,再park自己
t2线程先park自己,等待t1unpark t2,打印之后,再park自己
- 代码实现:生产者消费者,t1线程生产一个,t2线程消费一个。来10轮
synchronized方法
ReentrantLock,condition方法
关键字
synchronized
- 什么是synchronized
jvm同步机制,java内部监视锁。底层:对象头+Monitor对象。指令monitorEnter、monitorExit;每个对象对应一个monitor对象,monitor包含两个队列:waitSet、entryList,owner变量,计数器
抢占cpu的线程放到entryList队列,占用cpu的线程,owner置为当前线程,计数器加1 ;
线程释放资源,owner置为null,计数器-1
线程被阻塞挂起,放入到waitSet队列。
- synchronized使用
同步块、同步方法
- synchronized锁的对象是什么
类、对象、方法
volatile
- 什么是volatile
java轻量级同步机制。保证共享变量的可见性
- volatile特性
可见性、有序性
- volatile底层
底层汇编语言,加了内存屏障
锁机制
- 乐观锁和悲观锁
乐观锁:提交的时候才上锁;CAS、加版本号
悲观锁:一开始就上锁。synchronized,ReentrantLock
- 可重入锁ReentrantLock
可重入锁,一个线程在获取锁后,可以再次获取自己的内部锁,避免死锁
ReentrantLock 是实现Lock接口的一个类。底层CAS+AQS
支持公平锁和非公平锁
- 读写锁、独占锁、共享锁、排他锁
ReentantReadWriteLock,读写锁。实现读写分离.是ReentrantWriteLock接口的具体实现
- 自旋锁
不加锁。等待的线程不阻塞,而是循环判断,满足条件得到锁。通过CAS
- 锁升级
无锁->偏向锁->轻量级锁->重量级锁
锁对象的对象头有一个threadId字段,在第一次访问的时候,字段为空,jvm让其持有偏向锁,threadId置为该线程id。再次进入的时候,先判断threadId是否与其线程id一致。否则就升级为轻量级锁。通过自旋一定次数获取锁,如果还是没有获得锁,那么就升级为重量级锁。
java6以后对锁进行了升级
目的:降低锁带来的性能消耗
- 锁的优缺点对比.
- 偏向锁 :加锁解锁无需额外消耗;线程之间如果存在锁竞争,会带来撤销锁的额外消耗;适合一个线程访问同步块
- 轻量级锁:线程不阻塞,提高响应速度;始终得不到锁竞争的线程,一直循环会给CPU带来开销;适合追求响应速度,同步代码块执行快的场景
- 重量级锁
不用自旋,CPU开销不大;线程阻塞,响应时间慢;追求吞吐量,同步代码块执行慢的场景
- synchronized和Lock、ReebtrantLock的区别
- synchronized是jvm层面的,是关键字,Lock是接口,ReentrantLock是类
- synchronized不能精确唤醒,ReentrantLock可以
- synchronized不能中断
- synchronized是非公平的。ReentrantLock可设置为公平锁
- synchronized可用于代码块、类、方法、变量,ReentrantLock只能用在代码块锁
- synchronized不用手动释放锁。ReentrantLock需要手动释放锁
- 锁机制不同:syn底层操作对象头的mark word,ReentrantLock是CAS+AQS实现
线程池
- 底层核心
ThreadPoolExecutor 是线程池的核心
Executor提供工厂创建不同类型的线程池:创建固定线程数的线程池、只有一个线程的线程池、可扩容带缓冲的线程池
Executor是一个接口,用来执行Runnable任务(调用executor方法)
ExecutorService是比Executor更广泛的一个接口,可以执行Runnable任务、Callable任务。有返回值。且它提供控制线程池的方法
ExecutorService是Executor的子接口
- 线程池的参数
核心线程数、最大线程数、多余的闲置线程存活时间,存活时间单位,阻塞队列,线程工厂,拒绝策略
- 为什么要使用线程池
线程复用,降低资源消耗
提高响应速度:任务到了可以不需要等待线程创建好才执行
有效控制并发量
提高线程可管理性
- 提交任务时,线程池已满怎么办?
阻塞队列使用LinkedBlockingQueue:继续加入等待执行
阻塞队列使用ArrayBlockingQueue:拒绝策略
并发容器
ConcurrentHashMap
- 线程安全且高效的HashMap实现
- 使用分段锁的思想
- 如何实现线程安全的?
- 1.6版本segment继承ReentrantLock充当锁,segment维护哈希列表的若干个桶
- 1.8 使用CAS和synchronized保证并发安全
CopyOnWriteArrayList
- 写时复制
- 适合读多写少的场景
- 劣势:
- 写的时候拷贝数组,消耗内存,如果原数组多会导致yong gc甚至full gc
- 不能做到实时读取。拷贝、新增数据均需要时间,读取的时候可能还是旧的数据
- 每次add或者set都需要拷贝数组,性能低
- 设计思想
- 读写分析
- 数据最终一致
- 另开辟空间,解决并发冲突
Collections.synchronizedList
阻塞队列-线程同步的工具
BlockingQueue
- 实现类
- ArrayBlockingQueue 数组结构组成的阻塞
- LinkedBlockingQueue 链表结构组成的阻塞队列
- SynchronousQueue 不存储元素的阻塞队列
- 什么是阻塞队列,为什么需要它?
队列满时插入元素会阻塞,队列空时取元素会阻塞的队列
常用于生产者消费者场景。socket客户端数据的读取和分析
CAS操作
- 比较并交换。循环获取数据,如果真实值和期望值不一致,就一直循环,直到一致就修改数据
- 缺点
- ABA问题
- 一直CAS失败,一直循环,CPU开销大
- 只能保证一个共享变量的原子操作
JMM模型
- 虚拟的,实际不存在的。只是一种规范
- 主内存和线程的工作内存
- 可见性、有序性、原子性
ThreadLocal
- 本地线程副本变量工具类。每个线程都创建一个ThreadLocalMap对象。每个线程读取自己内部的ThreadLocalMap里的数据。避免资源在多线程共享
- 场景:为每个线程分配一个jdbc连接。线程间的jdbc连接互不影响
- 问题:
引起内存泄露,因为key是弱引用,value却是强引用
解决:每次用完ThreadLocal,调用remove方法,清楚数据
线程池情况下,用完记得清理
死锁
- 原因
互斥条件
请求并保持
不可剥夺
循环等待
- 如何解决
按顺序去申请资源
一次性申请所有资源
进一步申请资源时如果请求不到资源,就主动释放当前占用的资源。设置超时时间