你的位置:首页 > Java教程

[Java教程]java并发:简单面试问题集锦


多线程:Simultaneous Multithreading,简称SMT。

 

并行、并发

并行性(parallelism)指两个或两个以上的事件在同一时刻发生,在多道程序环境下,并行性使多个程序同一时刻可在不同CPU上同时执行。

并发的实质是一个物理CPU(也可以多个物理CPU) 在若干道程序之间多路复用,谈论并发的时候一定要加个单位时间,也就是说单位时间内并发量是多少,并发性是对有限物理资源强制行使多用户共享以提高效率,离开了单位时间其实是没有意义的。

 

Thread.start()与Thread.run()有什么区别?

Thread.start()方法用于启动线程,使之进入就绪状态,当cpu分配时间到该线程时,由JVM调度执行run()方法。

 

为什么需要run()和start()方法,我们可以只用run()方法来完成任务吗?

  我们需要run()、start()这两个方法是因为JVM创建一个单独的线程不同于普通方法的调用,这项工作由线程的start方法来完成,start由本地方法实现,需要显示地被调用,使用这两个方法的另外一个好处是任何一个对象都可以作为线程运行,只要实现了Runnable接口,这就避免了因继承Thread类而造成的Java多继承问题。

 

Sleep()、wait()

Java程序中wait 和 sleep都会造成某种形式的暂停,它们可以满足不同的需要。

sleep()是一个静态方法,只对当前线程有效,一个常见的错误是调用t.sleep()(注:这里的t是一个不同于当前线程的线程)。

wait()方法用于线程间通信,和sleep()不同的是wait()是Object的方法,object.wait()使当前线程处于“不可运行”状态。调用wait()方法时,线程先要获取这个对象的对象锁,当前线程必须对锁对象保持同步,当前线程被添加到等待队列中,随后另一线程可以同步同一个对象锁来调用notify()方法,这样将唤醒原来等待中的线程,然后释放该对象锁,而sleep()方法仅仅释放CPU资源或者让当前线程停止执行一段时间,但不会释放锁。

 

为什么wait和notify、notifyAll方法要在同步块中调用?

wait、notify、notifyAll是Java中Object对象上的三个方法,在多线程中,可以把某个对象作为事件对象,通过这个对象的wait、notify和notifyAll方法来完成线程间状态通知(也即线程间协同)。

notify和notifyAll都是唤醒调用某个对象的wait方法的线程,二者的区别在于,notify会唤醒一个等待线程,而notifyAll会唤醒所有的等待线程。

wait和notify、notifyAll方法要在同步块中调用,跟上述问题相互补充,主要是因为Java API强制要求这样做,如果你不这么做,你的代码会抛出IllegalMonitorStateException异常;还有一个原因是为了避免wait和notify之间产生竞态条件。

wait()/notify()的使用方式如下:

基本上wait()/notify()与sleep()/interrupt()类似,只是前者需要获取对象锁。

 

为什么应该在循环中检查等待条件?

  处于等待状态的线程可能会收到错误警报和伪唤醒,如果不在循环中检查等待条件,程序就会在没有满足结束条件的情况下退出。也可以这么说,在notify()方法调用之后和等待线程醒来之前这段时间,等待线程原来的等待状态可能会改变,这就是在循环中使用wait()方法效果更好的原因。

 

在一个对象上,多个线程是否可以调用不同的同步实例方法?

不能,因为对象同步了实例方法,某个线程调用对象的同步实例方法时获取了对象的对象锁,只有当执行完该方法释放对象锁后才能执行其它同步方法;但是多个线程可以同时访问不同实例的某个同步实例方法。

 

怎么检测一个线程是否拥有锁?

在java.lang.Thread中有一个方法叫holdsLock(),当且仅当指定线程拥有某个具体对象的锁时返回true。

 

何为基于共享容器协同的多线程模式以及基于事件协同的多线程模式?

在一些场景中我们需要在多个线程之间对共享的数据进行处理,例如经典的生产者-消费者模式,此即基于共享容器协同的多线程模式;

若一场景中有A、B两个线程,B线程需要等到某个状态或事件发生后才能继续自己的工作,而这个状态改变或者事件产生与A线程有关,此场景即基于事件协同的多线程模式

 

在静态方法上使用同步会发生什么事?

  同步静态方法会获取该类的“Class”对象,所以当一个线程进入同步的静态方法中时,线程监视器获取类本身的对象锁,其它线程不能进入这个类的任何静态同步方法。它不像实例方法,因为多个线程可以同时访问不同实例的某个同步实例方法。

 

一个线程运行时发生异常会怎样?

  如果异常没有被捕获,该线程将会停止执行。Thread.UncaughtExceptionHandler是用于处理未捕获异常造成线程突然中断情况的一个内嵌接口。当一个未捕获异常将造成线程中断的时候,JVM会使用Thread.getUncaughtExceptionHandler()来查询线程的UncaughtExceptionHandler,并将线程和异常作为参数传递给handler的uncaughtException()方法进行处理。

 

假设有三个线程T1,T2,T3,怎么确保它们按顺序执行?

  在多线程中有多种方法可以让线程按特定顺序执行,如你可以用线程类的join()方法在一个线程中启动另一个线程T,线程T执行完成后原线程继续执行。为了确保三个线程的顺序,你应该先启动最后一个(T3调用T2,T2调用T1),这样T1就会先完成而T3最后完成。

 

Thread类中的yield方法有什么作用?

  Yield方法可以暂停当前正在执行的线程对象,让其它有相同优先级的线程执行。它是一个静态方法,而且只保证当前线程放弃CPU占用,不能保证使其它线程一定能占用CPU,执行yield()的线程有可能在进入到暂停状态后马上又被执行。 

 

Java线程池中submit() 和 execute()方法有什么区别?

  两个方法都可以向线程池提交任务,execute()方法的返回类型是void,它定义在Executor接口中, 而submit()方法可以返回持有计算结果的Future对象,它定义在ExecutorService接口中,它扩展了Executor接口,其它线程池类(如:ThreadPoolExecutor和ScheduledThreadPoolExecutor)都有这些方法。

 

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

事实上如果一个任务不能被调度执行,那么ThreadPoolExecutor.submit()方法将会抛出一个RejectedExecutionException异常。

 

如何在Java中创建Immutable对象?

  Immutable对象可以在没有同步的情况下共享,降低了对某个对象进行并发访问时的同步化开销。这个问题看起来与多线程没有什么关系, 但不变性有助于简化已经很复杂的并发程序。Java没有@Immutable这样的注解符,要创建不可变类,要实现下面几个步骤:将所有的成员声明为私有的、对变量不提供setter方法(这样就不允许直接访问这些成员)、通过构造方法初始化所有成员、在getter方法中不要直接返回对象本身而是克隆对象并返回对象的拷贝。

 

什么是线程组

  在java的多线程处理中有线程组ThreadGroup的概念,ThreadGroup是为了方便线程管理出现的。我们可以统一设定线程组的一些属性,比如设置未捕获异常的处理方法,设置统一的安全策略等,也可以通过线程组方便地获得线程的一些信息。

  每一个ThreadGroup都可以包含一组子线程和一组子线程组,在一个进程中线程组是以树的方式存在,通常情况下根线程组是system线程组,system线程组下是main线程组,默认情况下第一级应用的线程组是通过main线程组创建出来的,也就是说system线程组是所有线程最顶级的父线程组。

Thread.currentThread().getThreadGroup();//可以获得当前线程的线程组

 

线程组与线程池的区别:

线程组和线程池是两个不同的概念,他们的作用完全不同,前者是为了方便线程的管理,后者是为了管理线程的生命周期,复用线程,减少创建销毁线程的开销。

 

JVM中哪个参数是用来控制线程的栈堆栈小的?

-Xss参数是用来控制线程的堆栈大小的。

 

你如何在Java中获取线程堆栈?

  对于不同的操作系统,有多种方法来获得Java的线程堆栈。当你获取线程堆栈时,JVM会把所有线程的状态存到日志文件或者输出到控制台。在Windows你可以使用Ctrl+ Break组合键来获取线程堆栈,Linux下用kill -3命令。你也可以用jstack这个工具来获取,它对线程id进行操作,你可以用jps这个工具找到id。