文章目录
- 并行和并发有什么区别?
- 线程和进程的区别?
- 守护线程是什么?
- 创建线程的三种方式
- Runnable 和 Callable 有什么区别?
- 线程有什么状态?
- sleep() 和 wait() 有什么区别?
- notify() 和 notifyAll() 有什么区别?
- 线程的 run() 和 start() 有什么区别?
- 创建线程池的方式有哪些?
- 线程池有哪些状态?
- 线程池中的 submit() 和 execute() 方法有什么区别?
- Java 程序中如何保证多线程的运行安全?
- synchronized 底层实现原理是什么?
- 多线程中 Synchronized 锁升级的原理是什么?
- synchronized 和 volatile 的区别是什么?
- Atomic 的原理是什么?
- synchronized 和 Lock 的区别?
- synchronized 和 ReentrantLock 区别是什么?
- 什么是死锁?
- 如何防止死锁?
- ThreadLocal 是什么?
并行和并发有什么区别?
- 并行:多个处理器或者多核处理器同时处理多个任务。
- 并发:多个任务同时在一个 CPU 核上,按细分的时间片轮流(交替)执行,但从逻辑上看这些任务是同时执行的。
线程和进程的区别?
一个程序下至少有一个进程,一个进程下至少有一个线程。进程下的线程数量越多,程序的执行速度就越快。
守护线程是什么?
这是是一种运行在后台的特殊进程。它独立于控制终端并周期性的执行某个任务或者等待处理某些发生的事件(如Java 中的垃圾回收线程…)。
创建线程的三种方式
- 继承 Thread 重写 run() 方法
- 实现 Runnable接口
- 实现 Callable 接口
Runnable 和 Callable 有什么区别?
前者没有返回值,后者有;
后者可看作前者的补充。
线程有什么状态?
- new —— 未启动
- runnable —— 正执行中
- blocked —— 阻塞的(被同步锁 / IO 锁阻塞)
- waiting —— 永久等待
- timed_waiting —— 等待指定的时间重新被唤醒
- terminated —— 执行完成
sleep() 和 wait() 有什么区别?
类不同:sleep()
来自 Thread 类,wait()
来自 Object。
释放锁:sleep()
不释放锁,wait()
释放锁。
用法不同:sleep()
到点便会自动回复,wait()
要使用notify()
或者notifyAll()
直接唤醒。
notify() 和 notifyAll() 有什么区别?
notify()
是唤醒一个线程,notifyAll()
是唤醒所有线程。
notify()
具体唤醒的是哪个线程这由虚拟机决定;notifyAll()
会将所有的线程由等待池移到锁池中,令其参与锁的竞争。不成功仍得留在锁池中等待锁的释放,继续执行直到成功。
线程的 run() 和 start() 有什么区别?
run()
用于执行线程运行时代码,并可重复调用;start()
用于启动线程,只能调用一次。
创建线程池的方式有哪些?
- ThreadPoolExecutor(): 最原始的线程池创建(最核心的)。
- newSingleThreadExecutor(): 它的工作线程数目被限制为 1,操作一个无界的工作队列。因此它保证了所有任务都是顺序执行,最多有一个任务处于活动状态,且不许使用者改动线程池实例,所以能避免它改变线程数目。
- newCachedThreadPool(): 它是用于处理大量短时间工作任务的线程池。它会试图缓存线程重用,当无缓存线程可用时,便创出新的工作线程;若线程限制时间超过60s会被终止并移出缓存;长时间闲置时,这种线程池不会消耗资源,其内部使用
SynchronousQueue
作为工作队列。 - newFixedThreadPool(int nThreads): 重用指定数目的线程,其背后是使用无界的工作队列,任何时候最多由 nThreads 个工作线程在活动。超过数目,将在工作队列中等待空闲线程出现,因工作线程退出少于数目,将会创建新的工作线程来弥补。
- newSingleThreadScheduledExecutor(): 创建单线程池,返回
ScheduledExecutorService
,能进行定时或者周期性的工作调度。 - newScheduledThreadPool(int corePooleSize): 同上类似,区别在于单一工作线程还是多个工作线程。
- newWorkStealingPool(int parallelism): java 8 才加入是个常被忽略的线程。它内部会构建 ForkJoinPool,利用 Work-Stealing 算法并行的处理任务,不保证顺序执行。
线程池有哪些状态?
- running:接受新任务,处理等待队列中的任务。
- shutdown:不接受新任务提交,但会处理等待队列中的任务。
- stop:不接受新任务提交,不处理等待队列列中的任务,中断正在执行任务的线程。
- tidying:销毁所有的任务,workCount 为 0,线程池的状态在转换为 tidying 状态时,会执行钩子方法 terminated() 。
- terminated:这个状态是在 terminated() 方法结束后。
线程池中的 submit() 和 execute() 方法有什么区别?
- submit():只能执行 Runnable 类型任务。
- execute():可执行 Runnable 和 Callable 任务。
Java 程序中如何保证多线程的运行安全?
- 安全类,如
java.util.concurrent
下的 - 自动锁
Synchonized
- 手动锁
Lock
Lock lock = new ReentrantLock();
lock.lock();
try() {
System.out.prinln("得到锁...");
} catch(Exception e) {
} finally {
System.out.prinln("释放锁...");
lock.unlock();
}
synchronized 底层实现原理是什么?
它是由一对monitorent / monitorexit
指令现实的,monitor 对象是同步的基本实现单元。
在 Java 6 前,monitor 的实现完全依靠操作系统内部的互斥锁,是因为需要在进行用户态到内核态的转换,因此同步操作是一个无差别的重量级操作,性能还很低。
后来 JVM 对此进行了改进,提供了三种 monitor 实现方式(常说的三种锁:偏向锁、轻量级锁、重量级锁)来提升其性能。
多线程中 Synchronized 锁升级的原理是什么?
在锁对象里有一个threadid
字段,在第一次访问时threadid
为空,JVM 让其持有偏向锁并将threadid
设为其线程 id,再进入时会先判断一下 threadid
是否与其线程 id 一致,相同便能直接使用此对象,不相同则将偏向锁升级为轻量级锁,令其通过一定的自旋循环次数来获取锁,若还不行就只能将轻量级锁升级为重量级锁。
目的:锁升级它是为了降低锁带来的性能消耗。
在 Java 6 后优化了
synchronized
的实现方式,使用了偏向锁升级为轻量级锁再升级重量级锁的方式。
synchronized 和 volatile 的区别是什么?
前者:
- 修饰类、方法、代码段
- 能保证变量的修改可见性和原子性
- 可能会造成线程堵塞
后者:
- 修饰变量符
- 仅能实现变量的修改可见性,不能保证原子性
- 不会造成线程堵塞
Atomic 的原理是什么?
CAS —— Compare And Wwap
它主要是利用了 CAS 和 volatile 以及 native 方法来保证原子操作,以避免 synchronized 的高性能开销,提升执行效率。
synchronized 和 Lock 的区别?
前者:
- 能给类、方法、代码段加锁
- 使用简单,且无需手动获取锁和释放锁。当异常发生时会自动释放锁,不会造成死锁
- 无法知道有没有成功获取到锁
后者:
- 只能给代码块加锁
- 需要手动加锁和释放锁,若使用不当没有
unLock()
方法去释放锁便会造成死锁 - 能知道有没有成功获取锁
synchronized 和 ReentrantLock 区别是什么?
synchronized 前期的实现是比较低效的,与 ReentrantLock 相比大多数场景性能都相差较大。但在 Java 6 中对 synchronized 进行了许多的改变。
前者:
- 能给类、方法、代码块加锁
- 自动获取锁和释放锁
后者:
- 仅能给代码块加锁
- 必须手动获取锁和释放锁
- 使用灵活,但需要释放锁的配合动作
什么是死锁?
设线程 A 持有独占锁 a,线程 B 持有独占锁 b。
这时线程 A 想尝试去获取独占锁 b,同时线程 B 也想获取独占锁 a。由于AB两线程相互持有对方需要的锁,从而发生了阻塞现象,这便是死锁。
如何防止死锁?
- 尽量使用
java.util.concurrent
并发类代替自己手写锁 - 尽量降低锁的使用粒度,尽量别令多个功能使用同一把锁
- 尽量减少同步代码块
- 尽量使用
tryLock(long timeout, TimeUnit unit)
的方法(ReentrantLock
、ReentrantReadWriteLock
),设置超时时间以便超时出现能及时防止死锁
ThreadLocal 是什么?
ThreadLocal —— 线程本地变量
它为每个使用该变量的线程提供了独立的变量副本,令每个线程都能独立的改变自己副本的同时不影响其它线程所对应的副本。
通常会在数据库连接和 session 管理等场景使用到。