Java面试——多线程与并发
CAS的原理
AQS等待队列为什么设计成双向链表?
juc包下知道哪些类?
AQS,ReentrantLock,ReentrantReadWriteLock,Semaphore,CountdownLatch,ConcurrentHashMap,CopyOnWriteArrayList
线程的sleep()方法和yield()方法有什么区别?
①sleep()方法给其他线程运行机会时不考虑线程的优先级,因此会给低优先级的线程以运行的机会;yield()方法只会给相同优先级或更高优先级的线程以运行的机会;
② 线程执行sleep()方法后转入阻塞(blocked)状态,而执行yield()方法后转入就绪(ready)状态;
③ sleep()方法声明抛出InterruptedException,而yield()方法没有声明任何异常;
④ sleep()方法比yield()方法(跟操作系统CPU调度相关)具有更好的可移植性。
线程的基本状态以及状态之间的关系
注意阻塞跟等待,在Sychronize中就有阻塞跟等待,等待锁的时候是阻塞,调用wait的时候是等待。
-
初始(NEW):新创建了一个线程对象,但还没有调用start()方法。
-
运行(RUNNABLE):Java线程中将就绪(ready)和运行中(running)两种状态笼统的称为“运行”。
线程对象创建后,其他线程(比如main线程)调用了该对象的start()方法。该状态的线程位于可运行线程池中,等待被线程调度选中,获取CPU的使用权,此时处于就绪状态(ready)。就绪状态的线程在获得CPU时间片后变为运行中状态(running)。 -
阻塞(BLOCKED):表示线程阻塞于锁。
-
等待(WAITING):进入该状态的线程需要等待其他线程做出一些特定动作(通知或中断)。
-
超时等待(TIMED_WAITING):该状态不同于WAITING,它可以在指定的时间后自行返回。
-
终止(TERMINATED):表示该线程已经执行完毕。
-
在 java 程序中怎么保证多线程的运行安全?
线程安全在三个方面体现:
原子性:提供互斥访问,同一时刻只能有一个线程对数据进行操作,(atomic,synchronized);
可见性:一个线程对主内存的修改可以及时地被其他线程看到,(synchronized,volatile);
有序性:一个线程观察其他线程中的指令执行顺序,由于指令重排序,该观察结果一般杂乱无序,(happens-before原则)。 -
多线程锁的升级原理是什么?
在Java中,锁共有4种状态,级别从低到高依次为:无状态锁,偏向锁,轻量级锁和重量级锁状态,这几个状态会随着竞争情况逐渐升级。锁可以升级但不能降级。
锁升级的图示过程:
图片.png
交替输出
三个线程分别输出'a','l','i',输出'a'的线程控制输出的次数。
public class main {
private volatile static boolean hasAPrint = false;
private volatile static boolean hasLPrint = true;
private volatile static boolean hasIPrint = true;
public static void main(String[] args) {
Object lock = new Object();
new Thread(()->{
for(int i=0;i<5;i++) {
synchronized (lock) {
while (!hasIPrint) {
try {
lock.wait();
} catch (InterruptedException e) {
e.printStackTrace();
}
}
System.out.print("a");
hasAPrint = true;
hasIPrint = false;
lock.notifyAll();
}
}
}).start();
new Thread(()->{
while (true) {
synchronized (lock) {
while (!hasAPrint) {
try {
lock.wait();
} catch (InterruptedException e) {
e.printStackTrace();
}
}
System.out.print("l");
hasLPrint = true;
hasAPrint = false;
lock.notifyAll();
}
}
}).start();
new Thread(()->{
while (true) {
synchronized (lock) {
while (!hasLPrint) {
try {
lock.wait();
} catch (InterruptedException e) {
e.printStackTrace();
}
}
System.out.print("i");
hasIPrint = true;
hasLPrint = false;
lock.notifyAll();
}
}
}).start();
}
大部分的代码都是一样的,下面是精简版本。
总结来说,这是同步,一个做完另外一个才接着做。
public class Printer {
private volatile int times;
private volatile int completeCondition;
public Printer(int times, int completeCondition) {
this.times = times;
this.completeCondition = completeCondition;
}
public void print(int waitCondition, int completeCondition, String string){
for (int i = 0; i < times; i++) {
synchronized (this){
while(this.completeCondition!=waitCondition){
try {
this.wait();
} catch (InterruptedException e) {
e.printStackTrace();
}
}
System.out.print(string);
this.completeCondition = completeCondition;
this.notifyAll();
}
}
}
private volatile static Printer printer = new Printer(5, 0);
public static void testPrint() {
new Thread(()->{
printer.print(0, 1, "a");
}).start();
new Thread(()->{
printer.print(1, 2, "l");
}).start();
new Thread(()->{
printer.print(2, 0, "i");
}).start();
}
}
线程池
对于一个普通的线程池,coreSize = 5, maxSize = 10,阻塞队列长度 20,且插入线程是永久执行的,那么不断插入线程,线程池中的数量以及对应的反应如何?
注意无法加入队列的时候再创建线程