Java面试——多线程面试题

0.前言

在任何Java面试当中多线程和并发方面的问题都是必不可少的一部分,本文汇总了常见的一些多线程面试题。

一些问题,比如volatile关键词的作用,synchronizedReentrantLock的区别,wait()sleep()的区别等等问题,已经在之前写过的文章中提到过了,这里就不赘述了,有兴趣可以查看以下几篇文章:Java并发——线程同步volatile与synchronized详解Java技术——Java多线程学习Java并发——synchronized和ReentrantLock的联系与区别

下面是总结的之前没有提到过的面试重点题。转载请注明出处:Java面试——多线程面试题_SEU_Calvin的博客-CSDN博客_java多线程面试题

 

1.多线程有什么用

1发挥多核CPU的优势

如果是单线程的程序,那么在双核CPU上就浪费了50%,在4CPU上就浪费了75%。多线程可以充分利用CPU的。

2防止阻塞

多条线程同时运行,一条线程的代码执行阻塞,也不会影响其它任务的执行。

 

2 Runnable接口和Callable接口的区别

Runnable接口中的run()方法的返回值是void,它只是纯粹地去执行run()方法中的代码而已;

Callable接口中的call()方法是有返回值的,是一个泛型,和FutureFutureTask配合可以用来获取异步执行的结果。

 

3.  CyclicBarrierCountDownLatch的区别

两个类都在java.util.concurrent下,都可以用来表示代码运行到某个点上,二者的区别在于:

1CyclicBarrier某个线程运行到某个点上之后该线程即停止运行,直到所有的线程都到达了这个点,所有线程才重新运行CountDownLatch则不是,某线程运行到某个点上之后,该线程会继续运行

2CyclicBarrier只能唤起一个任务CountDownLatch可以唤起多个任务。

3CyclicBarrier重用CountDownLatch不可重用,计数值为0CountDownLatch就不可再用了

 

4.  线程安全的级别

代码在多线程下执行和在单线程下执行永远都能获得一样的结果,那么代码就是线程安全的。线程安全也是有级别之分的:

1不可变

StringIntegerLong这些,都是final类型的类,要改变除非新创建一个。

2绝对线程安全

不管运行时环境如何都不需要额外的同步措施Java中有绝对线程安全的类,比如CopyOnWriteArrayListCopyOnWriteArraySet

3相对线程安全

Vector这种,addremove方法都是原子操作,不会被打断。

如果有个线程在遍历某个Vector,同时另一个线程对其结构进行修改会出现ConcurrentModificationExceptionfail-fast机制)。

4线程非安全

这个就没什么好说的了,ArrayListLinkedListHashMap等都是线程非安全的类。

 

5.  如何在两个线程之间共享数据

通过在线程之间共享对象就可以了,然后通过wait/notify/notifyAllawait/signal/signalAll进行唤起和等待,比方说阻塞队列BlockingQueue就是为线程之间共享数据而设计的。

 

6.为什么wait()方法和notify()/notifyAll()方法要在同步块中被调用

这是JDK强制的,wait()方法和notify()/notifyAll()方法(都是Object的方法)在调用前都必须先获得对象的锁。

 

7.  wait()方法和notify()/notifyAll()方法在放弃对象锁时有什么区别

wait()方法立即释放锁,notify()/notifyAll()方法则会等待线程剩余代码执行完毕才会放弃锁。

 

8.  怎么检测一个线程是否持有对象监视器

Thread类提供了一个holdsLock(Object obj)方法,当且仅当对象obj的锁被当前线程持有的时候才会返回true

 

9ConcurrentHashMap的并发度是什么

ConcurrentHashMap的并发度就是segment的大小,默认为16,这意味着最多可以同时有16条线程操作ConcurrentHashMap,这也是ConcurrentHashMapHashtable的最大优势。

 

10.  ReadWriteLock是什么

ReentrantLock的局限:

如果使用ReentrantLock是为了防止线程A在写数据、线程B在读数据造成的数据不一致。那么如果线程C在读数据,线程D也在读数据,读数据是不会改变数据的,那就没有必要加锁,但是ReentrantLock还是加锁了,这很显然降低了程序的性能。因此读写锁ReadWriteLock应运而生

是一个读写锁接口,ReentrantReadWriteLockReadWriteLock接口的一个具体实现,实现了读写的分离:

读锁是共享的,写锁是独占的,读和读之间不会互斥,读和写、写和读、写和写之间才会互斥,提升了读写的性能。

 

11.  如果你提交任务时,线程池队列已满,这时会发生什么

如果使用的无界队列(如LinkedBlockingQueue的话,继续添加任务到阻塞队列中等待执行。

如果你使用的是有界队列(如ArrayBlockingQueue)的话,则会使用拒绝策略

 

12.  Java中用到的线程调度算法是什么

抢占式:一个线程用完CPU之后,操作系统会根据线程优先级、线程饥饿情况等数据算出一个总的优先级并分配下一个时间片给某个线程执行。

 

13. 多线程中的忙循环是什么

忙循环就是程序员用空循环让一个线程等待,不像传统方法wait()sleep() yield() 它们都放弃了CPU控制,而忙循环不会放弃CPU

这么做的目的是为了保留并避免重建CPU缓存(在多核系统中,一个等待线程醒来的时候可能会在另一个内核运行,这样会重建缓存)。

 

14.  什么是自旋

很多时候synchronized代码块中逻辑简单执行速度快,此时等待的线程都加锁可能是一种不太值得的操作。

因此不妨让等待锁的线程不要被阻塞,而是synchronized的边界做忙循环,这就是自旋。如果做了多次忙循环发现还没有获得锁,再阻塞

 

15.  什么是乐观锁和悲观锁

1乐观锁:认为竞争不总是会发生,因此它拿数据时不上锁,但是在更新的时候会去判断在此期间有没有人去更新这个数据,适用于多读的场景。

2悲观锁:认为竞争总是会发生,因此每次拿数据的时候都会上锁

 

16.  实现一个死锁

死锁形成需要四个条件:

1)一个资源每次只能被一个进程使用。

2)一个进程因请求资源而阻塞时,对已获得的资源保持不放。

3)进程已获得的资源,在没有使用完之前,不能强行剥夺。

4)若干进程之间形成一种头尾相接的循环等待资源关系。

考虑如下情形:

1)线程A当前持有互斥所锁lock1,线程B当前持有互斥锁lock2

2)线程A试图获取lock2,因为线程B正持有lock2,因此线程A会阻塞等待线程Block2释放。

3)如果此时线程B也在试图获取lock1,同理线程也会阻塞。

4)两者都在等待对方所持有但是双方都不释放的锁,这时便会一直阻塞形成死锁。

//存放两个资源等待被使用
public class Resource {	
public static Object obj1 = new Object();
	public static Object obj2 = new Object();
}
//线程1
public class DeadThread1 implements Runnable {
	@Override
	public void run() {
	synchronized (Resource.obj1) {
	try {
		Thread.sleep(1000);
	} catch (InterruptedException e) {}
	synchronized (Resource.obj2) {
		System.out.println("DeadThread1 ");
	   }
}
}
}
//线程2
public class DeadThread2 implements Runnable {
	@Override
	public void run() {
	synchronized (Resource.obj2) {
	try {
		Thread.sleep(1000);
	} catch (InterruptedException e) {}
	synchronized (Resource.obj1) {
		System.out.println("DeadThread2 ");
	   }
}
}
}

//主函数中调用
Thread t1 = new Thread(new DeadThread1());
Thread t2 = new Thread(new DeadThread2());
//启动两个线程
t1.start();
t2.start();

17.  线程类的构造方法、静态块是被哪个线程调用的

new这个线程类所在的线程所调用的,而run方法里面的代码才是被线程自身所调用的。

 

18.  锁粗化是什么意思

Java虚拟机中存在着一种叫做粗化的优化方法,即同步范围变大

比方说StringBuffer,它是一个线程安全的类,反复append字符串意味着要进行反复的加解锁,这对性能不利,因为JVM在这条线程上要反复地在内核态和用户态之间切换,因此JVM会将多次append方法调用的代码进行一个锁粗化的操作,将多次的append的操作扩展到append方法的头尾,变成一个大的同步块,从而提升代码执行效率。

Java面试——多线程面试题

版权声明:程序员胖胖胖虎阿 发表于 2022年10月27日 下午2:56。
转载请注明:Java面试——多线程面试题 | 胖虎的工具箱-编程导航

相关文章

暂无评论

暂无评论...