1. 程序和进程、文件的区别?
文件:本地磁盘中的资源
程序:属于文件,是静态资源,但是是一种可执行的资源
进程:通过程序运行,被操作系统管理的
2. 操作系统的运行方式:
单核CPU:采用的是时间片轮转调度的方式 并发
多核CPU:在同一个时间点上,有多个进程同时运行 并行
3. 进程的状态
创建、就绪态、运行态、终止、阻塞态
阻塞态不能直接转变为运行态
4. 进程和线程的区别
- 进程是系统分配资源的最小单位 线程是系统调度的最小单位
- 线程之间是可以共享资源的
- 线程之间的创建、切换 比进程快
5. 创建线程的方法
- 继承
Thread
类 - 实现
Runnable
接口 - 匿名类创建
Thread
子类对象 - 匿名类创建
Runnable
子类对象
6. 中断线程的方法
-
. 通过
Thread
对象调用interrupted()
方法 清除中断标志 -
.
thread
接收中断消息的方式 -
. 1.
thread.interrupt()
-
. 2.
thread.isIntereupted()
: 不清除中断标志位 -
. 3.如果线程调用
wait()、join()、sleep()
等方法而阻塞挂起,则以中断异常的形式通知,清除中断标志位
7. 线程的等待
- 当前线程在代码执行的时候,遇到
join()
,当前线程就阻塞等待(满足一定条件时),当前线程继续运行
一定条件指的是:
传入的时间到了、线程引用的对象执行完毕 - 使用
activeCount()+yield()
结合
8. 线程的状态
9. sleep() 和 wait() 的区别
- sleep() 方法没有释放锁,而wait() 方法释放了锁
- 两者都可以暂停线程的运行
- wait() 通常用于线程间交互、通信,sleep() 通常被用于暂停线程
- wait() 方法调用后,不会自己苏醒,需要别的线程自动调用同一对象上的 notify() 或者 notifyAll() 方法,sleep() 方法执行完成后自动苏醒
10. 线程不安全的原因
- 原子性
一个操作或者多个操作时,要么全部的代码执行中没有被任何因素打断,要么不执行 - 可见性
主内存 - > 工作内存(修改)- > 主内存
修改共享变量时,立即同步到主内存中,并且使得该修改其他线程可见 - 有序性
禁止指令重排序
11. synchronized关键字
- synchronized关键字是解决多线程之间访问资源的同步性,可以保证被它修饰的方法或者代码块在任意时刻只有一个线程在执行
- synchronized关键字加在 static静态方法 和 synchronized(Class) 代码块上都是给类加锁,加载实例代码块或者方法上,是给类对象加锁
12. 单例模式
双重校验锁 是使用了volatile关键字 和 synchronized关键字修饰
单例模式:
- 使用了volatile关键字修饰变量
- 私有的构造方法
- 使用双重校验锁保证线程安全
- 饿汉模式
public class Singleton{
private static Singleton singleton = null;
private Singleton(){}
public synchronized static Singleton getInstance(){
if(singleton == null){
singleton = new Singleton();
}
}
}
- 懒汉模式
public class Singleton{
// 变量使用volatile 可以保证可见性,重排序
private static volatile Singleton singleton = null;
private Singleton(){}
public static Singleton getInstance(){
// 提高效率
if(singleton == null){
synchronized(Singleton.class){
// 为了保证单例,返回的是同一个对象
if(singleton == null){
// 1. 分配内存空间
// 2. 初始化对象
// 3. 赋值给变量
singleton = new Singleton();
}
}
}
}
}
13. 多线程的特点、应用场景
- 安全 - > 共享变量存在线程安全问题
- 效率 - > 创建线程比较耗时,多个线程并发执行,系统调度线程 (线程数,单个任务量都会影响)
- 应用场景:
1.适合单个任务量比较耗时或者任务数量比较多
2.并发: 适合阻塞式代码会导致后边的代码无法执行,可 以使用多线程
14. 线程池
创建线程池的参数设置:
- 核心线程数(正式员工)
- 最大线程数(正式+临时)
- 时间数量(临时工空闲时间)
- 时间单位
- 阻塞队列(存放任务的数据结构)
- 创建Thread线程的工厂类 没有提供的话,就使用默认的方式
- 拒绝策略
CallerRunsPolicy:线程自己执行
AbortPolicy:抛异常
DiscardPolicy:从阻塞队列队尾丢弃任务
DiscardOldestPolicy:从阻塞队列队头丢弃任务
15. 乐观锁和 悲观锁
- 乐观锁: 每次拿数据的时候都认为别的线程不会修改这个数据,所以不会上锁,但是在更新的时候,会判断别的线程有没有更改过这个数据,它适用于读操作多的场景
- 悲观锁:总是假设最坏的时候,每次拿数据的时候都认为别人会修改,所以每次拿数据时都要上锁,这样第二个线程想拿到这个数据,会一直阻塞,直到第一个释放锁,第二个才能获取到锁,synchronized锁就是悲观锁
- 乐观锁问题:不能处理所有的问题,会引入一定的系统复杂度
- 悲观锁问题:总是需要竞争锁,有许多线程会有状态的转变,性能太差
16. 自旋锁
一直在抢锁,没抢到就死等
问题:线程在消耗CPU资源,长期在做无用功
17. CAS
- 翻译过来是 比较并交换
- 使用场景是 执行时间快
- 乐观锁是一种思想,那CAS就是乐观锁的实现
- 当多个线程尝试使用CAS同时更新同一个变量时,只有一个线程可以更新到变量的值,其他的线程都会失败,但是其他线程不会挂起,而是被告知失败,并可以再次尝试
- CAS操作包含3个操作数,内存位置值V,预期值A,修改值B,如果v和A是匹配的,那么就将v的值改为B,否则不会做任何操作,无论哪种情况 都会返回该位置的值
存在的问题:ABA问题
产生的原因:当前线程拷贝主内存到工作内存进行修改的时间段,其他线程把主内存中变量的值 A - > B - > A,相当于已经修改过了,但是当前线程不知道,还在改
解决方法:加入版本信息
JDK中的API:
1.concurrent.atomic:原子性并发下的包
2.synchronized中,多个线程不同时间点执行同步代码块时,jdk优化会采取CAS
3.ConcurrentHashMap实现,put操作,节点是空,采取CAS
18. synchronized实现原理
底层实现是基于对象内部的监视器锁(Monitor),分别使用
monitorenter
和monitorexit
指令完成,wait和notify
也依赖于monitor对象,所以要在synchronized 内使用,monitorenter
指令在编译为字节码后插入到同步代码块的开始位置,monitorexit 指令在编译为字节码后插入到方法结束处和异常处,保证每个
monitorenter 必须有对应的 monitorexit
JVM对synchronized的锁优化方案:根据不同场景,使用不同的锁机制
1.偏向锁:针对同一个线程再次申请以持有的对象锁,实现原理:CAS
2.轻量级锁:大概率在同一个时间点,只有线程申请对象锁,实现原理:CAS
3.重量级锁:大概率在同一个时间点,多个线程竞争同一个对象锁,实现原理是操作系统的mutex锁
缺点:涉及到系统调度,用户态内核态的转换,开销大
synchronized锁只能升级,不能降级
其他优化方案:
1.锁粗化:一个线程对同一个对象锁反复获取释放的操作,中间没有其他受影响代码时,可以合并为一个锁
2.锁消除:临界区代码中,没有对象逃逸出当前线程,说明线程本身就是安全的,可以删除锁
19. 创建线程的三种方法
- 继承 Thread 类
- 实现 Runnable 接口
- 实现 Callable 接口,使用 FutureTask 执行
20. synchronized 与 ReentrantLock的区别
- 两者都是可重入锁:自己可以再次获取自己的内部锁
- synchronized 依赖 JVM 而 ReentrantLock 依赖于API
- ReentrantLock 比 synchronized 增加了一些高级功能:如 等待可中断、可实现公平锁、可实现选择性通知
21. volatile关键字
把一个变量声明为 volatile ,这就指示 JVM,这个变量是可变的,每次使用他要从主内存中获取
volatile关键字 的主要作用是, 保证变量的可见性,防止指令重排序
22. synchronized 与 volatile 关键字的区别
- 两者的关系是互补的,volatile是线程同步的轻量级实现,所以性能肯定好,但是volatile只能用于变量,而synchronized 可以用于修饰方法和代码块
- 多线程访问 volatile 不会发生阻塞,而synchronized可能会发生阻塞
- volatile 只能保证 可见性,不能保证原子性,而 synchronized 都能保证
- volatile解决的是 变量在多个线程之间的可见性,而 synchronized 解决的是 多个线程访问资源的同步性
23. execute() 与 submit() 区别
- execute() 方法用于提交 不需要返回值的任务,所以无法判定任务是否被线程池执行成功
- submit() 方法用于提交 需要返回值的任务,线程池会返回一个 Future对象,通过这个可以判断 线程池是否执行成功
24. 原子类
AtomicInteger 的实现原理是:
主要利用的是CAS+volatile+native方法来保证原子操作,避免了synchronized的高开销,CAS是使用期望的值和原本的值做一个比较,相等就更新成新的值,UnSafe类的 objectFieldOffset() 方法是一个本地方法,这个方法是用来获取原本的值的内存地址,value是一个volatile变量,在内存中可见,因此JVM可以保证任何时刻线程都拿到的是该变量最新的值
25. 线程同步的方式有哪些
- 同步方法:synchronized
- 同步代码块:synchronized
- 使用volatile实现:volatile
- 使用重入锁实现:ReentrantLock
- 使用局部变量:TreadLocal
- 使用原子变量:AtomicInteger
26. ThreadLocal原理
每个Thread中都具备一个ThreadLocalMap,而ThreadLocalMap 可以存储以 ThreadLocal 对象为key,ThreadLocal对象调用set()的值 为value,ThreadLocalMap 是 ThreadLocal 的静态内部类
存在的问题:内存泄漏
ThreadLocalMap中使用的 key 为 ThreadLocal的弱引用,而value是强引用,所以,如果ThreadLocal没有被外部强引用的情况下,在垃圾回收的时候会把key 清理掉,而value 还在,这样一来,ThreadLocalMap会出现key为null的键值对,如果不采取措施,那value会一直存在,无法被GC回收,就有可能导致内存泄漏,ThreadLocalMap已经考虑了这种情况,在调用set()、get()、remove() 方法的时候,会清理掉 key为null的情况,所以用完Threadlocal后,最好手动调用remove()
27. AQS 原理
AQS核心思想是:如果被请求的共享资源空闲,则将当前请求资源的线程设置为有效的工作线程,并且将共享资源设置为锁定状态,如果被请求的共享资源被占用,那么就将获取不到锁的线程加到双端队列中,AQS使用CAS对该同步状态进行原子操作实现对其值的修改
AQS提供的方法有3种:
- 独占式获取与释放同步状态:只允许一个线程获取到锁
- 共享式获取与释放同步状态:一定数量的线程共享式获取到锁
- 查询同步队列中等待线程情况
信号量
Semaphore:允许多个线程同时访问,synchronized和ReentrantLock都是一次只允许一个线程访问某个资源,而Semaphore可以指定多个线程同时访问某个资源
倒计时器
CountDownLatch:是一个同步工具类,用来协调多个线程之间的同步,通常用来控制线程等待,它可以让某一个线程等待直到倒计时结束,在开始执行
可重入互斥锁
ReentrantLock: 允许自己再次获取自己的内部锁,可以指定公平锁还是非公平锁
公平锁:先等待的线程先获得锁
非公平锁:直接抢,谁抢到就是谁的
28. Lock 体系
Lock锁的实现原理就是 AQS,调用lock()方法设置当前线程的同步状态,并在队列中保存线程及线程的同步状态,调用unlock()方法出队列
29. Condition
线程间通信:(和synchronized.wait() 相似)
1.通过Lock对象.newCondition() 获取Condition对象
2.通过Condition对象.await() 阻塞等待,并释放锁
3.通过Condition对象.signal() / signalAll() 通知之前等待的线程
30. 什么是线程死锁?
一个线程等待另外一个线程执行完毕后才能继续执行,但如果现在相关的几个线程彼此都在等待着,那么就会造成死锁
- 线程死锁产生的条件:
- 互斥:该资源任意时刻只由一个线程占用
- 请求与保持:一个线程因请求资源而阻塞,对已获得的资源保持不放
- 不可抢占:线程以获得的资源在未结束之前,不能被别人剥夺,只能等自己使用完毕后释放
- 循环等待:若干个进程形成 头尾相接的循环资源等待
31. 如何避免死锁
1.资源一次性发放
2.可剥夺资源:在线程满足条件时,释放掉已有的资源
3.资源有序分配:系统为每类资源赋予一个编号,每个线程按照编号递请求资源,释放则相反
32. 如何检测死锁
使用jdk的监控工具,比如jconsole、jstack查看进程的状态
33. ConcurrentHashMap
- HashMap:线程不安全的,JDK1.7 基于数组+链表,JDK1.8 基于数组+链表+红黑树
- HashTable:线程安全的,基于数组+链表,全部方法都是基于synchronized加锁,效率低
- ConcurrentHashMap:线程安全的
- JDK1.7:基于数组+链表,本质上是基于Segment分段锁技术,继承了ReentrantLock,不同的Segment之间多线程可以并发操作,同一个Segment使用Lock加锁
- JDK1.8:基于数组+链表+红黑树,本质上使用CAS+synchronized加锁实现线程安全,不同节点多线程可以并发操作,如put操作,同一个节点,如果为空,使用CAS,如果不为空使用synchronized加锁
- 读写分离:多线程对不同Node/Segment的插入删除是可以并发执行的,对同一个Node/Segment的写操作是互斥的,读操作都是无锁操作,可以并发执行