多线程面试题_100多线程和Java并发面试问答–最终清单(PDF下载)

多线程面试题

多线程面试题

在这篇文章中,我们将提供有关多线程和Java并发面试问答的综合文章。

编者注:并发始终是开发人员的挑战,编写并发程序可能非常困难。

引入并发时,有很多事情可能会崩溃,并且系统的复杂性会大大增加。

但是,编写健壮的并发程序的能力是开发人员的必备工具,并且可以帮助构建复杂的企业级应用程序。

在本文中,我们将讨论可以在编程面试中使用的不同类型的问题,以评估候选人对并发和多线程的理解。

这些问题不仅针对Java,而且围绕通用编程原则。 请享用!

1.进程和线程

1.我们对“并发”一词有什么了解?

并发是程序同时执行多个计算的能力。 这可以通过将计算分布在计算机的可用CPU内核上,甚至在同一网络内的不同计算机上来实现​​。

在此处了解更多信息:

Java 8并发教程

2.进程和线程之间有什么区别?

进程是操作系统提供的执行环境,它具有自己的一组私有资源(例如,内存,打开的文件等)。 与流程相反,线程位于流程中,并与流程的其他线程共享资源(内存,打开的文件等)。 在不同线程之间共享资源的能力使线程更适合于对性能有重要要求的任务。

3.在Java中,什么是进程和线程?

在Java中,进程对应于正在运行的Java虚拟机(JVM),而线程位于JVM中,并且可以由Java应用程序在运行时动态创建和停止。

4.什么是调度程序?

调度程序是一种调度算法的实现,该算法管理进程和线程对某些有限资源(如处理器或某些I / O通道)的访问。 大多数调度算法的目标是为可用的进程/线程提供某种负载平衡,以保证每个进程/线程都有适当的时间范围以排他地访问所请求的资源。

5. Java程序至少具有多少个线程?

每个Java程序都在主线程中执行; 因此,每个Java应用程序都有至少一个线程。

6. Java应用程序如何访问当前线程?

可以通过调用JDK类java.lang.Thread的静态方法currentThread()来访问当前线程:

 public class MainThread { 
    public static void main(String[] args) {

        long id = Thread.currentThread().getId();

        String name = Thread.currentThread().getName();

        ...

    }
 }
7.每个Java线程都有哪些属性?

每个Java线程都具有以下属性:

  • 在JVM中唯一的long类型的标识符
  • 类型为String的名称
  • int类型的优先级
  • 类型为java.lang.Thread.State的状态
  • 线程所属的线程组
8.线程组的目的是什么?

每个线程都属于一组线程。 JDK类java.lang.ThreadGroup提供了一些方法来处理整个线程组。 使用这些方法,例如,我们可以中断组中的所有线程或设置其最大优先级。

9.线程可以具有哪些状态,每个状态的含义是什么?
  • NEW:尚未启动的线程处于此状态。
  • RUNNABLE:在Java虚拟机中执行的线程处于此状态。
  • BLOCKED:处于等待监视器锁定状态的被阻塞线程处于此状态。
  • WAITING:无限期等待另一个线程执行特定操作的线程处于此状态。
  • TIMED_WAITING:一个正在等待另一个线程执行操作的线程最多达到指定的等待时间,该线程处于此状态。
  • TERMINATED:退出的线程处于此状态。
10.多线程编程的好处是什么?

多线程代码可以在以下方面提供帮助:

  • 改善应用程序响应能力
  • 有效地使用多处理器
  • 改善计划结构
  • 使用更少的系统资源
11.在多线程环境中遇到哪些常见问题?
  • 死锁–两个线程A和B分别持有lock_A和lock_B。 他们两个都想访问资源R。为了安全地访问R,需要lock_A和lock_B。 但是线程A需要lock_B,线程B需要lock_A。 但是他们两个都不准备放弃他们持有的锁。 因此,没有任何进展。 陷入僵局!
  • 种族条件–考虑生产者-消费者的经典例子。 如果您在添加或从队列中删除项目之前忘记锁定该怎么办? 想象一下,两个线程A和B试图添加一个项目而没有锁定。 线程A访问队列的后面。 然后,调度程序就有机会运行线程B,该线程B成功添加了项并更新了尾指针。 现在,线程A读取的尾指针已过时,但它认为它是尾并添加了该项。 因此,B添加的项目是LOST! 数据结构已损坏! 更糟糕的是,清理时还可能导致内存泄漏。
  • 数据竞争–想象应该设置的标志变量。 假设您已放锁以避免比赛情况。 现在,不同的线程想要设置不同的值。 由于调度程序可以以任何方式调度线程执行,因此您最终不知道标志的值。
  • 饥饿–这是线程调度程序引起的问题。 一些线程没有机会运行和完成,或者无法获得所需的锁,因为其他线程被赋予了更高的优先级。 他们“饿死”了CPU周期或其他资源。
  • 优先级反转–想象两个线程A和B。A具有比B高的优先级,因此比B具有更多的CPU周期。但是在访问共享资源时,B持有A所需的锁并屈服。 现在,如果没有锁,A无法做任何事情,并且浪费了大量CPU周期,因为B没有获得足够的周期,但是拥有了锁。
12.我们如何设置线程的优先级?

使用setPriority(int)方法设置线程的优先级。 要将优先级设置为最大值,我们使用常量Thread.MAX_PRIORITY并将其设置为最小值,我们使用常量Thread.MIN_PRIORITY因为这些值在不同的JVM实现之间可能会有所不同。

13.什么是多线程中的上下文切换?

纯粹的多任务处理不存在。 同时执行两项具有挑战性的任务是不可能的。 因此,当我们执行多任务时,我们真正要做的就是不断地从一项任务切换到另一项任务。 这就是上下文切换。

该术语起源于计算机科学。 但是,它同样适用于人类执行的心理任务。 毕竟,人类的思想在许多方面都类似于CPU。

就像运行多线程进程的CPU在运行另一个线程时暂时搁置一个给定线程的执行一样,人类的大脑也搁置了一个任务,以便将重点转移到另一个任务上。

14. Java中的绿色线程和本地线程之间的区别?
  • 绿色线程是指Java虚拟机本身在一个操作系统进程中创建,管理和上下文切换所有Java线程的模型。 没有使用操作系统线程库。
  • 本机线程是指Java虚拟机使用操作系统线程库(在UnixWare上名为libthread)创建和管理Java线程,并且每个Java线程都映射到一个线程库线程。
15.我们对种族条件一词有什么了解?

竞争条件描述的星座图,其中某些多线程实现的结果取决于参与线程的确切计时行为。 在大多数情况下,具有这种行为是不希望的,因此,竞赛条件一词还意味着由于缺少线程同步而导致的错误会导致不同的结果。 竞争条件的一个简单示例是两个并行线程对整数变量的递增。 由于该操作由一个以上的单个原子操作组成,因此可能发生两个线程读取并增加相同值的情况。 在此并发增量之后,整数变量的数量不会增加2,而只会增加1。

在此处了解更多信息:

java.util.concurrent.locks.Condition示例

16.将对象实例从一个线程传递到另一个线程时,您需要考虑什么?

在线程之间传递对象时,必须注意这些对象不能同时被两个线程操纵。 一个示例是Map实现,其键/值对由两个并发线程修改。 为了避免并发修改的问题,可以将对象设计为不可变的。

17.是否可以通过使用多线程来提高应用程序的性能? 列举一些例子。

如果我们有多个CPU内核可用,并且可以在可用CPU内核上并行计算的话,可以通过多线程来提高应用程序的性能。 一个示例是应该缩放存储在本地目录结构中的所有图像的应用程序。 生产者/消费者实现可以使用一个线程扫描目录结构和执行实际缩放操作的一堆工作线程,而不是一个接一个地迭代所有图像。 另一个示例是镜像某些网页的应用程序。 生产者线程可以解析第一个HTML页面并将发现的链接发布到队列中,而不是先加载一个HTML页面。 工作线程监视队列并加载解析器找到的网页。 当工作线程等待页面完全加载时,其他线程可以使用CPU解析已经加载的页面并发出新请求。

18.我们对可扩展性一词有什么了解?

可伸缩性是指程序通过向其添加更多资源来提高性能的能力。

19.是否可以通过使用多个处理器来计算一个应用程序的理论最大速度?

阿姆达尔定律提供了一个公式,可以通过为应用程序提供多个处理器来计算理论上的最大加速。 理论上的加速比由S(n) = 1 / (B + (1-B)/n) ,其中n表示处理器数量, B表示无法并行执行的程序部分。 当n收敛于无穷大时,项(1-B)/n收敛于零。 因此,在这种特殊情况下,公式可以简化为1/B 正如我们所看到的,理论上最大的加速比与必须串行执行的分数成反比。 这意味着该分数越低,可以实现越多的理论加速。

20.提供一个示例,为什么单线程应用程序的性能改进会导致多线程应用程序的性能下降。

此类优化的一个突出示例是List实现,该实现将元素的数量作为单独的变量保存。 这可以提高单线程应用程序的性能,因为size()操作不必遍历所有元素,但可以直接返回当前元素数。 在多线程应用程序中,由于多个并发线程可能会将元素插入列表,因此必须通过锁来保护其他计数器。 当列表的更新次数大于size()操作的调用次数时,此附加锁可能会降低性能。

2.线程对象

2.1定义和启动线程

21.如何用Java创建线程?

基本上,有两种方法可以用Java创建线程。第一个是编写一个扩展JDK类java.lang.Thread的类,并调用其方法start()

 public class MyThread extends Thread { 
    public MyThread(String name) {

        super (name);

    }

    @Override

    public void run() {

        System.out.println( "Executing thread " +Thread.currentThread().getName());

    }

    public static void main(String[] args) throws InterruptedException {

        MyThread myThread = new MyThread( "myThread" );

        myThread.start();

    }
 }

第二种方法是实现接口java.lang.Runnable并将此实现作为参数传递给java.lang.Thread的构造函数:

 public class MyRunnable implements Runnable { 
    public void run() {

        System.out.println( "Executing thread " +Thread.currentThread().getName());

    }

    public static void main(String[] args) throws InterruptedException {

        Thread myThread = new Thread( new MyRunnable(), "myRunnable" );

        myThread.start();

    }
 }
22.为什么不应该通过调用其方法stop()来停止线程?

不应使用java.lang.Thread的过时方法stop()来停止线程,因为对该方法的调用会导致线程解锁其已获取的所有监视器。 如果受释放锁之一保护的任何对象处于不一致状态,则该状态对所有其他线程可见。 当其他线程对此不一致的对象进行处理时,这可能导致任意行为。

23.是否可以启动一个线程两次?

否,在通过调用线程的start()方法start()线程之后,第二次调用start()会引发IllegalThreadStateException

24.以下代码的输出是什么?
 public class MultiThreading { 
    private static class MyThread extends Thread { 
        public MyThread(String name) {

            super (name);

        }

        @Override

        public void run() {

            System.out.println(Thread.currentThread().getName());

        }

    }

    public static void main(String[] args) {

        MyThread myThread = new MyThread( "myThread" );

        myThread.run();

    }
 }

上面的代码产生输出“ main”而不是“ myThread”。 从main()方法的第二行可以看出,我们错误地调用了run()方法而不是start() 。 因此,没有启动新线程,但是方法run()在主线程中执行。

25.什么是守护线程?

守护程序线程是当JVM决定是否应该停止时,不评估其执行状态的线程。 当所有用户线程(与守护程序线程相反)终止时,JVM停止。 因此,一旦所有用户线程停止,守护程序线程就可以用于实现监视功能,例如,JVM停止了该线程:

 public class Example { 
    private static class MyDaemonThread extends Thread { 
        public MyDaemonThread() {

            setDaemon( true );

        }

        @Override

        public void run() {

            while ( true ) {

                try {

                    Thread.sleep( 1 );

                } catch (InterruptedException e) {

                    e.printStackTrace();

                }

            }

        }

    }

    public static void main(String[] args) throws InterruptedException {

        Thread thread = new MyDaemonThread();

        thread.start();

    }
 }

即使守护线程仍在其无尽的while循环中运行,以上示例应用程序也会终止。

26.什么是Java线程转储?

Java线程转储是一种找出JVM中每个线程在特定时间点正在做什么的方法。

如果您的Java应用程序有时在负载下运行时挂起,这将特别有用,因为对转储的分析将显示线程卡在哪里。

您可以在Unix / Linux下通过运行kill -QUIT <pid>来生成线程转储,而在Windows下则可以通过按Ctl + Break来生成线程转储。

27.是否可以在启动普通用户线程后将其转换为守护线程?

用户线程一旦启动就无法转换为守护线程。 在已运行的线程实例上调用方法thread.setDaemon(true)会导致IllegalThreadStateException

28.忙碌等待使我们了解什么?

繁忙等待是指通过执行一些活动计算来等待事件的实现,这些计算使线程/进程占用了处理器,尽管调度程序可以将其从处理器中删除。 繁忙等待的示例是将等待时间花费在一个循环中,该循环一次又一次地确定当前时间,直到到达某个时间点为止:

 Thread thread = new Thread( new Runnable() {

    @Override

    public void run() {

        long millisToStop = System.currentTimeMillis() + 5000 ;

        long currentTimeMillis = System.currentTimeMillis();

        while (millisToStop > currentTimeMillis) {

            currentTimeMillis = System.currentTimeMillis();

        }

    }
 });
29. Java中的wait()和sleep()方法之间有什么区别?
等待():
  • wait()方法释放锁定。
  • wait()是Object类的方法。
  • wait()是非静态方法–公共最终void wait()引发InterruptedException {//…}
  • 应该通过notify()或notifyAll()方法通知wait()。
  • 需要从循环中调用wait()方法以处理错误警报。
  • 必须从同步上下文(即同步方法或块)中调用wait()方法,否则它将引发IllegalMonitorStateException

睡觉():

  • sleep()方法不会释放锁。
  • sleep()是java.lang.Thread类的方法。
  • sleep()是静态方法–公共静态无效睡眠(长毫秒,int nanos)抛出InterruptedException {//…}
  • 在指定的时间后,sleep()完成。
  • sleep()最好不要从循环中调用(即参见下面的代码)。
  • sleep()可以从任何地方调用。 没有具体要求。
30.当未捕获的异常离开run()方法时会发生什么?

我可能碰巧从run()方法中逸出了未经检查的异常。 在这种情况下,线程由Java虚拟机停止。 通过将实现UncaughtExceptionHandler接口的实例注册为异常处理程序,可以捕获此异常。

这可以通过调用静态方法Thread.setDefaultUncaughtExceptionHandler(Thread.UncaughtExceptionHandler)来完成,该方法告诉JVM在线程本身上未注册任何特定处理程序的情况下使用提供的处理程序,或者通过在以下位置调用setUncaughtExceptionHandler(Thread.UncaughtExceptionHandler)线程实例本身。

31. RunnableRunnable这两个接口有什么区别

Callable?

接口Runnable定义了方法run()没有任何返回值,而接口Callable允许方法call()返回一个值并引发异常。

32.什么是关机钩?

关闭钩子是在JVM关闭时执行的线程。 可以通过在Runtime实例上调用addShutdownHook(Runnable)进行注册:

 Runtime.getRuntime().addShutdownHook( new Thread() {

    @Override

    public void run() { 
    }
 });

2.2通过睡眠暂停执行

33.如何防止繁忙的等待?

防止繁忙等待的一种方法是使当前线程在给定的时间内Hibernate。 这可以通过调用方法java.lang.Thread.sleep(long)来完成,方法是将进入睡眠的毫秒数作为参数传递。

34.我们可以使用Thread.sleep()进行实时处理吗?

传递给Thread.sleep(long)调用的毫秒数仅表示调度程序不需要当前线程执行多长时间。 根据实际的实现,调度程序可能会让线程提前或延迟几毫秒再执行一次。 因此,不应将Thread.sleep()的调用用于实时处理。

35. Thread.yield()方法有什么作用?

静态方法Thread.yield()的调用为调度程序提供了一个提示,即当前线程愿意释放处理器。 调度程序可以随意忽略此提示。 由于未定义在调用Thread.yield()之后哪个线程将获取处理器,因此甚至有可能当前线程成为要执行的“下一个”线程。

36.类java.util.concurrent.Future用例是什么?

java.util.concurrent.Future实例用于表示异步计算的结果,这些结果无法立即获得。 因此,该类提供了一些方法来检查异步计算是否已完成,取消任务并检索实际结果。 后者可以通过提供的两个get()方法来完成。 第一个get()方法不带参数,并阻塞直到结果可用为止;而第二个get()方法带一个超时参数,如果结果在给定时间范围内不可用,则该方法使方法调用返回。

在此处了解更多信息:

java.util.concurrent.FutureTask示例

Java CompletionStage和CompletableFuture示例

37.我们如何在Java中停止线程?

要停止线程,可以使用指向当前线程的易失性引用,该引用可以被其他线程设置为null,以指示当前线程应停止其执行:

 private static class MyStopThread extends Thread {

    private volatile Thread stopIndicator; 
    public void start() {

        stopIndicator = new Thread( this );

        stopIndicator.start();

    }

    public void stopThread() {

        stopIndicator = null ;

    }

    @Override

    public void run() {

        Thread thisThread = Thread.currentThread();

        while (thisThread == stopIndicator) {

            try {

                Thread.sleep( 1000 );

            } catch (InterruptedException e) {

            }

        }

    }
 }

2.3中断

38.如何在使用Thread.sleep()之前唤醒已进入睡眠状态的线程?

java.lang.Thread的方法interrupt()中断睡眠线程。 通过InterruptedException唤醒通过调用Thread.sleep()进入睡眠状态的中断线程:

 public class InterruptExample implements Runnable { 
    public void run() {

        try {

            Thread.sleep(Long.MAX_VALUE);

        } catch (InterruptedException e) {

            System.out.println( "[" +Thread.currentThread().getName()+ "] Interrupted by exception!" );

        }

    }

    public static void main(String[] args) throws InterruptedException {

        Thread myThread = new Thread( new InterruptExample(), "myThread" );

        myThread.start();

        System.out.println( "[" +Thread.currentThread().getName()+ "] Sleeping in main thread for 5s..." );

        Thread.sleep( 5000 ); 
        System.out.println( "[" +Thread.currentThread().getName()+ "] Interrupting myThread" );

        myThread.interrupt();

    }
 }
39.线程如何查询是否已被中断?

如果线程不在像Thread.sleep()这样的方法中,该方法将抛出InterruptedException ,则该线程可以通过调用静态方法Thread.interrupted()或继承的方法isInterrupted()来查询是否已被中断。来自java.lang.Thread.

40.应该如何处理InterruptedException

诸如sleep()join()抛出InterruptedException以告知调用方另一个线程已中断该线程。 在大多数情况下,这样做是为了告诉当前线程停止其当前计算并意外完成它们。 因此,通过捕获异常并仅将其记录到控制台或某些日志文件中来忽略异常通常不是处理此类异常的适当方法。 此异常的问题在于,Runnable接口的方法run()不允许run()引发任何异常。 因此,仅将其重新添加无济于事。 这意味着run()的实现必须自己处理此检查的异常,这通常导致捕获和忽略该异常。

2.4加入

41.在启动子线程之后,我们如何在父线程中等待子线程的终止?

通过调用线程实例变量上的join()方法,可以等待线程终止:

 Thread thread = new Thread( new Runnable() {

    @Override

    public void run() { 
    }
 });
 thread.start();
 thread.join();
42.以下程序的输出是什么?
 public class MyThreads { 
    private static class MyDaemonThread extends Thread { 
        public MyDaemonThread() {

            setDaemon( true );

        }

        @Override

        public void run() {

            try {

                Thread.sleep( 1000 );

            } catch (InterruptedException e) {

            }

        }

    }

    public static void main(String[] args) throws InterruptedException {

        Thread thread = new MyDaemonThread();

        thread.start();

        thread.join();

        System.out.println(thread.isAlive());

    }
 }

上面的代码的输出为“ false”。 尽管MyDaemonThread的实例是守护程序线程,但join()的调用会使主线程等待,直到守护程序线程的执行完成。 因此,在线程实例上调用isAlive()揭示守护程序线程不再运行。

3.同步

43.同步方法和同步块之间的区别?
  • 同步块减小了锁定范围,但是同步方法的锁定范围是整个方法。
  • 同步块的性能更好,因为只有关键部分被锁定,但是同步方法的性能比块差。
  • 同步块提供了对锁的精细控制,但对此对象或类级别锁表示的当前对象提供了同步方法锁。
  • 同步块可以抛出NullPointerException,但同步方法不会抛出。
  • 同步块:同步(此){}
  • 同步方法:公共同步void fun(){}
44.什么是Java中的volatile关键字,它与Java中的同步方法有何不同?

使用volatile强制线程直接从RAM存储器读取和写入变量。 因此,当许多线程都使用相同的volatile变量时,它们都将看到RAM内存中存在的最后一个版本,而不是高速缓存中可能的旧副本。 当线程进入同步块时,它需要控制监视变量。 所有其他线程等待,直到第一个线程从同步块退出。 为了确保所有线程都能看到相同的修改,同步块中使用的所有变量都直接从RAM存储器而不是从高速缓存副本读取和写入。

45.同步关键字用于什么目的?

当您必须实现对资源的互斥访问(例如某个静态值或某些文件引用)时,可以使用同步块包含与互斥资源一起使用的代码:

 synchronized (SynchronizedCounter. class ) {

    counter++;
 }
46.什么是信号量?

信号量是一种数据结构,它维护必须由竞争线程获得的一组许可。 因此,信号量可用于控制有多少线程同时访问关键部分或资源。 因此, java.util.concurrent.Semaphore的构造函数将线程竞争的许可数量作为第一个参数。 每次调用其acquire()方法都会尝试获取可用许可之一。 在没有下一个许可之前,没有任何参数块的acquire()方法。 稍后,当线程完成对关键资源的工作时,可以通过在Semaphore实例上调用方法release()来释放许可。

在此处了解更多信息:

限制URL连接的信号量示例

java.util.concurrent.Semaphore示例

47.什么是CountDownLatch

SDK类CountDownLatch提供了一个同步辅助工具,可用于实现以下场景:线程必须等到其他一些线程达到相同状态才能启动所有线程。 通过提供一个递减的同步计数器,直到其达到零值,即可完成此操作。 CountDownLatch实例达到零后,所有线程即可继续进行。 可以使用计数器的值1来让所有线程在给定的时间点启动,也可以等待直到多个线程完成。 在后一种情况下,计数器将使用线程数进行初始化,并且每个完成其工作的线程都会将锁存器递减一个。

48. CountDownLatchCyclicBarrier什么区别?

这两个SDK类都在内部维护一个计数器,该计数器由不同的线程递减。 线程等待直到内部计数器达到零值,然后从那里继续。 但是与CountDownLatch相比, CyclicBarrier类在值达到零后将内部值重置为初始值。 顾名思义,因此CyclicBarrier实例可以用于实现用例,其中线程必须一次又一次地等待彼此。

在此处了解更多信息:

Java CountDownLatch示例

java.util.concurrent.Phaser示例

Java.util.concurrent.CyclicBarrier示例

更一般的等待/通知机制的CountDownLatch示例

3.1内部锁和同步

49.同步方法获得什么内在锁?

同步方法获取该方法对象的固有锁定,并在方法返回时释放该锁定。 即使该方法引发异常,也将释放固有锁定。 因此,同步方法等于以下代码:

 public void method() {

    synchronized ( this ) {

        ...

    }
 }
50.如果两个线程同时在不同的对象实例上调用同步方法,这些线程之一会阻塞吗?

两种方法都锁定同一监视器。 因此,您不能同时在不同线程上的同一对象上执行它们(两种方法之一将阻塞,直到另一种方法完成)。

51.构造函数可以同步吗?

否,构造函数无法同步。 之所以导致语法错误,是因为只有构造线程才有权访问正在构造的对象。

52. Lock与同步相比有什么好处?

锁的优点是:

  • 有可能使它们公平
  • 在等待Lock对象时,可以使线程响应中断。
  • 可以尝试获取锁,但是如果无法获取锁,则立即返回或在超时后返回
  • 可以在不同的范围内以不同的顺序获取和释放锁
53.原始值可以用于内部锁吗?

不可以,原始值不能用于内部锁。

54.内在锁是否可重入?

是的,同一线程可以一次又一次地访问固有锁。 否则,获取锁的代码将必须注意,它不会偶然尝试获取已获取的锁。

55.我们对公平锁有什么了解?

选择是通过障碍的一些独家资源的下一个线程时,一个公平的锁占用线程的等待时间考虑在内。 Java SDK提供了一个公平锁的示例实现: java.util.concurrent.locks.ReentrantLock 。 如果使用布尔标志设置为true的构造函数,则ReentrantLock授予对等待时间最长的线程的访问权限。

56. SDK类ReadWriteLock使用了哪种减少锁争用的技术?

SDK类ReadWriteLock使用以下事实:当没有其他线程尝试更新值时,并发线程在想要读取值时不必获取锁。 这由一对锁实现,一个锁用于只读操作,一个锁用于写操作。 尽管可以通过多个线程获得只读锁定,但是该实现保证释放写锁定后,所有读取操作都将看到更新的值。

在此处了解更多信息:

Java ReentrantLock示例

Java ReadWriteLock示例

Java ReentrantReadWriteLock示例

任务运行器的重入锁示例

可重入ReadWriteLock值计算器示例

3.2原子访问

57.我们通过原子操作了解什么?

原子操作是完全执行或根本不执行的操作。

58.语句c ++是原子的吗?

不,一个整数变量的增量包括一个以上的运算。 首先,我们必须加载c的当前值,将其递增,然后最后将新值存储回去。 当前执行此增量的线程可能会在这三个步骤中的任何一个之间中断,因此此操作不是原子操作。

59. Java中哪些操作是原子操作?

Java语言提供了一些原子性的基本操作,因此可用于确保并发线程始终看到相同的值:

  • 对参考变量和原始变量(长整型和双精度型除外)的读写操作
  • 对声明为易失性的所有变量的读写操作

4.活泼

4.1死锁

60.我们对僵局有什么了解?

死锁是一种情况,其中两个(或更多)线程各自在另一个线程上等待以释放其已锁定的资源,而线程本身已锁定另一个线程在等待的资源:

  • 线程1:锁定资源A,等待资源B
  • 线程2:锁定资源B,等待资源A
61.僵局情况有哪些要求?

通常,可以确定以下死锁要求:

  • 互斥:有一种资源在任何时间点只能由一个线程访问。
  • 资源持有:锁定一个资源后,线程尝试获取对某个其他排他资源的另一个锁定。
  • 无抢占:没有机制,如果一个线程在特定时间段内持有锁,则该机制可以释放资源。
  • 循环等待:在运行时发生一个星座,其中两个(或更多)线程分别在另一个线程上等待以释放已锁定的资源。
62.完全可以防止死锁吗?

In order to prevent deadlocks one (or more) of the requirements for a deadlock has to be eliminated:

  • Mutual exclusion: In some situation it is possible to prevent mutual exclusion by using optimistic locking.
  • Resource holding: A thread may release all its exclusive locks, when it does not succeed in obtaining all exclusive locks.
  • No preemption: Using a timeout for an exclusive lock frees the lock after a given amount of time.
  • Circular wait: When all exclusive locks are obtained by all threads in the same sequence, no circular wait occurs.
63. Is it possible to implement a deadlock detection?

When all exclusive locks are monitored and modelled as a directed graph, a deadlock detection system can search for two threads that are each waiting on the other thread to free a resource that it has locked. The waiting threads can then be forced by some kind of exception to release the lock the other thread is waiting on.

4.2 Starvation and Livelock

64. What is a livelock?

A livelock is a situation in which two or more threads block each other by responding to an action that is caused by another thread. In contrast to a deadlock situation, where two or more threads wait in one specific state, the threads that participate in a livelock change their state in a way that prevents progress on their regular work. An example would be a situation in which two threads try to acquire two locks, but release a lock they have acquired when they cannot acquire the second lock. It may now happen that both threads concurrently try to acquire the first thread. As only one thread succeeds, the second thread may succeed in acquiring the second lock. Now both threads hold two different locks, but as both want to have both locks, they release their lock and try again from the beginning. This situation may now happen again and again.

65. What do we understand by thread starvation?

Threads with lower priority get less time for execution than threads with higher priority. When the threads with lower priority performs a long enduring computations, it may happen that these threads do not get enough time to finish their computations just in time. They seem to “starve” away as threads with higher priority steal them their computation time.

66. Can a synchronized block cause thread starvation?

The order in which threads can enter a synchronized block is not defined. So in theory it may happen that in case many threads are waiting for the entrance to a synchronized block, some threads have to wait longer than other threads. Hence they do not get enough computation time to finish their work in time.

5. Guarded Blocks

67. Which two methods that each object inherits from java.lang.Object can be used to implement a simple producer/consumer scenario?

When a worker thread has finished its current task and the queue for new tasks is empty, it can free the processor by acquiring an intrinsic lock on the queue object and by calling the method wait() . The thread will be woken up by some producer thread that has put a new task into the queue and that again acquires the same intrinsic lock on the queue object and calls notify() on it.

68. What is the difference between notify() and notifyAll() ?

Both methods are used to wake up one or more threads that have put themselves to sleep by calling wait() . While notify() only wakes up one of the waiting threads, notifyAll() wakes up all waiting threads.

69. How it is determined which thread wakes up by calling notify() ?

It is not specified which threads will be woken up by calling notify() if more than one thread is waiting. Hence code should not rely on any concrete JVM implementation.

70

Is the following code that retrieves an integer value from some queue implementation correct?

 public Integer getNextInt() {

    Integer retVal = null ;

    synchronized (queue) {

        try {

            while (queue.isEmpty()) {

                queue.wait();

            }

        } catch (InterruptedException e) {

            e.printStackTrace();

        }

    }

    synchronized (queue) {

        retVal = queue.poll();

        if (retVal == null ) {

            System.err.println( "retVal is null" );

            throw new IllegalStateException();

        }

    }

    return retVal;
 }

Although the code above uses the queue as object monitor, it does not behave correctly in a multi-threaded environment. The reason for this is that it has two separate synchronized blocks. When two threads are woken up in line 6 by another thread that calls notifyAll() , both threads enter one after the other the second synchronized block. It this second block the queue has now only one new value, hence the second thread will poll on an empty queue and get null as return value.

6.Immutable Objects

71. What is an Immutable Object
  • Immutable objects can be published through any mechanism
  • Immutable objects can be used safely by any thread without additional synchronization, even when synchronization is not used to publish them.
72. How to Create an Immutable Object

To create an immutable object you need to:

  • Don't add any setter method
  • Declare all fields final and private
  • If a field is a mutable object create defensive copies of it for getter methods
  • If a mutable object passed to the constructor must be assigned to a field create a defensive copy of it
  • Don't allow sub-classes to override methods.
73. Which rules do you have to follow in order to implement an immutable class?
  • 所有字段均应为最终字段和私有字段。
  • 不应使用setter方法。
  • 为了防止子类违反不变性原则,应将类本身声明为final。
  • 如果字段不是原始类型,而是对另一个对象的引用:
    • 不应有将引用直接暴露给调用者的getter方法。

7.Lock Objects

74. Is it possible to check whether a thread holds a monitor lock on some given object?

The class java.lang.Thread provides the static method Thread.holdsLock(Object) that returns true if and only if the current thread holds the lock on the object given as argument to the method invocation.

75. What do we understand by lock contention?

Lock contention occurs, when two or more threads are competing in the acquisition of a lock. The scheduler has to decide whether it lets the thread, which has to wait sleeping and performs a context switch to let another thread occupy the CPU, or if letting the waiting thread busy-waiting is more efficient. Both ways introduce idle time to the inferior thread.

76. Which techniques help to reduce lock contention?

在某些情况下,可以通过应用以下技术之一来减少锁争用:

  • 锁的范围减小了。
  • The number of times a certain lock is acquired is reduced (lock splitting).
  • 使用硬件支持的乐观锁定操作而不是同步。
  • Avoid synchronization where possible.
  • Avoid object pooling.
77. Which technique to reduce lock contention can be applied to the following code?
 synchronized (map) {

    UUID randomUUID = UUID.randomUUID();

    Integer value = Integer.valueOf( 42 );

    String key = randomUUID.toString();

    map.put(key, value);
 }

The code above performs the computation of the random UUID and the conversion of the literal 42 into an Integer object within the synchronized block, although these two lines of code are local to the current thread and do not affect other threads. Hence they can be moved out of the synchronized block:

 UUID randomUUID = UUID.randomUUID();
 Integer value = Integer.valueOf( 42 );
 String key = randomUUID.toString();
 synchronized (map) {

    map.put(key, value);
 }
78. Explain by an example the technique lock splitting.

Lock splitting may be a way to reduce lock contention when one lock is used to synchronize access to different aspects of the same application. Suppose we have a class that implements the computation of some statistical data of our application. A first version of this class uses the keyword synchronized in each method signature in order to guard the internal state before corruption by multiple concurrent threads. This also means that each method invocation may cause lock contention as other threads may try to acquire the same lock simultaneously. But it may be possible to split the lock on the object instance into a few smaller locks for each type of statistical data within each method. Hence thread T1 that tries to increment the statistical data D1 does not have to wait for the lock while thread T2 simultaneously updates the data D2.

79. What do we understand by lock striping?

In contrast to lock splitting, where we introduce different locks for different aspects of the application, lock striping uses multiple locks to guard different parts of the same data structure. An example for this technique is the class ConcurrentHashMap from JDK's java.util.concurrent package. The Map implementation uses internally different buckets to store its values. The bucket is chosen by the value's key. ConcurrentHashMap now uses different locks to guard different hash buckets. Hence one thread that tries to access the first hash bucket can acquire the lock for this bucket, while another thread can simultaneously access a second bucket. In contrast to a synchronized version of HashMap this technique can increase the performance when different threads work on different buckets.

80. What is Lock interface in Java Concurrency API?

A java.util.concurrent.locks.Lock interface is used to as a thread synchronization mechanism similar to synchronized blocks. New Locking mechanism is more flexible and provides more options than a synchronized block.

Main differences between a Lock and a synchronized block are following:

  • Guarantee of sequence ? Synchronized block does not provide any guarantee of sequence in which waiting thread will be given access. Lock interface handles it.
  • No timeout ? Synchronized block has no option of timeout if lock is not granted. Lock interface provides such option.
  • Single method ? Synchronized block must be fully contained within a single method whereas a lock interface's methods lock() and unlock() can be called in different methods.

8.Executors

8.1 Executor Interfaces

81. Pros of ExecutorService over Timer
  • Timer can't take advantage of available CPU cores unlike ExecutorService especially with multiple tasks using flavours of ExecutorService like ForkJoinPool
  • ExecutorService provides collaborative API if you need coordination between multiple tasks. Assume that you have to submit N number of worker tasks and wait for completion of all of them. You can easily achieve it with invokeAll API. If you want to achieve the same with multiple Timer tasks, it would be not simple.
  • ThreadPoolExecutor provides better API for management of Thread life cycle.
82. What is the relation between the two interfaces Executor and ExecutorService?

The interface Executor only defines one method: execute(Runnable) . Implementations of this interface will have to execute the given Runnable instance at some time in the future. The ExecutorService interface is an extension of the Executor interface and provides additional methods to shut down the underlying implementation, to await the termination of all submitted tasks and it allows submitting instances of Callable .

83. What happens when you submit() a new task to an ExecutorService instance whose queue is already full?

As the method signature of submit() indicates, the ExecutorService implementation is supposed to throw a RejectedExecutionException .

84. What is a ScheduledExecutorService?

The interface ScheduledExecutorService extends the interface ExecutorService and adds method that allow to submit new tasks to the underlying implementation that should be executed a given point in time. There are two methods to schedule one-shot tasks and two methods to create and execute periodic tasks.

Learn more here:

Java CompletionService Example

Java ExecutorService Example – Tutorial

Java ScheduledExecutorService Example

Java RunnableScheduledFuture Example

java.util.concurrent.ThreadFactory Example

java.util.concurrent.RejectedExecutionException – How to solve RejectedExecutionException

Exchanger example passing logs to a background logger

java.util.concurrent.RejectedExecutionHandler Example

java.util.concurrent.ScheduledThreadPoolExecutor Example

8.2 Thread Pools

85. Do you know an easy way to construct a thread pool with 5 threads that executes some tasks that return a value?

The SDK provides a factory and utility class Executors whose static method newFixedThreadPool(int nThreads) allows the creation of a thread pool with a fixed number of threads (the implementation of MyCallable is omitted):

 public static void main(String[] args) throws InterruptedException, ExecutionException {

    ExecutorService executorService = Executors.newFixedThreadPool( 5 );

    Future<Integer>[] futures = new Future[ 5 ];

    for ( int i = 0 ; i < futures.length; i++) {

        futures[i] = executorService.submit( new MyCallable());

    }

    for ( int i = 0 ; i < futures.length; i++) {

        Integer retVal = futures[i].get();

        System.out.println(retVal);

    }

    executorService.shutdown();
 }
86. Is it possible to perform stream operations in Java 8 with a thread pool?

Collections provide the method parallelStream() to create a stream that is processed by a thread pool. Alternatively you can call the intermediate method parallel() on a given stream to convert a sequential stream to a parallel counterpart.

87. Is object pooling always a performance improvement for multi-threaded applications?

Object pools that try to avoid the construction of new objects by pooling them can improve the performance of single-threaded applications as the cost for object creation is interchanged by requesting a new object from the pool. In multi-threaded applications such an object pool has to have synchronized access to the pool and the additional costs of lock contention may outweigh the saved costs of the additional construction and garbage collection of the new objects. Hence object pooling may not always improve the overall performance of a multi-threaded application.

8.3 Fork/Join

88. How can we access the thread pool that is used by parallel stream operations?

The thread pool used for parallel stream operations can be accessed by ForkJoinPool.commonPool() . This way we can query its level of parallelism with commonPool.getParallelism() . The level cannot be changed at runtime but it can be configured by providing the following JVM parameter: -Djava.util.concurrent.ForkJoinPool.common.parallelism=5 .

89. What kind of tasks can be solved by using the Fork/Join framework?

The base class of the Fork/Join Framework java.util.concurrent.ForkJoinPool is basically a thread pool that executes instances of java.util.concurrent.ForkJoinTask . The class ForkJoinTask provides the two methods fork() and join() . While fork() is used to start the asynchronous execution of the task, the method join() is used to await the result of the computation. Hence the Fork/Join framework can be used to implement divide-and-conquer algorithms where a more complex problem is divided into a number of smaller and easier to solve problems.

90. Is it possible to find the smallest number within an array of numbers using the Fork/Join-Framework?

The problem of finding the smallest number within an array of numbers can be solved by using a divide-and-conquer algorithm. The smallest problem that can be solved very easily is an array of two numbers as we can determine the smaller of the two numbers directly by one comparison. Using a divide-and-conquer approach the initial array is divided into two parts of equal length and both parts are provided to two instances of RecursiveTask that extend the class ForkJoinTask . By forking the two tasks they get executed and either solve the problem directly, if their slice of the array has the length two, or they again recursively divide the array into two parts and fork two new RecursiveTasks. Finally each task instance returns its result (either by having it computed directly or by waiting for the two subtasks). The root tasks then returns the smallest number in the array.

91. What is the difference between the two classes RecursiveTask and RecursiveAction ?

In contrast to RecursiveTask the method compute() of RecursiveAction does not have to return a value. Hence RecursiveAction can be used when the action works directly on some data structure without having to return the computed value.

Learn more here:

java.util.concurrent.RecursiveTask Example

java.util.concurrent.ForkJoinPool Example

java.util.concurrent.ForkJoinWorkerThread Example

9.Concurrent Collections

92. What are Concurrent Collection Classes?

Java Collection classes are fail-fast which means that if the Collection will be changed while some thread is traversing over it using iterator, the iterator.next() will throw ConcurrentModificationException.

93. What is Java Memory Model?

The Java memory model describes how threads in the Java programming language interact through memory. Together with the description of single-threaded execution of code, the memory model provides the semantics of the Java programming language.

94. What is the difference between HashMap and Hashtable particularly with regard to thread-safety?

The methods of Hashtable are all synchronized. This is not the case for the HashMap implementation. Hence Hashtable is thread-safe whereas HashMap is not thread-safe. For single-threaded applications it is therefore more efficient to use the “newer” HashMap implementation.

95. Is there a simple way to create a synchronized instance of an arbitrary implementation of Collection , List or Map ?

The utility class Collections provides the methods synchronizedCollection(Collection) , synchronizedList(List) and synchronizedMap(Map) that return a thread-safe collection/list/map that is backed by the given instance.

Learn more here:

Java 8 Parallel Arrays Example

java.util.concurrent.ConcurrentNavigableMap Example

java.util.concurrent.ConcurrentSkipListMap Example

java.util.concurrent.CopyOnWriteArraySet Example

java.util.concurrent.CopyOnWriteArrayList Example

10.BlockingQueue

96. What is BlockingQueue?

A BlockingQueue is a java Queue that support operations that wait for the queue to become non-empty when retrieving and removing an element, and wait for space to become available in the queue when adding an element. The interface TransferQueue has been added. It is a refinement of the BlockingQueue interface in which producers can wait for consumers to receive elements.

Learn more here:

Java BlockingQueue Example

java.util.concurrent.DelayQueue Example

java.util.concurrent.LinkedBlockingQueue Example

Java.util.concurrent.SynchronousQueue Example

java.util.concurrent.ArrayBlockingQueue Example

java.util.concurrent.locks.AbstractQueuedSynchronizer Example

Blocking Queue example to execute commands

Blocking Queue example of limited connection pool

Synchronous Queue example to execute commands

11.Atomic Variables

97. What do we understand by a CAS operation?

CAS stands for compare-and-swap and means that the processor provides a separate instruction that updates the value of a register only if the provided value is equal to the current value. CAS operations can be used to avoid synchronization as the thread can try to update a value by providing its current value and the new value to the CAS operation. If another thread has meanwhile updated the value, the thread's value is not equal to the current value and the update operation fails. The thread then reads the new value and tries again. That way the necessary synchronization is interchanged by an optimistic spin waiting.

98. Which Java classes use the CAS operation?

The SDK classes in the package java.util.concurrent.atomic like AtomicInteger or AtomicBoolean use internally the CAS operation to implement concurrent incrementation.

 public class CounterAtomic {

    private AtomicLong counter = new AtomicLong(); 
    public void increment() {

        counter.incrementAndGet();

    }

    public long get() {

        return counter.get();

    }
 }

Learn more here:

java.util.concurrent.atomic.AtomicBoolean Example

java.util.concurrent.atomic.AtomicLongArray Example

Java AtomicIntegerArray Example

Java AtomicInteger示例

Java AtomicMarkableReference Example

Java AtomicReference Example

12. Concurrent Random Numbers

99. What is the purpose of the class java.lang.ThreadLocal ?

As memory is shared between different threads, ThreadLocal provides a way to store and retrieve values for each thread separately. Implementations of ThreadLocal store and retrieve the values for each thread independently such that when thread A stores the value A1 and thread B stores the value B1 in the same instance of ThreadLocal , thread A later on retrieves value A1 from this ThreadLocal instance and thread B retrieves value B1.

100. What are possible use cases for java.lang.ThreadLocal ?

Instances of ThreadLocal can be used to transport information throughout the application without the need to pass this from method to method. Examples would be the transportation of security/login information within an instance of ThreadLocal such that it is accessible by each method. Another use case would be to transport transaction information or in general objects that should be accessible in all methods without passing them from method to method.

Learn more here:

java.util.concurrent.ThreadLocalRandom Example

Ok, so now you are ready for your interview! Don't forget to check our FREE Academy course Java Concurrency Essentials !

If you enjoyed this, then subscribe to our newsletter to enjoy weekly updates and complimentary whitepapers! Also, check out JCG Academy for more advanced training!

欢迎您提出您的意见,我们会将其纳入本文! nclude them in the article!

Last Updated Jan. 26, 2019

翻译自: https://www.javacodegeeks.com/2014/11/multithreading-concurrency-interview-questions-answers.html

多线程面试题

  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值