多线程面试题 - Go语言中文社区

多线程面试题


多线程,线程池,线程安全 

多线程

  1. 程序、进程、线程的区别是什么? 举个现实的例子说明。

    1. 程序(Program):是一个指令的集合。程序不能独立执行,只有被加载到内存中,系统为它分配资源后才能执行

    2. 进程(Process):如上所述,一个执行中的程序称为进程。
      进程是系统分配资源的独立单位,每个进程占有特定的地址空间。
      程序是进程的静态文本描述,进程是程序在系统内顺序执行的动态活动。

    3.  线程(Thread):是进程的“单一的连续控制流程“。
      线程是CPU调度和分配的基本单位,是比进程更小的能独立运行的基本单位,也被称为轻量级的进程。
       线程不能独立存在,必须依附于某个进程。一个进程可以包括多个并行的线程,一个线程肯定属于一个进程。Java虚拟机允许应用程序并发地执行多个线程。

    4. 举例:如一个车间是一个程序,一个正在进行生产任务的车间是一个进程,车间内每个从事不同工作的工人是一个线程。

  2. 什么是线程? 
    线程是操作系统能够进行运算调度的最小单位,它被包含在进程之中,是进程中的实际运作单位。程序员可以通过它进行多处理器编程,你可以使用多线程对 运算密集型任务提速。比如,如果一个线程完成一个任务要100毫秒,那么用十个线程完成改任务只需10毫秒。
  3. 线程和进程有什么区别?
    线程是进程的子集,一个进程可以有很多线程,每条线程并行执行不同的任务。不同的进程使用不同的内存空间,而所有的线程共享一片相同的内存空间。别把它和栈内存搞混,每个线程都拥有单独的栈内存用来存储本地数据。
  4. 线程的几种状态

    线程通常有五种状态,创建,就绪,运行、阻塞和死亡状态。

    1. 新建状态(New):新创建了一个线程对象。

    2. 就绪状态(Runnable):线程对象创建后,其他线程调用了该对象的start()方法。该状态的线程位于可运行线程池中,变得可运行,等待获取CPU的使用权。

    3. 运行状态(Running):就绪状态的线程获取了CPU,执行程序代码。 运行状态(Running):就绪状态的线程获取了CPU,执行程序代码。

    4. 阻塞状态(Blocked):阻塞状态是线程因为某种原因放弃CPU使用权,暂时停止运行。直到线程进入就绪状态,才有机会转到运行状态。阻塞的情况又分为三种:

      1. 等待阻塞:运行的线程执行wait()方法,该线程会释放占用的所有资源,JVM会把该线程放入“等待池”中。进入这个状态后,是不能自动唤醒的,必须依靠其他线程调用notify()或notifyAll()方法才能被唤醒,wait是object类的方法

      2. 同步阻塞:运行的线程在获取对象的同步锁时,若该同步锁被别的线程占用,则JVM会把该线程放入“锁池”中。

      3. 其他阻塞:运行的线程执行sleep()或join()方法,或者发出了I/O请求时,JVM会把该线程置为阻塞状态。当sleep()状态超时、join()等待线程终止或者超时、或者I/O处理完毕时,线程重新转入就绪状态。sleep是Thread类的方法

    5. 死亡状态(Dead):线程执行完了或者因异常退出了run()方法,该线程结束生命周期。
  5. Java中通过哪些方式创建多线程类?
    1. 继承Thread类创建线程,Thread类有实现Runnable接口
    2. 实现Runnable接口创建线程
    3. 实现Callable接口通过FutureTask包装器来创建Thread线程
      import java.util.concurrent.Callable;
      
      public class CallTask implements Callable<Integer>{
      
      	@Override
      	public Integer call() throws Exception {
      		int num=0;
      		for(int i=0;i<100;i++) {
      			num+=i;
      		}
      		return num;
      	}
      
      }
      
      
      import java.util.concurrent.ExecutionException;
      import java.util.concurrent.FutureTask;
      
      public class CallableTest {
          public static void main(String[] args) throws ExecutionException, InterruptedException {
              
              CallTask callTask = new CallTask();
              //使用FutureTask实现类接收返回值 泛型是返回值类型
              FutureTask <Integer> futureTask = new FutureTask <>(callTask);
      
              for (int i = 0; i < 100; i++) {
                  new Thread(futureTask).start();
                  Integer sum = futureTask.get();
                  System.out.println(i+"---"+sum);
              }
          }
      }
      
    4. 使用ExecutorServiceCallableFuture实现有返回结果的线程
  6. 用Runnable还是Thread?
    这个问题是上题的后续,大家都知道我们可以通过继承Thread类或者调用Runnable接口来实现线程,问题是,那个方法更好呢?什么情况下使 用它?这个问题很容易回答,如果你知道Java不支持类的多重继承,但允许你调用多个接口。所以如果你要继承其他类,当然是调用Runnable接口好 了。
  7. Java中Runnable和Callable有什么不同?
    Runnable和Callable都代表那些要在不同的线程中执行的任务。Runnable从JDK1.0开始就有了,Callable是在 JDK1.5增加的。它们的主要区别是Callable的 call() 方法可以返回值和抛出异常,而Runnable的run()方法没有这些功能。Callable可以返回装载有计算结果的Future对象。
  8.  什么是FutureTask?
    在Java并发程序中FutureTask表示一个可以取消的异步运算。它有启动和取消运算、查询运算是否完成和取回运算结果等方法。只有当运算完 成的时候结果才能取回,如果运算尚未完成get方法将会阻塞。一个FutureTask对象可以对调用了Callable和Runnable的对象进行包 装,由于FutureTask也是调用了Runnable接口,所以它可以提交给Executor来执行。
  9. Thread 类中的start() 和 run() 方法有什么区别?
    这个问题经常被问到,但还是能从此区分出面试者对Java线程模型的理解程度。start()方法被用来启动新创建的线程,而且start()内部 调用了run()方法,这和直接调用run()方法的效果不一样。当你调用run()方法的时候,只会是在原来的线程中调用,没有新的线程启动,start()方法才会启动新线程。
  10. 当调用一个线程对象的start方法后,线程马上进入运行状态吗?
    不是,只是进入就绪(可运行)状态,等待分配CPU时间片。一旦得到CPU时间片,即进入运行状态。
  11. A线程的优先级是10,B线程的优先级是1,那么当进行调度时一定会调用A吗?A线程的优先级是10,B线程的优先级是1,那么当进行调度时一定会调用A吗?(线程的调度)
    不一定,Java中在对线程调度时,也采用了优先级调度策略,具体策略为:“优先级高的线程应该有更大的获取CPU资源执行的概率,优先级低的线程并不是总不能执行”。
  12. 说说:sleep、yield、join、wait方法的区别。
    1. sleep()方法需要指定等待的时间,它可以让当前正在执行的线程在指定的时间内暂停执行,进入同步阻塞状态,该方法既可以让其他同优先级或者高优先级的线程得到执行的机会,也可以让低优先级的线程得到执行机会。但是sleep()方法不会释放“锁标志”,也就是说如果有synchronized同步块,其他线程仍然不能访问共享数据。 作用于线程
      1. Thread.sleep()方法用来暂停线程的执行,将CPU放给线程调度器。
      2. Java有两种sleep方法,一个只有一个毫秒参数,另一个有毫秒和纳秒两个参数。
      3. 与wait方法不同,sleep方法不会释放锁
      4. 如果其他的线程中断了一个休眠的线程,sleep方法会抛出Interrupted Exception。
      5. 休眠的线程在唤醒之后不保证能获取到CPU,它会先进入就绪态,与其他线程竞争CPU。
      6. 有一个易错的地方,当调用t.sleep()的时候,会暂停线程t。这是不对的,因为Thread.sleep是一个静态方法,它会使当前线程而不是线程t进入休眠状态。
      7. Thread.sleep()方法是一个静态方法,它暂停的是当前执行的线程。
    2. join(): 当前线程等待,调用此方法的线程执行结束再继续执行。如:在main方法中调用t.join(),那main方法在此时进入阻塞状态,一直等t线程执行完,main方法再恢复到就绪状态,准备继续执行。
      join方法必须在线程start方法调用之后调用才有意义。这个也很容易理解:如果一个线程都没有start,那它也就无法同步了。作用于线程

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

    4. wait():

      1. wait只能在同步(synchronize)环境中被调用,而sleep不需要。

      2. 进入wait状态的线程能够被notify和notifyAll线程唤醒,但是进入sleeping状态的线程不能被notify方法唤醒。

      3. wait方法在进入wait状态的时候会释放对象的锁,但是sleep方法不会。

      4. wait方法是针对一个被同步代码块加锁的对象

  13. wait、notify、notifyAll是在Thread类中定义的方法吗?作用分别是什么?
    wait(),notify(),notifyAll()不属于Thread类,而是属于Object类,也就是说每个对象都有wait(),notify(),notifyAll()的功能。
    因为每个对像都有锁,锁是每个对像的基础,而wait(),notify(),notifyAll()都是跟锁有关的方法。
    三个方法的作用分别是:
    1. wait:导致当前线程等待,进入阻塞状态,直到其他线程调用此对象的 notify() 方法或 notifyAll() 方法。当前线程必须拥有此对象监视器(对象锁)。该线程释放对此监视器的所有权并等待,直到其他线程通过调用 notify 方法,或 notifyAll 方法通知在此对象的监视器上等待的线程醒来。然后该线程将等到重新获得对监视器的所有权后才能继续执行.

    2. notify:唤醒在此对象监视器(对象锁)上等待的单个线程。如果所有线程都在此对象上等待,则会选择唤醒其中一个线程(随机选取一个线程)。直到当前线程放弃此对象上的锁定,才能继续执行被唤醒的线程。此方法只应由作为此对象监视器的所有者的线程来调用.

      "当前线程必须拥有此对象监视器"与"此方法只应由作为此对象监视器的所有者的线程来调用"说明wait方法与notify方法必须在同步块内执行,即synchronized(obj之内).

    3. notifyAll: 唤醒在此对象监视器(对象锁)上等待的所有线程。

  14. 为什么不推荐使用stop和destroy方法来结束线程的运行?
    1. destroy():该方法最初用于破坏该线程,但不作任何资源释放。它所保持的任何监视器都会保持锁定状态。不过,该方法决不会被实现。即使要实现,它也极有可能以 suspend() 方式被死锁。如果目标线程被破坏时保持一个保护关键系统资源的锁,则任何线程在任何时候都无法再次访问该资源。如果另一个线程曾试图锁定该资源,则会出现死锁。
    2. stop():此方法可以强行中止一个正在运行或挂起的线程。但stop方法不安全,就像强行切断计算机电源,而不是按正常程序关机。可能会产生不可预料的结果。

      举例来说:

      当在一个线程对象上调用stop()方法时,这个线程对象所运行的线程就会立即停止,并抛出特殊的ThreadDeath()异常。这里的“立即”因为太“立即”了,

      假如一个线程正在执行:

      synchronized void {

       x = 3;

       y = 4;

      }

      由于方法是同步的,多个线程访问时总能保证x,y被同时赋值,而如果一个线程正在执行到x = 3;时,被调用了 stop()方法,即使在同步块中,它也干脆地stop了,这样就产生了不完整的残废数据。而多线程编程中最最基础的条件要保证数据的完整性,所以请忘记 线程的stop方法,以后我们再也不要说“停止线程”了。

  15. Java中如何停止一个线程?
    Java提供了很丰富的API但没有为停止线程提供API。JDK 1.0本来有一些像stop(), suspend() 和 resume()的控制方法但是由于潜在的死锁威胁因此在后续的JDK版本中他们被弃用了,之后Java API的设计者就没有提供一个兼容且线程安全的方法来停止一个线程。当run() 或者 call() 方法执行完的时候线程会自动结束,如果要手动结束一个线程,你可以用volatile 布尔变量来退出run()方法的循环或者是取消任务来中断线程。

  16. 写个代码说明,终止线程的典型方式。
    1.  当run()方法执行完后,线程就自动终止了。

    2. 但有些时候run()方法不会结束(如服务器端监听程序),或者其它需要用循环来处理的任务。在这种情况下,一般是将这些任务放在一个循环中,如while循环。如果想让循环永远运行下去,可以使用while(true){……}来处理。但要想使while循环在某一特定条件下退出,最直接的方法就是设一个boolean类型的标志,并通过设置这个标志为true或false来控制while循环是否退出。

  17. 一个线程运行时发生异常会怎样?
    简单的说,如果异常没有被捕获该线程将会停止执行
    Thread.UncaughtExceptionHandler是用于处理未捕获异常造成线程突然中断情况的一个内嵌接口。当一个未捕获异常将造成线程中断的时候,JVM会使用Thread.getUncaughtExceptionHandler()来查询线程的UncaughtExceptionHandler,并将线程和异常作为参数传递给handler的uncaughtException()方法 进行处理。
  18. 有三个线程T1,T2,T3,怎么确保它们按顺序执行?
    在多线程中有多种方法让线程按特定顺序执行,你可以用线程类的join()方法在一个线程中启动另一个线程,另外一个线程完成该线程继续执行。为了确保三个线程的顺序你应该先启动最后一个(T3调用T2,T2调用T1),这样T1就会先完成而T3最后完成。
  19. 现在有T1、T2、T3三个线程,你怎样保证T2在T1执行完后执行,T3在T2执行完后执行?
    这个线程问题通常会在第一轮或电话面试阶段被问到,目的是检测你对”join”方法是否熟悉。这个多线程问题比较简单,可以用join方法实现。
  20. 下面的代码,实际上有几个线程在运行:

    public static void main(String[] argc) throws Exception {
    
                Runnable r = new Thread6();
    
                Thread t = new Thread(r, "Name test");
    
                t.start();
    
    }
    
    两个:线程t和main()方法(主线程)。

 

 

线程池

  1. 什么是线程池? 
    在程序启动的时 候就创建若干线程来响应处理,它们被称为线程池,里面的线程叫工作线程
  2. 为什么用线程池?

    通过重用线程池中的线程,来减少每个线程创建和销毁的性能开销。
    对线程进行一些维护和管理,比如定时开始,周期执行,并发数控制等等。

  3. 线程池参数什么意思?
    比如去火车站买票, 有10个售票窗口, 但只有5个窗口对外开放. 那么对外开放的5个窗口称为核心线程数, 而最大线程数是10个窗口.如果5个窗口都被占用, 那么后来的人就必须在后面排队, 但后来售票厅人越来越多, 已经人满为患, 就类似于线程队列已满.这时候火车站站长下令, 把剩下的5个窗口也打开, 也就是目前已经有10个窗口同时运行. 后来又来了一批人,10个窗口也处理不过来了, 而且售票厅人已经满了, 这时候站长就下令封锁入口,不允许其他人再进来, 这就是线程异常处理策略.而线程存活时间指的是, 允许售票员休息的最长时间, 以此限制售票员偷懒的行为.
  4. 讲一讲线程池中的threadpoolexecutor,每个参数干什么用的?
    Executor是一个接口,跟线程池有关的基本都要跟他打交道。ThreadPoolExecutor的关系 

    Executor接口很简单,只有一个execute方法。
    ExecutorService是Executor的子接口,增加了一些常用的对线程的控制方法,之后使用线程池主要也是使用这些方法。
    AbstractExecutorService是一个抽象类。ThreadPoolExecutor就是实现了这个类。
    线程池类ThreadPoolExecutor在包java.util.concurrent下。
    ThreadPoolExecutor的参数:

    ThreadPoolExecutor mExecutor = new ThreadPoolExecutor(corePoolSize,// 核心线程数
                                            maximumPoolSize, // 最大线程数
                                            keepAliveTime, // 闲置线程存活时间
                                            TimeUnit.MILLISECONDS,// 时间单位
                                            new LinkedBlockingDeque<Runnable>(),// 线程队列
                                            Executors.defaultThreadFactory(),// 线程工厂
                                            new AbortPolicy()// 队列已满,而且当前线程数已经超过最大线程数时的异常处理策略
    
                              );

    corePoolSize

    核心线程数,默认情况下核心线程会一直存活,即使处于闲置状态也不会受存keepAliveTime限制。除非将allowCoreThreadTimeOut设置为true。

    maximumPoolSize

    线程池所能容纳的最大线程数。超过这个数的线程将被阻塞。当任务队列为没有设置大小的LinkedBlockingDeque时,这个值无效。

    keepAliveTime

    非核心线程的闲置超时时间,超过这个时间就会被回收。

    unit

    指定keepAliveTime的单位,如TimeUnit.SECONDS。当将allowCoreThreadTimeOut设置为true时对corePoolSize生效。

    workQueue

    线程池中的任务队列.

    常用的有三种队列,SynchronousQueue,LinkedBlockingDeque,ArrayBlockingQueue。

    threadFactory

    线程工厂,提供创建新线程的功能。ThreadFactory是一个接口,只有一个方法

    public interface ThreadFactory {

      Thread newThread(Runnable r);

    }

    通过线程工厂可以对线程的一些属性进行定制。

    RejectedExecutionHandler

    RejectedExecutionHandler也是一个接口,只有一个方法

    public interface RejectedExecutionHandler {

      void rejectedExecution(Runnable var1, ThreadPoolExecutor var2);

    }

    当线程池中的资源已经全部使用,添加新线程被拒绝时,会调用RejectedExecutionHandler的rejectedExecution方法。

  5. 说一下线程池内部使用规则

    1. 下面都假设任务队列没有大小限制:

      1. 如果线程数量<=核心线程数量,那么直接启动一个核心线程来执行任务,不会放入队列中。

      2. 如果线程数量>核心线程数,但<=最大线程数,并且任务队列是LinkedBlockingDeque的时候,超过核心线程数量的任务会放在任务队列中排队。

      3. 如果线程数量>核心线程数,但<=最大线程数,并且任务队列是SynchronousQueue的时候,线程池会创建新线程执行任务,这些任务也不会被放在任务队列中。这些线程属于非核心线程,在任务完成后,闲置时间达到了超时时间就会被清除。

      4. 如果线程数量>核心线程数,并且>最大线程数,当任务队列是LinkedBlockingDeque,会将超过核心线程的任务放在任务队列中排队。也就是当任务队列是LinkedBlockingDeque并且没有大小限制时,线程池的最大线程数设置是无效的,他的线程数最多不会超过核心线程数。

      5. 如果线程数量>核心线程数,并且>最大线程数,当任务队列是SynchronousQueue的时候,会因为线程池拒绝添加任务而抛出异常。

    2. 任务队列大小有限时

      1. 当LinkedBlockingDeque塞满时,新增的任务会直接创建新线程来执行,当创建的线程数量超过最大线程数量时会抛异常。

      2. SynchronousQueue没有数量限制。因为他根本不保持这些任务,而是直接交给线程池去执行。当任务数量超过最大线程数时会直接抛异常。

  6. 如果你提交任务时,线程池队列已满。会时发会生什么?
    这个问题问得很狡猾,许多程序员会认为该任务会阻塞直到线程池队列有空位。事实上如果一个任务不能被调度执行那么ThreadPoolExecutor’s submit()方法将会抛出一个RejectedExecutionException异常。
  7. Java线程池中submit() 和 execute()方法有什么区别?
    两个方法都可以向线程池提交任务,execute()方法的返回类型是void,它定义在Executor接口中, 而submit()方法可以返回持有计算结果的Future对象,它定义在ExecutorService接口中,它扩展了Executor接口,其它线 程池类像ThreadPoolExecutor和ScheduledThreadPoolExecutor都有这些方法。

 

 

线程安全

  1. Java内存模型是什么?
    1. 主内存与工作内存
    2. 内存间交互操作
    3. 对于volatile型变量的特殊规则
    4. 对于long和double型变量的特殊规则
    5. 原子性、可见性与有序性
    6. 先行发生原则
  2. Java中的volatile 变量是什么?
    volatile是一个特殊的修饰符,只有成员变量才能使用它。在Java并发程序缺少同步类的情况下,多线程对成员变量的操作对其它线程是透明的。volatile变量可以保证下一个读取操作会在前一个写操作之后发生。
  3. Java中的volatile关键字是什么作用?怎样使用它?在Java中它跟synchronized方法有什么不同?
    自从Java 5和Java内存模型改变以后,基于volatile关键字的线程问题越来越流行。应该准备好回答关于volatile变量怎样在并发环境中确保可见性、顺序性和一致性。
  4. volatile 变量和 atomic 变量有什么不同?
    这是个有趣的问题。首先,volatile 变量和 atomic 变量看起来很像,但功能却不一样。Volatile变量可以确保先行关系,即写操作会发生在后续的读操作之前, 但它并不能保证原子性。例如用volatile修饰count变量那么 count++ 操作就不是原子性的。而AtomicInteger类提供的atomic方法可以让这种操作具有原子性如getAndIncrement()方法会原子性 的进行增量操作把当前值加一,其它数据类型和引用变量也可以进行相似操作。

    简单来说, volatile 只解决可见性,不保证同步。
    atomic 则是保证了可见性 和同步性。内部采用CAS算法实现。
  5. 说一下 atomic 的原理?
    atomic底层使用CAS算法保证同步。
  6. 什么是线程安全?
    用mysqL的锁的概念来说,就是能保证共享数据的并发性。
  7. Java中synchronized 和 ReentrantLock 有什么不同?
    在基本用法上,ReentrantLock与synchronized很相似,他们都具备一样的线程重入特性,只是代码写法上有点区别,一个表现为API层面的互斥锁(lock()和unlock()方法配合try/finally语句块来完成),另一个表现为原生语法层面的互斥锁。不过,相比synchronized,ReentrantLock增加了一些高级功能,主要有以下3项:等待可中断、可实现公平锁,以及锁可以绑定多个条件。
    优先考虑使用synchronized来进行同步,synchronized的效率更高。
  8. 多线程同步的方式
    1. 同步有两种方式,第一种是使用关键字synchronized,第二种是使用接口lock

    2. 对于线程交互,synchnronized有object对象的wait(),notify()和notifyall()等方法;lock也有相对应的Condition.await(),Condition.signal()和Condition.signalall()等方法

    3. synchronized会死锁,lock也会死锁,但是lock可以使用lock.trylock()尝试获得锁,如果超出设定时间就放弃,而不是一直请求

    4. synchronized会自动解锁,lock必须手动解锁lock.unlock()

  9. Java中的同步集合与并发集合有什么区别?
    同步集合与并发集合都为多线程和并发提供了合适的线程安全的集合,不过并发集合的可扩展性更高。在Java1.5之前程序员们只有同步集合来用且在 多线程并发的时候会导致争用,阻碍了系统的扩展性。Java5介绍了并发集合像ConcurrentHashMap,不仅提供线程安全还用锁分离和内部分区等现代技术提高了可扩展性。
  10. 如何避免死锁?

    Java多线程中的死锁
    死锁是指两个或两个以上的进程在执行过程中,因争夺资源而造成的一种互相等待的现象,若无外力作用,它们都将无法推进下去。这是一个严重的问题,因为死锁会让你的程序挂起无法完成任务,死锁的发生必须满足以下四个条件:
    1. 互斥条件:一个资源每次只能被一个进程使用。
    2. 请求与保持条件:一个进程因请求资源而阻塞时,对已获得的资源保持不放。
    3. 不剥夺条件:进程已获得的资源,在末使用完之前,不能强行剥夺。
    4. 循环等待条件:若干进程之间形成一种头尾相接的循环等待资源关系。

      避免死锁最简单的方法就是阻止循环等待条件,将系统中所有的资源设置标志位、排序,规定所有的进程申请资源必须以一定的顺序(升序或降序)做操作来避免死锁。
  11. Java中活锁和死锁有什么区别?
    这是上题的扩展,活锁和死锁类似,不同之处在于处于活锁的线程或进程的状态是不断改变的,活锁可以认为是一种特殊的饥饿。一个现实的活锁例子是两个 人在狭小的走廊碰到,两个人都试着避让对方好让彼此通过,但是因为避让的方向都一样导致最后谁都不能通过走廊。简单的说就是,活锁和死锁的主要区别是前者 进程的状态可以改变但是却不能继续执行。
  12. 怎么检测一个线程是否拥有锁?
    我一直不知道我们竟然可以检测一个线程是否拥有锁,直到我参加了一次电话面试。在java.lang.Thread中有一个方法叫holdsLock(),它返回true如果当且仅当当前线程拥有某个具体对象的锁。
  13. 你如何在Java中获取线程堆栈?
    对于不同的操作系统,有多种方法来获得Java进程的线程堆栈。当你获取线程堆栈时,JVM会把所有线程的状态存到日志文件或者输出到控制台。
    在 Windows你可以使用Ctrl + Break组合键来获取线程堆栈,Linux下用kill -3命令。
    你也可以用jstack这个工具来获取,它对线程id进行操作,你可以用jps这个工具找到id。
  14. 你将如何使用thread dump?你将如何分析Thread dump?
    在UNIX中你可以使用kill -3,然后thread dump将会打印日志,在windows中你可以使用”CTRL+Break”。非常简单和专业的线程面试问题,但是如果他问你怎样分析它,就会很棘手。 
  15. JVM中哪个参数是用来控制线程的堆栈大小的
    这个问题很简单, -Xss参数用来控制线程的堆栈大小。
  16. Java中ConcurrentHashMap的并发度是什么?
    ConcurrentHashMap把实际map划分成若干部分来实现它的可扩展性和线程安全。这种划分是使用并发度获得的,它是 ConcurrentHashMap类构造函数的一个可选参数,默认值为16,这样在多线程情况下就能避免争用。
  17. Java中的ReadWriteLock是什么?
    一般而言,读写锁是用来提升并发程序性能的锁分离技术的成果。Java中的ReadWriteLock是Java 5 中新增的一个接口,一个ReadWriteLock维护一对关联的锁,一个用于只读操作一个用于写。在没有写线程的情况下一个读锁可能会同时被多个读线程 持有。写锁是独占的,你可以使用JDK中的ReentrantReadWriteLock来实现这个规则,它最多支持65535个写锁和65535个读 锁。
  18. 多线程中的忙循环是什么?
    忙循环就是程序员用循环让一个线程等待,不像传统方法wait(), sleep() 或 yield() 它们都放弃了CPU控制,而忙循环不会放弃CPU,它就是在运行一个空循环。这么做的目的是为了保留CPU缓存,在多核系统中,一个等待线程醒来的时候可能会在另一个内核运行,这样会重建缓存。为了避免重建缓存和减少等待重建的时间就可以使用它了。
  19. 如果同步块内的线程抛出异常会发生什么?
    这个问题坑了很多Java程序员,若你能想到锁是否释放这条线索来回答还有点希望答对。无论你的同步块是正常还是异常退出的,里面的线程都会释放锁,所以对比锁接口我更喜欢同步块,因为它不用我花费精力去释放锁,该功能可以在finally block里释放锁实现。
  20. 单例模式的双检锁是什么?
    这个问题在Java面试中经常被问到,但是面试官对回答此问题的满意度仅为50%。一半的人写不出双检锁还有一半的人说不出它的隐患和 Java1.5是如何对它修正的。它其实是一个用来创建线程安全的单例的老方法,当单例实例第一次被创建时它试图用单个锁进行性能优化,但是由于太过于复 杂在JDK1.4中它是失败的,我个人也不喜欢它。无论如何,即便你也不喜欢它但是还是要了解一下,因为它经常被问到。
  21.  如何在Java中创建线程安全的Singleton?
    这是上面那个问题的后续,如果你不喜欢双检锁而面试官问了创建Singleton类的替代方法,你可以利用JVM的类加载和静态变量初始化特征来创建Singleton实例,或者是利用枚举类型来创建Singleton,我很喜欢用这种方法。
  22. 写出3条你遵循的多线程最佳实践
    这种问题我最喜欢了,我相信你在写并发代码来提升性能的时候也会遵循某些最佳实践。以下三条最佳实践我觉得大多数Java程序员都应该遵循:
    1. 给你的线程起个有意义的名字。
      这样可以方便找bug或追踪。OrderProcessor, QuoteProcessor or TradeProcessor 这种名字比 Thread-1. Thread-2 and Thread-3 好多了,给线程起一个和它要完成的任务相关的名字,所有的主要框架甚至JDK都遵循这个最佳实践。
    2. 避免锁定和缩小同步的范围
      锁花费的代价高昂且上下文切换更耗费时间空间,试试最低限度的使用同步和锁,缩小临界区。因此相对于同步方法我更喜欢同步块,它给我拥有对锁的绝对控制权。
    3. 多用同步类少用wait 和 notify
      首先,CountDownLatch, Semaphore, CyclicBarrier 和 Exchanger 这些同步类简化了编码操作,而用wait和notify很难实现对复杂控制流的控制。其次,这些类是由最好的企业编写和维护在后续的JDK中它们还会不断 优化和完善,使用这些更高等级的同步工具你的程序可以不费吹灰之力获得优化。
    4. 多用并发集合少用同步集合
      这是另外一个容易遵循且受益巨大的最佳实践,并发集合比同步集合的可扩展性更好,所以在并发编程时使用并发集合效果更好。如果下一次你需要用到map,你应该首先想到用ConcurrentHashMap。
  23.  如何强制启动一个线程?
    这个问题就像是如何强制进行Java垃圾回收,目前还没有觉得方法,虽然你可以使用System.gc()来进行垃圾回收,但是不保证能成功。在Java里面没有办法强制启动一个线程,它是被线程调度器控制着且Java没有公布相关的API。
  24. 在Java中Lock接口比synchronized块的优势是什么?你需要实现一个高效的缓存,它允许多个用户读,但只允许一个用户写,以此来保持它的完整性,你会怎样去实现它?
    |lock接口在多线程和并发编程中最大的优势是它们为读和写分别提供了锁,它能满足你写像ConcurrentHashMap这样的高性能数据结构和有条件的阻塞。Java线程面试的问题越来越会根据面试者的回答来提问。我强烈建议在你去参加多线程的面试之前认真读一下Locks,因为当前其大量用于构建电子交易终统的客户端缓存和交易连接空间。
  25. 用Java实现阻塞队列?
    这是一个相对艰难的多线程面试问题,它能达到很多的目的。
    第一,它可以检测侯选者是否能实际的用Java线程写程序;
    第二,可以检测侯选者对并发场景的理解,并且你可以根据这个问很多问题。如果他用wait()和notify()方法来实现阻塞队列,你可以要求他用最新的Java 5中的并发类来再写一次。
  26. 用Java编程一个会导致死锁的程序,你将怎么解决?
    这是我最喜欢的Java线程面试问题,因为即使死锁问题在写多线程并发程序时非常普遍,但是很多侯选者并不能写deadlock free code(无死锁代码?),他们很挣扎。只要告诉他们,你有N个资源和N个线程,并且你需要所有的资源来完成一个操作。为了简单这里的n可以替换为2,越大的数据会使问题看起来更复杂。通过避免Java中的死锁来得到关于死锁的更多信息。
  27.  什么是原子操作,Java中的原子操作是什么?
    JDK6 以后,新增加了一个包java.util.concurrent.atomic,里面有各种原子类,比如AtomicInteger。
    而AtomicInteger提供了各种自增,自减等方法,这些方法都是原子性的。 换句话说,自增方法 incrementAndGet 是线程安全的,同一个时间,只有一个线程可以调用这个方法
  28. 什么是竞争条件?你怎样发现和解决竞争?
    这是一道出现在多线程面试的高级阶段的问题。大多数的面试官会问最近你遇到的竞争条件,以及你是怎么解决的。有些时间他们会写简单的代码,然后让你检测出代码的竞争条件。可以参考我之前发布的关于Java竞争条件的文章。在我看来这是最好的java线程面试问题之一,它可以确切的检测候选者解决竞争条件的经验,or writing code which is free of data race or any other race condition。关于这方面最好的书是《Concurrency practices in Java》。
  29. Java中你怎样唤醒一个阻塞的线程
    这是个关于线程和阻塞的棘手的问题,它有很多解决方法。如果线程遇到了IO阻塞,我并且不认为有一种方法可以中止线程。如果线程因为调用wait()、sleep()、或者join()方法而导致的阻塞,你可以中断线程,并且通过抛出InterruptedException来唤醒它。我之前写的《How to deal with blocking methods in java》有很多关于处理线程阻塞的信息。
  30. 在Java中CycliBarriar和CountdownLatch有什么区别?
    这个线程问题主要用来检测你是否熟悉JDK5中的并发包。这两个的区别是CyclicBarrier可以重复使用已经通过的障碍,而CountdownLatch不能重复使用。
  31. 什么是不可变对象,它对写并发应用有什么帮助?
    另一个多线程经典面试问题,并不直接跟线程有关,但间接帮助很多。这个java面试问题可以变的非常棘手,如果他要求你写一个不可变对象,或者问你为什么String是不可变的。
  32. 你在多线程环境中遇到的共同的问题是什么?你是怎么解决它的?
    多线程和并发程序中常遇到的有Memory-interface、竞争条件、死锁、活锁和饥饿。问题是没有止境的,如果你弄错了,将很难发现和调试。这是大多数基于面试的,而不是基于实际应用的Java线程问题。
  33. ThreadLocal 是什么?有哪些使用场景?
  34. 补充的其它几个问题:
    1. Java中用到的线程调度算法是什么?
      抢占式。一个线程用完CPU之后,操作系统会根据线程优先级、线程饥饿情况等数据算出一个总的优先级并分配下一个时间片给某个线程执行。
      操作系统中可能会出现某条线程常常获取到VPU控制权的情况,为了让某些优先级比较低的线程也能获取到CPU控制权,可以使用Thread.sleep(0)手动触发一次操作系统分配时间片的操作,这也是平衡CPU控制权的一种操作。
    2. 在线程中你怎么处理不可捕捉异常?
      在java中要捕捉多线程产生的异常,需要自定义异常处理器,并设定到对应的线程工厂中
    3. 为什么使用Executor框架比使用应用创建和管理线程好?
    4. 在Java中Executor和Executors的区别?
      1. Executor 接口对象能执行我们的线程任务;
      2. Executors 工具类的不同方法按照我们的需求创建了不同的线程池,来满足业务的需求。
      3. ExecutorService 接口继承了Executor接口并进行了扩展,提供了更多的方法,我们能够获得任务执行的状态并且可以获取任务的返回值。
    5. 如何在Windows和Linux上查找哪个线程使用的CPU时间最长?

这些面试题参考多位博主整理而来,由于参考的博主没有标明出处,所以就不标明原地址了。

版权声明:本文来源CSDN,感谢博主原创文章,遵循 CC 4.0 by-sa 版权协议,转载请附上原文出处链接和本声明。
原文链接:https://blog.csdn.net/yiguang_820/article/details/83542510
站方申明:本站部分内容来自社区用户分享,若涉及侵权,请联系站方删除。
  • 发表于 2019-08-27 15:11:58
  • 阅读 ( 1640 )
  • 分类:Linux

0 条评论

请先 登录 后评论

官方社群

GO教程

猜你喜欢