Java面试——多线程面试题总结

摘要:
前言关于多线程和并发的问题是任何Java面试中不可或缺的一部分。本文总结了一些常见的多线程面试问题。以下是之前没有提到的关键面试问题的总结。Java中有绝对线程安全的类,例如CopyOnWriteArrayList和CopyOnWriteArray Set。

0.前言

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

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

下面是总结的之前没有提到过的面试重点题。转载请注明出处:http://blog.csdn.net/seu_calvin/article/details/52411531

 

1.多线程有什么用

1发挥多核CPU的优势

如果是单线程的程序,那么在双核CPU上就浪费了50%,在4CPU上就浪费了75%。多线程可以真正发挥出多核CPU的优势来,达到充分利用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,同时另一个线程在add这个Vector99%的情况下都会出现ConcurrentModificationException,也就是fail-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,注意这是一个static方法,这意味着“某条线程”指的是当前线程。

 

9ConcurrentHashMap的并发度是什么

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

 

10.  ReadWriteLock是什么

不是说ReentrantLock不好,只是ReentrantLock某些时候有所局限。

如果使用ReentrantLock是为了防止线程A在写数据、线程B在读数据造成的数据不一致(读和写同时操作造成的)。那么如果线程C在读数据,线程D也在读数据,读数据是不会改变数据的,那就没有必要加锁,但是还是ReentrantLock还是加锁了,这很显然降低了程序的性能。

读写锁ReadWriteLock应运而生ReadWriteLock是一个读写锁接口,ReentrantReadWriteLockReadWriteLock接口的一个具体实现,实现了读写的分离:读锁是共享的,写锁是独占的,读和读之间不会互斥,读和写、写和读、写和写之间才会互斥,提升了读写的性能。

 

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

如果你使用的LinkedBlockingQueue,也就是无界队列的话,没关系,继续添加任务到阻塞队列中等待执行,因为LinkedBlockingQueue可以近乎认为是一个无穷大的队列,可以无限存放任务

如果你使用的是有界队列比方说ArrayBlockingQueue的话,任务首先会被添加到ArrayBlockingQueue中,ArrayBlockingQueue满了,则会使用拒绝策略

 

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

抢占式:一个线程用完CPU之后,操作系统会根据线程优先级、线程饥饿情况等数据算出一个总的优先级并分配下一个时间片给某个线程执行。为了让某些低优先级的线程也能获取到CPU控制权,平衡CPU控制权,可以使用Thread.sleep(0)手动触发一次操作系统分配时间片的操作。

 

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

忙循环就是程序员用循环让一个线程等待,不像传统方法wait()sleep() yield() 它们都放弃了CPU控制,而忙循环不会放弃CPU,它就是在运行一个空循环。这么做的目的是为了保留CPU缓存(在多核系统中,一个等待线程醒来的时候可能会在另一个内核运行,这样会重建缓存)。为了避免重建缓存减少等待重建的时间就可以使用它了。

 

14.  什么是自旋

很多synchronized里面的代码只是一些很简单的代码,执行时间非常快,此时等待的线程都加锁可能是一种不太值得的操作,不妨让等待锁的线程不要被阻塞,而是synchronized的边界做忙循环,这就是自旋。如果做了多次忙循环发现还没有获得锁,再阻塞,这样可能是一种更好的策略。

 

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

1乐观锁:乐观锁认为竞争不总是会发生,因此它不需要持有锁,将比较-设置这两个动作作为一个原子操作尝试去修改内存中的变量,如果失败则表示发生冲突,继而执行相应的重试逻辑。

2悲观锁:悲观锁认为竞争总是会发生,因此每次对某资源进行操作时,都会持有一个独占的锁,就像synchronized

 

16.  实现一个死锁

当线程需要同时持有多个锁时,有可能产生死锁。考虑如下情形:

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面试——多线程面试题总结第1张


免责声明:文章转载自《Java面试——多线程面试题总结》仅用于学习参考。如对内容有疑问,请及时联系本站处理。

上篇Solution 「LOCAL」大括号树针对不同浏览器的css 简单下篇

宿迁高防,2C2G15M,22元/月;香港BGP,2C5G5M,25元/月 雨云优惠码:MjYwNzM=

相关文章

委托和多线程(一)

         定义:委托是一个类型安全的对象,它指向程序中另一个以后会被调用的方法(或多个方法)。通俗的说,委托是一个可以引用方法的对象,当创建一个委托,也就创建一个引用方法的对象,进而就可以调用那个方法,即委托可以调用它所指的方法。 委托的试用步骤: 1、定义委托:权限修饰符   delegate   返回值     委托名 (参数); 2、声明委托...

springboot配置 Druid , yml格式

datasource: driver-class-name: com.mysql.cj.jdbc.Driver url: jdbc:mysql://localhost:3306/mp?useUnicode=true&characterEncoding=utf-8&serverTimezone=Asia/Shanghai u...

Sqlite多线程相关整理

Sqlite多线程相关整理 Sqlite With MultiThreads 什么是线程安全? 当多个线程访问某个方法时,不管你通过怎样的调用方式、或者说这些线程如何交替地执行,我们在主程序中不需要去做任何的同步,这个类的结果行为都是我们设想的正确行为,那么我们就可以说这个类是线程安全的。 一 来自官方FAQ https://www.sqlite.org/...

win32之临界区

线程安全问题 每个线程都有自己的栈,而局部变量是存储在栈中的,这就意味着每个线程都有一份自己的“局部变量”,如果线程 仅仅使用 “局部变量” 那么就不存在线程安全问题 那如果多个线共用一个全局变量呢? 多线程的线程安全问题前提: 1、有全局变量 2、对全局变量有写的权限 我们写一段代码,模拟一下两个进程访问一个全局变量,代码如下: #include <...

Java中各种集合(字符串类)的线程安全性!!!

Java中各种集合(字符串类)的线程安全性!!! 一、概念: 线程安全:就是当多线程访问时,采用了加锁的机制;即当一个线程访问该类的某个数据时,会对这个数据进行保护,其他线程不能对其访问,直到该线程读取完之后,其他线程才可以使用。防止出现数据不一致或者数据被污染的情况。 线程不安全:就是不提供数据访问时的数据保护,多个线程能够同时操作某个数据,从而出现数...

Delphi线程基础知识

参考http://blog.chinaunix.net/uid-10535208-id-2949323.html 一、概述 Delphi提供了好几种对象以方便进行多线程编程。多线程应用程序有以下几方面的功能: 1.避免性能瓶颈:单线程应用程序在进行比较慢的操作如磁盘读写的时候,CPU必须停下来等待,直到该操作执行完毕。而多线程应用程序在进行比较慢的操作如磁...