Java核心知识点常考面试题(持续更新中)
- 线程与线程池
- 线程
- 线程池
- Java锁机制
- java线程模型
- java锁分类
- 轻量级锁
- 重量级锁
- ReentrantLock 底层原理与源码深度解析
- ReentrantReadWriteLock 深入理解读写锁
- CountDownLatch
- semaphore 实现公平锁与非公平锁
- 线程死锁与避免死锁
- Java 容器(Collection、Map)
线程与线程池
线程
一、线程的状态
二、 sleep、wait、join、yield 方法的区别
1、sleep 与 wait 方法的区别
(1)sleep 是 Thread 类的静态本地方法;wait 则是 Object 类的本地方法。
(2)sleep 方法不会释放 lock;但是 wait 会释放,而且会加入到等待队列中。
(3)sleep 方法不依赖于同步器 synchronized;但是 wait 方法需要依赖 synchronized关键字。
(4)sleep 不需要被主动唤醒(休眠时间结束后退出阻塞);但是 wait 方法需要(不指定时间时,需要被别人中断)。
(5)sleep 方法一般用于当前线程休眠,或者轮询暂停操作;wait 方法则多用于线程之间的通信(得益于wait会释放锁)。
(6)sleep 会让出 CPU 执行时间且强制上下文切换;而 wait 则不一定,wait 后可能还是有机会重新竞争到锁继续执行。
2、yield 方法
yield 方法执行后线程直接进入就绪态,马上释放CPU的执行权,但是依然保留了CPU的执行资格,所以有可能CPU下次进行线程调度时,还会让这个线程获取到执行权继续执行。
3、join 方法
线程执行 join 方法后会进入阻塞状态。例如在线程B中调用线程A的 join 方法,那么线程B就会进入阻塞队列中,直到线程A结束或者中断。
三、线程的创建方式
1、继承 Thread 类;
2、实现 Runnable 接口,不带返回值;
3、实现 Callable 接口,带返回值,阻塞式获取返回值;
public static void main(String[] args) {
System.out.println("===开始===");
//通过结合FutureTask类实现
FutureTask<String> futureTask = new FutureTask<>(() -> {
String result = "";
//结果处理过程......
result = "Hello world!";
return result;
});
Thread thread = new Thread(futureTask);
thread.start();
try {
//阻塞主线程并获取返回值
String result = futureTask.get();
System.out.println("result = " + result);
} catch (InterruptedException | ExecutionException e) {
e.printStackTrace();
}
System.out.println("===结束===");
}
4、通过线程池创建线。注意:Java原生线程池(阿里官方不推荐使用Executors),原因如下:
线程池
1、线程池的核心参数
(1)corePoolSize:核心线程数。
(2)maxinumPoolSize:最大线程数。
(3)keepAliveTime:空闲线程存活时间。
(4)unit:时间单位(秒、分钟等)。
(5)workQueue:任务队列,存放任务的容器。
(6)threadFactory:线程工厂,可以使用默认的,或自定义的,通常使用默认。
(7)handler:拒绝策略,顾名思义,拒绝线程访问,JKD自带共有四种策略,如下表:
拒绝策略名称 | 描述 |
---|---|
new ThreadPoolExecutor.AbortPolicy() | 丢弃任务,并抛出RejectedExecutionException异常 |
new ThreadPoolExecutor.DiscardPolicy() | 丢弃任务,但是不抛出异常 |
new ThreadPoolExecutor.DiscardOldestPolicy() | 丢弃队列最前面的任务,然后重新提交被拒绝的任务 |
new ThreadPoolExecutor.CallerRunsPolicy() | 该任务被线程池拒绝,由调用 execute() 方法的线程执行该任务。如果执行程序已关闭,则会丢弃该任务 |
2、如何设置线程池参数
public static void main(String[] args) {
//计算CPU核数
int cpuCores = Runtime.getRuntime().availableProcessors();
ThreadPoolExecutor threadPool = new ThreadExecutor(
corePoolSize, //核心线程数
maxinumPoolSize, //最大线程数,CPU密集型即高并发(一般为CPU核数),IO密集型(一般为:CPU核数*2)
keepAliveTime, //空闲线程的存活时间
unit, //超时时间单位
workQueue, //工作队列:new LinkedBlockingDeque<>(3),阻塞队列
threadFactory, //Executors.defaultThreadFactory(),默认创建线程的工厂,一般不动
handler //拒绝策略:ThreadPoolExecutor.AbortPolicy(),队列满了还有任务直接抛出异常
);
}
3、线程池的状态及说明
4、线程池中的提交优先级和执行优先级
参考文章
:https://blog.51cto.com/u_15891990/5908010
(1)提交优先级: 核心线程 > 工作队列 > 非核心线程
(2)执行优先级: 核心线程 > 非核心线程 > 工作队列
Java锁机制
视屏地址:B站讲的最好的Java锁机制
java线程模型
一、java线程模型,参考文章:https://www.cnblogs.com/songgj/p/15390160.html
java锁分类
参考文章:
方式一、彻底理解Java中的21种锁
方式二、彻底理解Java中的21种锁
轻量级锁
一、CAS(Compare And Swap):比较并交换。也被称为:乐观锁、自旋锁
。参考文章:https://blog.csdn.net/weixin_43715214/article/details/128255225
重量级锁
一、synchronized 关键字(非公平锁) 参考如下文章:
1、深入理解 synchronized(一):https://blog.csdn.net/weixin_43715214/article/details/128608153
2、深入理解 synchronized(二):https://blog.csdn.net/weixin_43715214/article/details/128628524
3、synchronized 锁升级过程:
4、分段式CAS:LongAdder。参考文章:https://www.ngui.cc/el/1845495.html?action=onClick
ReentrantLock 底层原理与源码深度解析
1、参考视屏:B站讲的最好的Java锁机制【P8 - P13】
2、参考文章:深入ReentrantLock实现原理和源码分析
一、ReentrantLock 公平锁与非公平锁
1、ReentrantLock 公平锁与非公平锁,参考文章:https://zhuanlan.zhihu.com/p/45305463
2、ReentrantLock 中的 lock() 与 tryLock() 方法的区别
public class Test {
private static ReentrantLock reentrantLock = new ReentrantLock();
public static void main(String[] args) {
//阻塞式加锁,业务代码不会执行
reentrantLock.lock();
//非阻塞式加锁,可以根据尝试加锁是否成功来决定执行逻辑。通常结合while实现自旋
boolean tryLockResult = reentrantLock.tryLock();
/*
业务代码 .....
*/
}
}
ReentrantReadWriteLock 深入理解读写锁
参考文章:深入理解读写锁ReentrantReadWriteLock
参考案例:https://blog.csdn.net/wujian_csdn_csdn/article/details/114385796
CountDownLatch
CounDownLatch 表示计数器,可以给 CountDownLatch 设置一个数字,一个线程调用 CountDownLatch 的 await() 方法将会阻塞,其他线程可以调用 CountDownLatch 的 countDown() 方法来对这个共享计数器中的数字减一,当数字被减成 0 后,所有的 await 的线程都将被唤醒。对应的底层原理就是,调用 await() 方法的线程就会领用AQS排队,一旦数字被减为 0 ,则会将 AQS 中排队的线程依次唤醒。(建议参考源码
)
semaphore 实现公平锁与非公平锁
Semaphore 表示信号量,可以设置许可的个数,表示同时允许最多多少个线程使用改信号量,通过 acquire() 来获取许可,如果没有许可可用则线程阻塞,并通过 AQS 来排队,可以通过 release() 方法来释放许可,当某个线程释放了许可后,会从 AQS 中正在排队的第一个线程开始一次唤醒,直到没有空闲许可。(其中公平与非公平两种方式建议参考源码
)
线程死锁与避免死锁
一、如何查看线程死锁:https://blog.csdn.net/fengsheng5210/article/details/123576559
二、MySQL如何查看死锁:https://blog.csdn.net/wufagang/article/details/125554792
三、java如何避免死锁,造成死锁的四个必要条件如下:
1、互斥:一个资源每次只能被一个进程使用。
2、请求且保持:一个进程因请求资源而阻塞时,对已获得的资源保持不放。
3、资源不可剥夺:进程已获得的资源,在末使用完之前,不能强行剥夺。
4、循环等待:若干进程之间形成一种头尾相接的循环等待资源关系。
注意
:避免死锁只需要破坏其中一个条件就可以避免死锁,而其中前 3 个条件是作为锁要符合的必要条件,所以避免死锁就需要打破第 4 个条件(循环等待)
。
在开发过程中的注意事项:
1、注意加锁的顺序,保证每个线程按同样的顺序进行加锁。
2、加锁的时间,可以针对锁设置超时时间。
3、检查锁循环等待,避免死锁。
Java 容器(Collection、Map)
Java容器知识点:建议参考源码及网络资料深入理解。