Appearance
1.什么是进程、线程、协程?
- 进程:本质上是一个独立执行的程序,例如QQ、微信等
- 线程:操作系统进行运算调度的最小单位,一个进程中可以并发多个线程,每条线程执行不同的任务
- 协程:轻量级的线程,也称之为用户级别的线程就叫协程,一个线程可以多个协程
2.并发和并行的区别?
- 并发:指的是在同一时间段内,多个任务交替执行
- 并行:指的是在同一时间点上,多个任务同时进行执行
- 并发本质上是一个CPU快速切换处理多个任务,并行是多个CPU同时处理多个任务
3.创建线程有几种方式?
- 继承Thread
- 实现Runnable接口
- 使用Callable和FutureTask
- 使用线程池
- Spring的定时任务等…… 注:哪种方法最常用?2和4方法常用。详见创建线程的几种方式
4.线程的常见方法sleep/yield/join,wait/notify/notifyAll, 分别解释下
- 属于线程Thread的方法
sleep
:让线程暂缓执行,等待预计时间之后再恢复;交出CPU使用权但不会释放锁,线程进入阻塞状态,结束后进入就绪状态。yield
:暂停当前线程的对象执行权,去执行其他线程,交出CPU使用权,不会释放锁;但是它不会进入阻塞状态,而是一直处于就绪状态。join
:让调用join方法的线程先执行完毕,然后在执行其他线程
- 属于Object的方法
wait
:让线程进入等待状态,直到notify
或notifyAll
通知才会唤醒,某线程调用此方法会释放锁;notify
:随机从等待队列中通知一个持有相同锁的线程notifyAll
:唤醒全部线程
- 注意:sleep不会释放锁,而wait会释放锁
5.你能画出多线程状态是如何流转的吗?
6.你能举例说明几个业务代码使用多线程的例子吗?
- 发送短信使用多线程
- 调用第三方接口,比如消息推送,推送到个手机厂商
- 将日志持久化使用多线程
- 上面3个主要是异步任务,还有一些同步任务,例如定时备份数据库
- 分布式计算,主要是大数据的离线计算
- Netty网络编程通过多线程发布消息队列消息
7.你能想到哪些数据结构不是线程安全的?
ArrayList、HashMap、LinkedList
8.那么哪些是线程安全的?
首先可以加锁,然后jdk内置了许多线程安全的数据结构。
- ConcurrentHashMap
- ConcurrentLinkedDeque
- ConcurrentLinkedQueue
- ConcurrentSkipListMap
- ConcurrentSkipSet
- CopyOnWriteArrayList
- CopyOnWriteArraySet
9.聊聊volatile关键字,它和synchronized有什么区别?
- volatile关键字保证了共享变量的可见性,使得其他线程能立即看到其值的变化,避免脏读;相当于轻量级的synchronized
- volatile:保证可见性,但是不能保证原子性
- synchronized:保证可见性,也保证原子性
10.使用volatile关键字有什么特点或注意事项?
- 不能修饰写入操作依赖当前值的变量,比如num++、num=num+1,因为其本质不是原子操作
- 由于禁止指令重排,所以JVM优化将不对其生效,效率上有所削减
11.为什么会出现脏读?
- JMM规定所有的变量存在在主内存,每个线程有自己的工作内存
- 线程对变量的操作都在工作内存中进行,不能直接对主内存就行操作
- 使用volatile修饰变量,每次读取前必须从主内存属性最新的值,每次写入需要立刻写到主内存中
12.volatile可以避免指令重排,那么什么是指令重排?
不同线程之间的执行顺序可能会被改变,对于单线程来说这个改变是不会影响结果的,但是多线程共同拥有的全局变量,可能会在指令重排时被优化,导致执行顺序改变。
java
int a = 3 //1
int b = 4 //2
int c =5 //3
int h = a*b*c //4
比如如上四行代码,1234和2314的执行结果是相同的,所以jvm在某些条件下的优化会修改代码的执行顺序,对于单线程来说是不影响的。
13.并发编程的三要素是什么?
- 原子性:操作要么全部完成,要么不完成,没有中间状态;不存在上下文切换;
- 原子操作:int n = 1
- 非原子操作:n++
- 可见性:一个线程对共享变量的修改能够被其他线程立即感知到。
- 有序性:程序执行的结果按照一定的顺序来保证。
14.如何把多步操作变成原子操作?
- 获取锁对象,操作结束后释放锁
- 将操作封装到方法中,对方法使用
synchronized
java
public class XdTest {
private int num = 0;
//使用lock,每个对象都是有锁,只有获得这个锁才可以进行对应的操作
Lock lock = new ReentrantLock();
public void add1(){
lock.lock();
try {
num++;
}finally {
lock.unlock();
}
}
//使用synchronized,和上述是一个操作,这个是保证方法被锁住而已,上述的是代码块被锁住
public synchronized void add2(){
num++;
}
}
15.你对CAS知道多少,直到存在的ABA问题吗?
CAS是一种乐观锁先比较再替换,CAS修改时会先比较内存位置的值与预期值是否相等,如果不相等修改失败交给线程做其他处理。
ABA问题:修改后的值和CAS预期值相同,也就是该内存虽然被修改过,但修改的值相同;解决办法是添加一个版本号字段,不止比较值还比较版本号。
16.能不能谈谈你对线程安全的理解?
方法在并发环境被调用时能够正确处理多个线程之间的共享变量,使程序功能正确完成。
17.synchronized是如何实现的?
- 每个Java对象都有一个内置的锁(也称为监视器锁或互斥锁)。
- 当线程进入一个被synchronized修饰的方法或代码块时,它会尝试获取该对象的锁。
- 如果锁没有被其他线程占用,则当前线程获取到锁,可以执行被保护的代码。
- 如果锁已被其他线程占用,当前线程将被阻塞,直到锁被释放。
- 当线程执行完被保护的代码后,会释放锁,让其他线程可以获取锁并执行。
18.如何理解AQS?
- 在AQS内部,通过维护一个FIFO队列和一个volatile的int类型的state变量,实现了对象锁的占有和释放。
- 当state=1时表示锁已被占有,通过CAS操作修改state变量的值。
- 对于加锁失败的线程,会被封装成一个Node节点并排队在队列尾部。
19.什么是Unsafe?
它可以直接操作内存或执行一些特殊操作的方法,它执行硬件级别操作绕过了Java语言的安全检查机制;它并不希望被一般的开发者调用,不可以直接new只能通过反射获取对象。
20.synchronized和reentrantLock区别?
- 二者都是可重入锁
- synchronized是内置的,而reentrantLock通过java代码实现
- reentrantLoc只能手动释放锁
- synchronized只能是非公平锁,ReentrantLock可以实现公平锁和非公平锁