Java多线程
简介
本文总结了面试复习Java多线程的一些基础题
https://www.bookstack.cn/books/java-concurrency-note 参考书籍
参考必看:
https://www.cnblogs.com/crazymakercircle/p/13903850.html
正文
什么是线程和进程? 线程与进程的关系,区别及优缺点?
进程是系统进行资源分配和调度的单位;线程是CPU调度和分派的单位,一个进程可以有多个线程,这些线程共享这个进程的资源。
说说并发与并行的区别?
并发是同时出发,但同一个时间段只有一个线程在执行。
而并行是同时运行,同一个时间端有多个线程在执行。
为什么要使用多线程呢?
充分利用多线程的优势,加快计算能力
使用多线程可能带来什么问题?(内存泄漏、死锁、线程不安全等等)
创建线程有哪几种方式?
(a.继承 Thread 类;b.实现 Runnable 或者Callable接口;c. 使用 Executor 框架;d.使用 FutureTask)
/**
* 创建线程的几种方式
*/
class MyThread extends Thread{
private String string ;
@Override
public void run() {
string= "12312";
System.out.println("set value ----");
System.out.println("get value ---:"+string);
}
}
class MyRunnable implements Runnable{
@Override
public void run() {
System.out.println("实现Runnable接口");
}
}
class MyCallable implements Callable{
@Override
public Object call() throws Exception {
System.out.println(Thread.currentThread().getName());
return null;
}
}
说说线程的生命周期和状态?
初始,运行,等待<超时等待,等待>,阻塞,终止
什么是上下文切换?
CPU在调度的过程中,一个线程由运行转为可运行状态,或者线程由可运行状态到运行状态所经历的相关信息的存储和资源的转化,称之为上下文切换。
什么是线程死锁?如何避免死锁?
两个线程相互竞争一个资源,而这个资源只能被一个线程所使用的时候,没有获取到资源的那个线程因阻塞而发生死锁的现象,这个线程永远在等待获取资源。
说说 sleep() 方法和 wait() 方法区别和共同点?
区别是: wait方法会释放锁,而sleep()方法不会释放锁
wait()是Object类的一个方法,而sleep()是Thread的静态方法
sleep()会在指定的时间内变成可运行状态,而wait需要调用Object类的notify()方法或者notifyAll()方法才可以变成可运行状态,当然wait(long time)也可以变
wait常用于线程之间的相互通信,而sleep()常用于线程的暂停
为什么我们调用 start() 方法时会执行 run() 方法,为什么我们不能直接调用 run() 方法?
run()用于执行对应的方法,而start()用于启动对应的线程
run()只是在本线程中执行,而start()新创建一个线程执行
volatile 关键字
volatile 是提供多线程共享变量可见性和禁止重排序优化的关键字
Java 内存模型(JMM);
Java虚拟机栈: 每一个方法是一个栈帧
本地方法栈: JVM运行Java的native方法
程序计数器: 描述程序执行的到的位置
堆:线程共享,GC主要清理的区域
方法区:用于存储被JVM加载的类信息,静态变量,常量,即时编译器编译以后的代码数据,又称(非堆),Java8之前是永久代实现,Java8开始使用元空间,并且把字符串常量移动到堆中存储,元空间在直接内存中存储
运行时常量池是属于方法区的一部分,在程序编译时期会把常量放入到运行时常量池,用来存放编译时期生成的字面量和符号引用,运行时期也可以将新的常量放入到池中,比如常见String的intern()方法。
重排序与 happens-before 原则了解吗?
happens-before :可以译为先行发生<前面操作的结果对后续操作是可见的>,就是多线程条件下,两个线程之间的每一行代码执行的顺序是未知的,所以该原则就是为约束这个行为,具体的体现在volatile的防止重排序这一特性上,JVM在运行的时候会把我们的代码进行指令重排序,为了更好的提高性能,而有些时候,对于多线程来说,重排序反而导致了我们没法得到最后的理想的结果。
这个有点深,需要慢慢研究...
说说 synchronized 关键字和 volatile 关键字的区别;
synchronized是悲观锁,属于抢占式的,会引起其它线程的阻塞。
volatile是提供多线程共享变量并且防止指令重排序优化。
synchronized 和 Lock 有什么区别
synchronized是Java的内置关键字,在JVM层面,Lock是Java的一个类
synchronized可以给方法,类和代码块加锁,而Lock只能给代码块加锁
synchronized不需要主动的去释放锁,也不会造成死锁,而Lock需要自己加锁和释放锁,如果使用不当unLock去释放锁,会造成死锁。
Lock 可以知道有没有获取到锁,而synchronized无法知道。
CAS;
比较并交换
ThreadLocal
基本定义:
ThreadLocal的作用主要是做数据隔离,填充的数据只属于当前线程,变量的数据对别的线程而言是相对隔离的,在多线程环境下,如何防止自己的变量被其它线程篡改。
使用的场景:
事务隔离级别,Spring源码实现的事务隔离级别。
Spring采用ThreadLocal的方式,来保证单个线程中的数据库操作使用的是同一个数据库连接,
同时,采用这种方式可以使业务层使用事务时不需要感知并管理connection对象,
通过传播级别,巧妙地管理多个事务配置之间的切换,挂起和恢复。
Spring框架使用ThreadLocal来实现事务隔离的代码在TransactionSynchronizationManager类中:
Spring的事务主要是AOP和ThreadLocal去实现的
Thread实现线程之间的数据隔离的原因是:
每个线程Thread都维护了自己的threadLocals变量,所以在每个线程创建ThreadLocal的时候,实际上都是存在自己线程threadLocal变量中的
别人没办法拿到,从而实现了隔离。
ThreadLocalMap 并非实现了Map接口,而是,内部为 Entry 的结构,而Entry 是继承了WeakReference(弱引用)的 ,只是单纯的数组结构
由于 ThreadLocalMap 是弱引用,所以在进行gc的时候会被垃圾回收,如果创建ThreadLocal的线程一直运行,可发生内存泄漏,
线程池
为什么要用线程池?
为了减少反复的创建线程和销毁线程所带来的资源消耗,所以我们使用线程池来管理和复用连接。
你会使用线程池吗?
如何创建线程池比较好? (推荐使用 ThreadPoolExecutor 构造函数创建线程池)
ThreadPoolExecutor 类的重要参数了解吗?ThreadPoolExecutor 饱和策略了解吗?
线程池原理了解吗?
几种常见的线程池了解吗?为什么不推荐使用FixedThreadPool?
/**
* 创建可缓存的线程池
*/
public static void test1(){
ExecutorService executorService = Executors.newCachedThreadPool();
for (int i = 0; i < 10; i++) {
final Integer index =i;
// 执行
executorService.execute(new Runnable() {
@Override
public void run() {
try {
Thread.sleep(100);//让当前线程休息一会儿
} catch (InterruptedException e) {
e.printStackTrace();
}
System.out.println(Thread.currentThread().getName()+"执行"+index);//打印当前线程
}
});
}
executorService.shutdown();
}
/**
* 创建指定数量的线程池
*/
public static void test2(){
ExecutorService executorService = Executors.newFixedThreadPool(3);
}
/**
* 创建单例的线程池
*/
public static void test3(){
ExecutorService executorService = Executors.newSingleThreadExecutor();
}
/**
* 创建一个定长的线程池
*/
public static void test4(){
ExecutorService executorService = Executors.newScheduledThreadPool(5);
}
如何设置线程池的大小?
AQS
简介
原理
AQS 常用组件。
Semaphore(信号量)-允许多个线程同时访问
CountDownLatch (倒计时器)-CountDownLatch 允许 count 个线程阻塞在一个地方,直至所有线程的任务都执行完毕。
CyclicBarrier(循环栅栏)-CyclicBarrier 和 CountDownLatch 非常类似,它也可以实现线程间的技术等待,但是它的功能比 CountDownLatch 更加复杂和强大。主要应用场景和 CountDownLatch 类似。
ReentrantLock 和 ReentrantReadWriteLock
锁的常见分类
可重入锁和非可重入锁
公平锁与非公平锁
读写锁和排它锁
synchronized 关键字
说一说自己对于 synchronized 关键字的了解;
说说自己是怎么使用 synchronized 关键字,在项目中用到了吗;
讲一下 synchronized 关键字的底层原理;
说说 JDK1.6 之后的 synchronized 关键字底层做了哪些优化,可以详细介绍一下这些优化吗;
谈谈 synchronized 和 ReentrantLock 的区别;
Atomic 与 CAS
CAS:
介绍
原理
Atomic 原子类:
介绍一下 Atomic 原子类;
JUC 包中的原子类是哪 4 类?;
讲讲 AtomicInteger 的使用;
能不能给我简单介绍一下 AtomicInteger 类的原理。
并发容器
ConcurrentHashMap: 线程安全的 HashMap
CopyOnWriteArrayList: 线程安全的 List,在读多写少的场合性能非常好,远远好于 Vector.
ConcurrentLinkedQueue: 高效的并发队列,使用链表实现。可以看做一个线程安全的 LinkedList,这是一个非阻塞队列。
BlockingQueue: 这是一个接口,JDK 内部通过链表、数组等方式实现了这个接口。表示阻塞队列,非常适合用于作为数据共享的通道。
ConcurrentSkipListMap: 跳表的实现。这是一个 Map,使用跳表的数据结构进行快速查找。
并发的三要素
原子性:一个不可再被分割的颗粒,要么全部成功,要么全部失败。
可见性:一个线程对共享变量的修改,在另一个线程中能立即被看到。
有序性:程序执行的顺序按照代码的先后顺序执行。