1,什么是线程和进程?线程与进程的关系,区别及优缺点?
进程是一次程序的执行。
执行一次程序包含多个任务,线程是一次任务的执行。
进程是资源分配的最小单位,线程是CPU调度的最小单位
提示:可以从从 JVM ⻆度说进程和线程之间的关系
优缺点:线程执行开销小,但不利于资源的管理和保护;而进程正相反
2,为什么要使用多线程呢?
- 从计算机⻆度来说主要是为了充分利⽤多核 CPU 的能⼒。
- 从项⽬⻆度来说主要是为了提升系统的性能
3,说说线程的生命周期和状态?
- New(初始化状态)
- Runnable(就绪状态)
- Running(运行状态)
- Blocked(阻塞状态)
- Terminated(终止状态)
4,什么是线程死锁?如何避免死锁?如何预防和避免线程死锁?
两个或两个以上线程,因争抢资源造成互相等待的现象,若无外力作用,则会一直持续下去。
# 死锁代码示例:
import java.util.concurrent.TimeUnit;
public class DeadLockDemo
{
public static void main(String[] args)
{
final Object objectLockA = new Object();
final Object objectLockB = new Object();
new Thread(() -> {
synchronized (objectLockA)
{
System.out.println(Thread.currentThread().getName()+"\t"+"自己持有A,希望获得B");
//暂停几秒钟线程
try { TimeUnit.SECONDS.sleep(1); } catch (InterruptedException e) { e.printStackTrace(); }
synchronized (objectLockB)
{
System.out.println(Thread.currentThread().getName()+"\t"+"A-------已经获得B");
}
}
},"A").start();
new Thread(() -> {
synchronized (objectLockB)
{
System.out.println(Thread.currentThread().getName()+"\t"+"自己持有B,希望获得A");
//暂停几秒钟线程
try { TimeUnit.SECONDS.sleep(1); } catch (InterruptedException e) { e.printStackTrace(); }
synchronized (objectLockA)
{
System.out.println(Thread.currentThread().getName()+"\t"+"B-------已经获得A");
}
}
},"B").start();
}
}
死锁的四个必要条件
- 互斥
- 请求和保存
- 不可剥夺
- 环路等待
死锁预防和打破:破坏死锁的必要条件中的一个或多个
如何检测死锁:
- 命令 jps -l
jstack 进程编号 - 图形化 jconsole
5,synchronized 关键字
synchronized 关键字的作⽤和用法
作用:被synchronized修饰的对象或方法任意时刻只能有一个线程访问。
用法:修饰普通方法;修饰静态方法;修饰代码块
synchronized 关键字的底层原理
JDK1.6 之后的 synchronized 关键字底层做了哪些优化。 synchronized 锁升级流程。
synchronized 和 ReentrantLock 的区别。
synchronized是属于原生语法层面的互斥,ReentrantLock是API层面的互斥。
ReentrantLock的粗细度和灵活度要高于synchronized。
synchronized 和 volatile 的区别。
1)volatile本质是告诉JVM当前变量在寄存器中的值是不确定的,需要从主存中读取。synchronized则是锁定当前变量,只有当前线程可以访问该变量,其它线程被阻塞。
2)volatile仅能使用在变量级别,synchronized则可以使用在变量、方法。
3)volatile仅能实现变量修改的可见性,而synchronized则可以保证变量修改的可见性和原子性。
4)volatile不会造成线程阻塞,synchronized会造成线程阻塞。
6,并发编程的三个重要特性
- 原子性
- 可见性
- 有序性
7,JMM 和 happens-before 原则。
JMM理念是屏蔽各种硬件和操作系统的内存访问差异,使java程序在各种平台下都能达到一致的访问效果。
happens-before原则
从JMM设计者的角度来分析,设计JMM需要考虑两个关键因素:
- 程序员希望内存模型易于理解和编程,最好是不要重排序复合人的思维,是个强内存模型。
- 编译器和处理器希望内存模型对它们的束缚越小越好,这样可以做更多的优化提高性能。
而happens-before就是上述因素的平衡点。
happens-before之8条
1.次序规则:
前一个操作的结果可以被后续的操作获取。
2.锁定规则:
一个unLock操作先行发生于后面((这里的“后面”是指时间上的先后))对同一个锁的lock操作;
3.volatile变量规则:
对一个volatile变量的写操作先行发生于后面对这个变量的读操作。
前面的写对后面的读是可见的,这里的“后面”同样是指时间上的先后。
4.传递规则:
如果操作A先行发生于操作B,而操作B又先行发生于操作C,则可以得出操作A先行发生于操作C;
5.线程启动规则:
Thread对象的start()方法先行发生于此线程的每一个动作
6.线程中断规则:
对线程interrupt()方法的调用先行发生于被中断线程的代码检测到中断事件的发生;
7.线程终止规则:
线程中的所有操作都先行发生于对此线程的终止检
8.线程终结规则:
一个对象的初始化完成(构造函数执行结束)先行发生于它的finalize()方法的开始
8,volatile 关键字
特性
- 可见性
- 禁重排
禁重排通过内存屏障实现。
JMM就将内存屏障插入策略分为4种
写:
1.在每个volatile 写操作的前面插入一个StoreStore 屏障
2.在每个volatile 写操作的后面插入一个StoreLoad屏障
读:
3.在每个volatile读操作的后面插入一个LoadLoad屏障
4.在每个volatile读操作的后面插入一个LoadStore 屏障
9,ThreadLocal 关键字
ThreadLocal为线程提供局部变量。
底层原理
ThreadLocal里面包含ThreadLocalMap,ThreadLocal的set方法,会先检查Map里面有没有以ThreadLocal作为key的数据,没有则创建以当前线程的ThreadLocal做key的map数据。
内存泄露问题
ThreadLocalMap 中使⽤的 key 为 ThreadLocal 的弱引用,⽽ value 是强引用;
ThreadLocal 没有被外部牵引的情况下,在垃圾回收的时候,key 会被清理掉,⽽ value 不会 被清理掉。这样⼀来, ThreadLocalMap 中就会出现key为null的Entry。假如我们不做任何措施 的话,value 永远⽆法被GC 回收,这个时候就可能会产生内存泄露。
使用场景
10,线程池
线程池有哪几种
- newCachedThreadPool
- newFixedThreadPool
- newScheduledThreadPool
- newSingleThreadExecutor
线程池优缺点
线程池重要参数
-
corePoolSize 核心线程数
-
maximumPoolSize 最大线程数
-
keepAliveTime 空闲线程存活时间
-
unit 时间单位
-
workQueue 工作队列
-
threadFactory 线程工厂
-
handler 拒绝策略
- CallerRunsPolicy 拒绝任务
- AbortPolicy 抛弃任务,抛出异常
- DiscardPolicy 直接抛弃任务
- DiscardOldestPolicy 抛弃队列中最早的任务,尝试把该任务加入
11,ReentrantLock 和 AQS
ReentrantLock类组合了Sync类,而Sync类继承了**AbstractQueuedSynchronizer(AQS)**类,所以ReentrantLock 实现加锁,解锁是通过AQS实现的。
- state表示资源状态,默认为0
- OwnerThread表示持有资源的线程,默认为null
- CLH存放等待的线程。
ReentrantLock加锁,会将 state=1,OwnerThread设置为自己,要是有其他线程来访问,会将线程放入CLH队列。解锁,会将 state=0,OwnerHtread置null,在通知CLH队列的线程。
12,CAS 了解么?原理?什么是 ABA 问题?ABA 问题怎么解决?
比较并交换。
原理:CAS操作有依赖三个值,内存中的值,预估旧值,要修改的新值。内存值和旧值相等,就修改。
ABA问题:一个线程将变量修改后,又改回去,另一个线程对变量操作时,不知道变量被修改过。
解决ABA问题
AtomicStampedReference:AtomicStampedReference在构建的时候需要一个类似int型的变量stamped,每一次需改,stamped都会变化,所以能够解决ABA问题。