`

Java程序员面试中的多线程问题

阅读更多

很多核心Java面试题来源于多线程(Multi-Threading)和集合框架(Collections Framework),理解核心线程概念时,娴熟的实际经验是必需的。这篇文章收集了 Java 线程方面一些典型的问题,这些问题经常被高级工程师所问到。

0.Java 中多线程同步是什么?

在多线程程序下,同步能控制对共享资源的访问。如果没有同步,当一个 Java 线程在修改一个共享变量时,另外一个线程正在使用或者更新同一个变量,这样容易导致程序出现错误的结果。

1.解释实现多线程的几种方法?

一 Java 线程可以实现 Runnable 接口或者继承 Thread 类来实现,当你打算多重继承时,优先选择实现 Runnable。

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

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

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

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

4.什么是 ThreadLocal 类,怎么使用它?

ThreadLocal 是一个线程级别的局部变量,并非“本地线程”。ThreadLocal 为每个使用该变量的线程提供了一个独立的变量副本,每个线程修改副本时不影响其它线程对象的副本(译者注)。

下面是线程局部变量(ThreadLocal variables)的关键点:

一个线程局部变量(ThreadLocal variables)为每个线程方便地提供了一个单独的变量。

ThreadLocal 实例通常作为静态的私有的(private static)字段出现在一个类中,这个类用来关联一个线程。

当多个线程访问 ThreadLocal 实例时,每个线程维护 ThreadLocal 提供的独立的变量副本。

常用的使用可在 DAO 模式中见到,当 DAO 类作为一个单例类时,数据库链接(connection)被每一个线程独立的维护,互不影响。(基于线程的单例)

ThreadLocal 难于理解,下面这些引用连接有助于你更好的理解它。

Good article on ThreadLocal on IBM DeveloperWorks 》、《理解 ThreadLocal 》、《Managing data : Good example 》、《Refer Java API Docs

5.什么时候抛出 InvalidMonitorStateException 异常,为什么?

调用 wait ()/notify ()/notifyAll ()中的任何一个方法时,如果当前线程没有获得该对象的锁,那么就会抛出 IllegalMonitorStateException 的异常(也就是说程序在没有执行对象的任何同步块或者同步方法时,仍然尝试调用 wait ()/notify ()/notifyAll ()时)。由于该异常是 RuntimeExcpetion 的子类,所以该异常不一定要捕获(尽管你可以捕获只要你愿意).作为 RuntimeException,此类异常不会在 wait (),notify (),notifyAll ()的方法签名提及。

6.Sleep ()、suspend ()和 wait ()之间有什么区别?

Thread.sleep ()使当前线程在指定的时间处于“非运行”(Not Runnable)状态。线程一直持有对象的监视器。比如一个线程当前在一个同步块或同步方法中,其它线程不能进入该块或方法中。如果另一线程调用了 interrupt ()方法,它将唤醒那个“睡眠的”线程。

注意:sleep ()是一个静态方法。这意味着只对当前线程有效,一个常见的错误是调用t.sleep (),(这里的t是一个不同于当前线程的线程)。即便是执行t.sleep (),也是当前线程进入睡眠,而不是t线程。t.suspend ()是过时的方法,使用 suspend ()导致线程进入停滞状态,该线程会一直持有对象的监视器,suspend ()容易引起死锁问题。

object.wait ()使当前线程出于“不可运行”状态,和 sleep ()不同的是 wait 是 object 的方法而不是 thread。调用 object.wait ()时,线程先要获取这个对象的对象锁,当前线程必须在锁对象保持同步,把当前线程添加到等待队列中,随后另一线程可以同步同一个对象锁来调用 object.notify (),这样将唤醒原来等待中的线程,然后释放该锁。基本上 wait ()/notify ()与 sleep ()/interrupt ()类似,只是前者需要获取对象锁。

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

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

8.当一个同步方法已经执行,线程能够调用对象上的非同步实例方法吗?

可以,一个非同步方法总是可以被调用而不会有任何问题。实际上,Java 没有为非同步方法做任何检查,锁对象仅仅在同步方法或者同步代码块中检查。如果一个方法没有声明为同步,即使你在使用共享数据 Java 照样会调用,而不会做检查是否安全,所以在这种情况下要特别小心。一个方法是否声明为同步取决于临界区访问(critial section access),如果方法不访问临界区(共享资源或者数据结构)就没必要声明为同步的。

下面有一个示例说明:Common 类有两个方法 synchronizedMethod1()和 method1(),MyThread 类在独立的线程中调用这两个方法。

  1. publicclassCommon{
  2. publicsynchronizedvoidsynchronizedMethod1(){
  3. System.out.println("synchronizedMethod1called");
  4. try{
  5. Thread.sleep(1000);
  6. }catch(InterruptedExceptione){
  7. e.printStackTrace();
  8. }
  9. System.out.println("synchronizedMethod1done");
  10. }
  11. publicvoidmethod1(){
  12. System.out.println("Method1called");
  13. try{
  14. Thread.sleep(1000);
  15. }catch(InterruptedExceptione){
  16. e.printStackTrace();
  17. }
  18. System.out.println("Method1done");
  19. }
  20. }
  1. publicclassMyThreadextendsThread{
  2. privateint id = 0 ;
  3. privateCommoncommon;
  4. publicMyThread(Stringname,intno,Commonobject){
  5. super(name);
  6. common = object ;
  7. id = no ;
  8. }
  9. publicvoidrun(){
  10. System.out.println("RunningThread"+this.getName());
  11. try{
  12. if( id ==0){
  13. common.synchronizedMethod1();
  14. }else{
  15. common.method1();
  16. }
  17. }catch(Exceptione){
  18. e.printStackTrace();
  19. }
  20. }
  21. publicstaticvoidmain(String[]args){
  22. Common c = new Common();
  23. MyThread t1 = new MyThread("MyThread-1",0,c);
  24. MyThread t2 = new MyThread("MyThread-2",1,c);
  25. t1.start();
  26. t2.start();
  27. }
  28. }

这里是程序的输出:

  1. RunningThreadMyThread-1
  2. synchronizedMethod1called
  3. RunningThreadMyThread-2
  4. Method1called
  5. synchronizedMethod1done
  6. Method1done

 

结果表明即使 synchronizedMethod1()方法执行了,method1()也会被调用。

9.在一个对象上两个线程可以调用两个不同的同步实例方法吗?

不能,因为一个对象已经同步了实例方法,线程获取了对象的对象锁。所以只有执行完该方法释放对象锁后才能执行其它同步方法。看下面代码示例非常清晰:Common 类有 synchronizedMethod1()和 synchronizedMethod2()方法,MyThread 调用这两个方法。

  1. publicclassCommon{
  2. publicsynchronizedvoidsynchronizedMethod1(){
  3. System.out.println("synchronizedMethod1called");
  4. try{
  5. Thread.sleep(1000);
  6. }catch(InterruptedExceptione){
  7. e.printStackTrace();
  8. }
  9. System.out.println("synchronizedMethod1done");
  10. }
  11. publicsynchronizedvoidsynchronizedMethod2(){
  12. System.out.println("synchronizedMethod2called");
  13. try{
  14. Thread.sleep(1000);
  15. }catch(InterruptedExceptione){
  16. e.printStackTrace();
  17. }
  18. System.out.println("synchronizedMethod2done");
  19. }
  20. }
  1. publicclassMyThreadextendsThread{
  2. privateint id = 0 ;
  3. privateCommoncommon;
  4. publicMyThread(Stringname,intno,Commonobject){
  5. super(name);
  6. common = object ;
  7. id = no ;
  8. }
  9. publicvoidrun(){
  10. System.out.println("RunningThread"+this.getName());
  11. try{
  12. if( id ==0){
  13. common.synchronizedMethod1();
  14. }else{
  15. common.synchronizedMethod2();
  16. }
  17. }catch(Exceptione){
  18. e.printStackTrace();
  19. }
  20. }
  21. publicstaticvoidmain(String[]args){
  22. Common c = new Common();
  23. MyThread t1 = new MyThread("MyThread-1",0,c);
  24. MyThread t2 = new MyThread("MyThread-2",1,c);
  25. t1.start();
  26. t2.start();
  27. }
  28. }

10.什么是死锁

死锁就是两个或两个以上的线程被无限的阻塞,线程之间相互等待所需资源。这种情况可能发生在当两个线程尝试获取其它资源的锁,而每个线程又陷入无限等待其它资源锁的释放,除非一个用户进程被终止。就 JavaAPI 而言,线程死锁可能发生在一下情况。

  • 当两个线程相互调用 Thread.join ()
  • 当两个线程使用嵌套的同步块,一个线程占用了另外一个线程必需的锁,互相等待时被阻塞就有可能出现死锁。

11.什么是线程饿死,什么是活锁?

线程饿死和活锁虽然不想是死锁一样的常见问题,但是对于并发编程的设计者来说就像一次邂逅一样。

当所有线程阻塞,或者由于需要的资源无效而不能处理,不存在非阻塞线程使资源可用。JavaAPI 中线程活锁可能发生在以下情形:

  • 当所有线程在程序中执行 Object.wait (0),参数为 0 的 wait 方法。程序将发生活锁直到在相应的对象上有线程调用 Object.notify ()或者 Object.notifyAll ()。
  • 当所有线程卡在无限循环中。

这里的问题并不详尽,我相信还有很多重要的问题并未提及,您认为还有哪些问题应该包括在上面呢?欢迎在评论中分享任何形式的问题与建议。

很多核心Java面试题来源于多线程(Multi-Threading)和集合框架(Collections Framework),理解核心线程概念时,娴熟的实际经验是必需的。这篇文章收集了 Java 线程方面一些典型的问题,这些问题经常被高级工程师所问到。

0.Java 中多线程同步是什么?

在多线程程序下,同步能控制对共享资源的访问。如果没有同步,当一个 Java 线程在修改一个共享变量时,另外一个线程正在使用或者更新同一个变量,这样容易导致程序出现错误的结果。

1.解释实现多线程的几种方法?

一 Java 线程可以实现 Runnable 接口或者继承 Thread 类来实现,当你打算多重继承时,优先选择实现 Runnable。

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

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

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

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

4.什么是 ThreadLocal 类,怎么使用它?

ThreadLocal 是一个线程级别的局部变量,并非“本地线程”。ThreadLocal 为每个使用该变量的线程提供了一个独立的变量副本,每个线程修改副本时不影响其它线程对象的副本(译者注)。

下面是线程局部变量(ThreadLocal variables)的关键点:

一个线程局部变量(ThreadLocal variables)为每个线程方便地提供了一个单独的变量。

ThreadLocal 实例通常作为静态的私有的(private static)字段出现在一个类中,这个类用来关联一个线程。

当多个线程访问 ThreadLocal 实例时,每个线程维护 ThreadLocal 提供的独立的变量副本。

常用的使用可在 DAO 模式中见到,当 DAO 类作为一个单例类时,数据库链接(connection)被每一个线程独立的维护,互不影响。(基于线程的单例)

ThreadLocal 难于理解,下面这些引用连接有助于你更好的理解它。

Good article on ThreadLocal on IBM DeveloperWorks 》、《理解 ThreadLocal 》、《Managing data : Good example 》、《Refer Java API Docs

5.什么时候抛出 InvalidMonitorStateException 异常,为什么?

调用 wait ()/notify ()/notifyAll ()中的任何一个方法时,如果当前线程没有获得该对象的锁,那么就会抛出 IllegalMonitorStateException 的异常(也就是说程序在没有执行对象的任何同步块或者同步方法时,仍然尝试调用 wait ()/notify ()/notifyAll ()时)。由于该异常是 RuntimeExcpetion 的子类,所以该异常不一定要捕获(尽管你可以捕获只要你愿意).作为 RuntimeException,此类异常不会在 wait (),notify (),notifyAll ()的方法签名提及。

6.Sleep ()、suspend ()和 wait ()之间有什么区别?

Thread.sleep ()使当前线程在指定的时间处于“非运行”(Not Runnable)状态。线程一直持有对象的监视器。比如一个线程当前在一个同步块或同步方法中,其它线程不能进入该块或方法中。如果另一线程调用了 interrupt ()方法,它将唤醒那个“睡眠的”线程。

注意:sleep ()是一个静态方法。这意味着只对当前线程有效,一个常见的错误是调用t.sleep (),(这里的t是一个不同于当前线程的线程)。即便是执行t.sleep (),也是当前线程进入睡眠,而不是t线程。t.suspend ()是过时的方法,使用 suspend ()导致线程进入停滞状态,该线程会一直持有对象的监视器,suspend ()容易引起死锁问题。

object.wait ()使当前线程出于“不可运行”状态,和 sleep ()不同的是 wait 是 object 的方法而不是 thread。调用 object.wait ()时,线程先要获取这个对象的对象锁,当前线程必须在锁对象保持同步,把当前线程添加到等待队列中,随后另一线程可以同步同一个对象锁来调用 object.notify (),这样将唤醒原来等待中的线程,然后释放该锁。基本上 wait ()/notify ()与 sleep ()/interrupt ()类似,只是前者需要获取对象锁。

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

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

8.当一个同步方法已经执行,线程能够调用对象上的非同步实例方法吗?

可以,一个非同步方法总是可以被调用而不会有任何问题。实际上,Java 没有为非同步方法做任何检查,锁对象仅仅在同步方法或者同步代码块中检查。如果一个方法没有声明为同步,即使你在使用共享数据 Java 照样会调用,而不会做检查是否安全,所以在这种情况下要特别小心。一个方法是否声明为同步取决于临界区访问(critial section access),如果方法不访问临界区(共享资源或者数据结构)就没必要声明为同步的。

下面有一个示例说明:Common 类有两个方法 synchronizedMethod1()和 method1(),MyThread 类在独立的线程中调用这两个方法。

  1. publicclassCommon{
  2. publicsynchronizedvoidsynchronizedMethod1(){
  3. System.out.println("synchronizedMethod1called");
  4. try{
  5. Thread.sleep(1000);
  6. }catch(InterruptedExceptione){
  7. e.printStackTrace();
  8. }
  9. System.out.println("synchronizedMethod1done");
  10. }
  11. publicvoidmethod1(){
  12. System.out.println("Method1called");
  13. try{
  14. Thread.sleep(1000);
  15. }catch(InterruptedExceptione){
  16. e.printStackTrace();
  17. }
  18. System.out.println("Method1done");
  19. }
  20. }
  1. publicclassMyThreadextendsThread{
  2. privateint id = 0 ;
  3. privateCommoncommon;
  4. publicMyThread(Stringname,intno,Commonobject){
  5. super(name);
  6. common = object ;
  7. id = no ;
  8. }
  9. publicvoidrun(){
  10. System.out.println("RunningThread"+this.getName());
  11. try{
  12. if( id ==0){
  13. common.synchronizedMethod1();
  14. }else{
  15. common.method1();
  16. }
  17. }catch(Exceptione){
  18. e.printStackTrace();
  19. }
  20. }
  21. publicstaticvoidmain(String[]args){
  22. Common c = new Common();
  23. MyThread t1 = new MyThread("MyThread-1",0,c);
  24. MyThread t2 = new MyThread("MyThread-2",1,c);
  25. t1.start();
  26. t2.start();
  27. }
  28. }

这里是程序的输出:

  1. RunningThreadMyThread-1
  2. synchronizedMethod1called
  3. RunningThreadMyThread-2
  4. Method1called
  5. synchronizedMethod1done
  6. Method1done

 

结果表明即使 synchronizedMethod1()方法执行了,method1()也会被调用。

9.在一个对象上两个线程可以调用两个不同的同步实例方法吗?

不能,因为一个对象已经同步了实例方法,线程获取了对象的对象锁。所以只有执行完该方法释放对象锁后才能执行其它同步方法。看下面代码示例非常清晰:Common 类有 synchronizedMethod1()和 synchronizedMethod2()方法,MyThread 调用这两个方法。

  1. publicclassCommon{
  2. publicsynchronizedvoidsynchronizedMethod1(){
  3. System.out.println("synchronizedMethod1called");
  4. try{
  5. Thread.sleep(1000);
  6. }catch(InterruptedExceptione){
  7. e.printStackTrace();
  8. }
  9. System.out.println("synchronizedMethod1done");
  10. }
  11. publicsynchronizedvoidsynchronizedMethod2(){
  12. System.out.println("synchronizedMethod2called");
  13. try{
  14. Thread.sleep(1000);
  15. }catch(InterruptedExceptione){
  16. e.printStackTrace();
  17. }
  18. System.out.println("synchronizedMethod2done");
  19. }
  20. }
  1. publicclassMyThreadextendsThread{
  2. privateint id = 0 ;
  3. privateCommoncommon;
  4. publicMyThread(Stringname,intno,Commonobject){
  5. super(name);
  6. common = object ;
  7. id = no ;
  8. }
  9. publicvoidrun(){
  10. System.out.println("RunningThread"+this.getName());
  11. try{
  12. if( id ==0){
  13. common.synchronizedMethod1();
  14. }else{
  15. common.synchronizedMethod2();
  16. }
  17. }catch(Exceptione){
  18. e.printStackTrace();
  19. }
  20. }
  21. publicstaticvoidmain(String[]args){
  22. Common c = new Common();
  23. MyThread t1 = new MyThread("MyThread-1",0,c);
  24. MyThread t2 = new MyThread("MyThread-2",1,c);
  25. t1.start();
  26. t2.start();
  27. }
  28. }

10.什么是死锁

死锁就是两个或两个以上的线程被无限的阻塞,线程之间相互等待所需资源。这种情况可能发生在当两个线程尝试获取其它资源的锁,而每个线程又陷入无限等待其它资源锁的释放,除非一个用户进程被终止。就 JavaAPI 而言,线程死锁可能发生在一下情况。

  • 当两个线程相互调用 Thread.join ()
  • 当两个线程使用嵌套的同步块,一个线程占用了另外一个线程必需的锁,互相等待时被阻塞就有可能出现死锁。

11.什么是线程饿死,什么是活锁?

线程饿死和活锁虽然不想是死锁一样的常见问题,但是对于并发编程的设计者来说就像一次邂逅一样。

当所有线程阻塞,或者由于需要的资源无效而不能处理,不存在非阻塞线程使资源可用。JavaAPI 中线程活锁可能发生在以下情形:

  • 当所有线程在程序中执行 Object.wait (0),参数为 0 的 wait 方法。程序将发生活锁直到在相应的对象上有线程调用 Object.notify ()或者 Object.notifyAll ()。
  • 当所有线程卡在无限循环中。

这里的问题并不详尽,我相信还有很多重要的问题并未提及,您认为还有哪些问题应该包括在上面呢?欢迎在评论中分享任何形式的问题与建议。

分享到:
评论

相关推荐

    Java程序员面试宝典

    《Java程序员面试宝典》列举了各大IT公司的面试真题,详细分析了应聘Java程序员职位的常见考点,主要内容包括面试流程及求职准备、Java语言基础、数据类型、集合框架、图形用户界面、输入与输出、多线程、反射机制、...

    Java程序员面试笔试宝典-何昊pdf版

    根据提供的文件信息,我们可以推断出这是一本关于Java程序员面试和笔试准备的书籍,作者为何昊。本书可能包含了大量关于Java编程语言的基础知识、高级特性以及与面试相关的技巧和策略等内容。下面将对可能涉及的重要...

    java程序员面试简历 WORD 模版

    首先,标题"java程序员面试简历 WORD 模版"表明这是一个专门为Java程序员设计的Word文档模板,用于构建面试简历。在制作简历时,你需要关注以下几点: 1. **个人信息**:简历的开头应包含姓名、联系方式(电话、...

    Java程序员面试笔试宝典

    《Java程序员面试笔试宝典》是一本专门为寻求Java相关职位的程序员准备的参考资料,它涵盖了Java编程语言的基础、进阶以及面试中常见的问题和考点。这本书的高清版旨在提供清晰易读的学习体验,帮助读者更好地理解和...

    Java程序员面试宝典.rar

    《Java程序员面试宝典》是Java开发者在求职面试过程中的一份重要参考资料,它涵盖了Java编程的基础、进阶以及面试常见问题。这份压缩包文件包含了一本名为“2008820190118.chm”的帮助文档,很可能是详细整理的面试...

    java程序员面试笔试宝典 + 115个Java面试题和答案+进入IT行业必读的324个java面试题

    Java程序员在面试和笔试过程中,会遇到各种各样的问题,这些问题涵盖了Java语言的基础、进阶、多线程、网络编程、数据库操作、设计模式、框架应用等多个方面。本篇文章将根据提供的资料,深入探讨其中的一些关键知识...

    JAVA程序员面试宝典

    《JAVA程序员面试宝典》是一本专为Java开发者准备的面试指南,旨在帮助读者全面掌握Java编程语言的核心概念和技术,以便在求职面试中表现出色。这本书涵盖了从基础语法到高级特性的广泛内容,包括但不限于面向对象...

    Java程序员面试ppt

    Java程序员面试是一个全面考察应聘者技术能力、问题解决能力和沟通技巧的过程。面试通常包括投递简历、电话通知、笔试、一轮或多轮面试等步骤。在面试中,表现出积极主动的态度和解决问题的决心非常重要。 面试题...

    Java程序员面试宝典+.rar

    Java程序员面试宝典是一本专为Java开发者准备的...总的来说,Java程序员面试宝典包含了上述所有知识点,旨在帮助求职者在面试中展现出全面的技术实力。通过深入学习和实践,你可以提升自己的竞争力,成功地迎接挑战。

    2019年最新版修订版Java程序员面试宝典.pdf

    Java程序员面试宝典2019修订版是针对Java开发人员的一份重要的参考资料,涵盖了Java基础知识和一些面试中常见的问题。以下是从文档中提取的与Java相关的知识点: 1. Java面向对象特性:面向对象的三大特性包括封装...

    java程序员面试宝典配套视频(部分).rar

    "java程序员面试宝典配套视频(部分).rar" 包含了一系列针对Java开发者的面试复习材料,涵盖了Java语言的基础,进阶特性,以及与之相关的并发编程和企业级应用开发等内容。 首先,基础的Java知识是任何面试都无法...

    java程序员面试题

    Java程序员面试题是一个涵盖广泛领域的主题,涉及到Java基础、Web开发、设计模式、数据库和框架等多个方面。以下是对这些文件内容的详细解析: 1. **Model1-CoreJava.doc & Model1-CoreJava-answer.doc**: 这部分...

    JAVA程序员面试指南

     第二篇(第2章~第13章)介绍了Java程序员涉及的基础知识,内容包括Java语言基础、异常的处理、I/O控制流、面向对象编程、线程、集合以及数据库技术等基本知识点。  第三篇(第14章~第16章)专门介绍了Java开发...

Global site tag (gtag.js) - Google Analytics