加入收藏 | 设为首页 | 会员中心 | 我要投稿 百客网 - 百科网 (https://www.baikewang.cn/)- 科技、建站、经验、云计算、5G、大数据,站长网!
当前位置: 首页 > 服务器 > 搭建环境 > Unix > 正文

抽空整理的45道经典多线程面试题

发布时间:2022-11-01 10:43:30 所属栏目:Unix 来源:
导读:  更多面经在【小熊学Java】

  1、进程与线程的区别?

  进程:是实现某个独立功能的程序,它是操作系统(如windows 系统)进行资源分配和调度的一个独立单位,也是可以独立运行的一段程序。

  
  更多面经在【小熊学Java】
 
  1、进程与线程的区别?
 
  进程:是实现某个独立功能的程序,它是操作系统(如windows 系统)进行资源分配和调度的一个独立单位,也是可以独立运行的一段程序。
 
  线程:是操作系统能够进行运算调度的最小单位,它被包含在进程之中,是进程中的实际运作单位,可以使?用多线程对进?行行运算提速。比如,如果?个线程完成?个任务要100毫秒,那么用十个线程完成改任务只需10毫秒
 
  线程与进程之间的区别:
 
  进程间相互独立,但同一进程内的各个线程会共享该进程拥有的资源,而进程则是用独占的方式来占有资源unix线程切换,进程间不能共享资源。 线程上下文切换(从一个线程切换到另一个线程)的速度要比进程上下文切换速度快的多。 每一个线程都有一个运行的入口、顺序执行序列和出口,但线程不能独立运行,必须依靠进程的调度和控制线程的执行。 一般操作系统级别的会注重“进程”的角度和管理,而应用项目会偏重于“线程”。在编程中偏重于多线程,而不是多进程。 2、什么是线程安全与线程不安全?
 
  线程安全:多线程访问时,采用了加锁机制,当一个线程访问该类的某一个数据时,会对该数据进行保护,其他线程无法访问,直到该线程读取完,其他线程才可使用,不会出现数据不一致或数据污染。
 
  线程不安全:不提供数据访问保护,有可能出现先后更改数据造成所得到的数据是脏数据
 
  线程安全都是由全局变量和静态变量引起的。
 
  若每个线程中对全局变量、静态变量只有读操作,?无写操作,?般来说,这个全局变量是线程安全的;
 
  若有多个线程同时执行写操作,一般都需要考虑线程同步,否则的话就可能影响线程安全。
 
  3、什么是多线程?多线程的优点与缺点?
 
  多线程:多线程是指程序中包含多个执行流,即在一个程序中可以同时运行多个不同的线程来执行不同的任务。
 
  优点:可以提高 CPU 的利用率。在多线程程序中,一个线程必须等待的时候,CPU 可以运行其它的线程而不是等待,这样就大大提高了程序的效率。也就是说允许单个程序创建多个并行执行的线程来完成各自的任务。
 
  缺点:
 
  4、什么是上下文切换? 5、守护线程与用户线程有什么区别? 6、什么是线程死锁?
 
  死锁:指两个或两个以上的线程(进程),在执行过程中,由于竞争资源或由于彼此通信而造成的一种阻塞现象,若无外力作用,它们都将无法推进下去。此时称系统处于死锁状态或系统产生了死锁,这些永远在互相等待的进程(线程)称为死锁进程(线程)。
 
  例如:线程A与线程B都拥有各自的资源,但试图去获取对方的资源,这两个线程就会互相等待而进入死锁状态。
 
  图片说明
 
  7、形成死锁的四个必要条件是什么? 互斥条件:在一段时间内,某个资源只由一个进程占用。如果此时其他进程请求资源,就只能等待,直到占有资源的进程释放。 占有且等待条件:进程至少保持一个资源,但又提出了新的资源的请求,而请求的资源被其他进程占有,此时请求进程阻塞,对自己已经获取的资源保持不放。 不可抢占条件:别人已经占有了某项资源,你不能因为自己也需要该资源,就去把别人的资源抢过来。 循环等待条件:若干进程之间形成一种头尾相接的循环等待资源关系。(比如一个进程集合,A在等B,B在等C,C在等A) 8、如何避免死锁? 避免一个线程同时获得多个锁 避免一个线程在锁内同时占有多个资源,尽量保证每个锁只占有一个资源 尝试使用定时锁,使用lock.tryLock(timeout)来替代使用内部锁机制 9、创建线程的四种方式? 继承Thread类 实现 Runnable 接口 使用 Callable 和 Future 创建线程 使用线程池创建线程 1、继承Thread类
 
  步骤:
 
  定义Thread类的子类,并重写该类的run()方法,该方法的方法体就是线程需要完成的任务,run()方法也称为线程执行体; 创建Thread子类的实例,也就是创建了线程对象; 启动线程,调用线程的start()方法。
 
  public class CreateThread extends Thread{
      @Override
      public void run() {
          //获取线程名
          System.out.println(Thread.currentThread().getName());
      }
      public static void main(String[] args) {
          CreateThread createThread = new CreateThread();
          //线程启动
          createThread.start();
      }
  }
  2、实现Runnable接口
 
  步骤:
 
  定义Runnable接口的实现类,一样要重写run()方法,这个run()方法和Thread中的run()方法一样是线程的执行体; 创建Runnable实现类的实例,并用这个实例作为Thread的target来创建Thread对象,这个Thread对象才是真正的线程对象; 调用线程对象的start()方法来启动线程。
 
  public class RunnableCreateThread implements Runnable{
      @Override
      public void run() {
          System.out.println("实现Runnable接口创建线程");
      }
      public static void main(String[] args) {
          new Thread(new RunnableCreateThread()).start();
      }
  }
  3、使用Callable和Future创建线程
 
  与 Runnable 接口不一样,Callable 接口提供了一个 call() 方法作为线程执行体,call() 方法比 run() 方法功能要强大,比如:call() 方法可以有返回值、call() 方法可以声明抛出异常。
 
  Java5 提供了 Future 接口来代表 Callable 接口里 call() 方法的返回值,并且为 Future 接口提供了一个实现类 FutureTask,这个实现类既实现了 Future 接口,还实现了 Runnable 接口,因此可以作为 Thread 类的 target。在 Future 接口里定义了几个公共方法来控制它关联的 Callable 任务。
 
  步骤:
 
  创建实现 Callable 接口的类 MyCallable; 以 myCallable 为参数创建 FutureTask 对象; 将 FutureTask 作为参数创建 Thread 对象; 调用线程对象的 start() 方法
 
  public class MyCallable implements Callable {
      @Override
      public Object call() throws Exception {
          System.out.println(Thread.currentThread().getName());
          return "huahua";
 
      }
      public static void main(String[] args) {
          //创建 FutureTask 对象
          FutureTask futureTask = new FutureTask<>(new MyCallable());
          //创建线程并启动
          Thread thread = new Thread(futureTask);
          thread.start();
          try {
              Thread.sleep(1000);
              //获取返回值
              System.out.println("返回的结果是:" + futureTask.get());
          } catch (Exception e) {
              e.printStackTrace();
          }
      }
  }
  4、基于线程池创建线程
 
  Executors 提供了一系列工厂方法用于创先线程池,返回的线程池都实现了ExecutorService 接口。
 
  主要有四种:
 
  newFixedThreadPool:创建一个定长线程池,可控制线程最大并发数,超出的线程会在队列中等待。 newCachedThreadPool:创建一个可缓存线程池,如果线程池长度超过处理需要,可灵活回收空闲线程,若无可回收,则新建线程。 newSingleThreadExecutor:创建一个单线程化的线程池,它只会用唯一的工作线程来执行任务,保证所有任务按照指定顺序(FIFO, LIFO, 优先级)执行。 newScheduledThreadPool:创建一个定长线程池,支持定时及周期性任务执行。
 
  public class CreateThreadByExecutors implements Runnable {
      public static void main(String[] args) {
          ExecutorService service = Executors.newSingleThreadExecutor();
          CreateThreadByExecutors thread = new CreateThreadByExecutors();
          for (int i = 0; i < 10; i++) {
              service.execute(thread);
              System.out.println("=======任务开始=========");
              service.shutdown();
          }
      }
      @Override
      public void run() {
          System.out.println("hello Thread");
      }
  }
  10、实现Runnable接口与实现Callable接口有什么区别?
 
  相同点:
 
  主要区别:
 
  注:Callalbe接口支持返回执行结果,需要调用FutureTask.get()得到,此方***阻塞主进程的继续往下执行,如果不调用不会阻塞。
 
  11、线程的 run()和 start()有什么区别? 12、为什么我们调用 start() 方法时会执行 run() 方法,为什么我们不能直接调用run() 方法?
 
  总结:调用 start 方法方可启动线程并使线程进入就绪状态,而 run 方法只是 thread 的一个普通方法调用,还是在主线程里执行。
 
  13、什么是 Callable 和 Future? 14、什么是 FutureTask?
 
  FutureTask 表示一个异步运算的任务。FutureTask 里面可以传入一个 Callable 的具体实现类,可以对这个异步运算的任务的结果进行等待获取、判断是否已经完成、取消任务等操作。只有当运算完成的时候结果才能取回,如果运算尚未完成 get 方法将会阻塞。一个 FutureTask 对象可以对调用了 Callable 和 Runnable 的对象进行包装,由于 FutureTask 也是Runnable 接口的实现类,所以 FutureTask 也可以放入线程池中。
 
  15、线程有哪5个状态?
 
  线程5个状态:创建、就绪、运行、阻塞和死亡。
 
  创建状态(new):在生成线程对象,并没有调用该对象的start方法,这是线程处于创建状态。
 
  就绪状态(Runnable):当调用了线程对象的start方法之后,该线程就进入了就绪状态,但是此时线程调度程序还没有把该线程设置为当前线程,此时处于就绪状态,等待线程被调度选中,获取CPU的使用权。在线程运行之后,从等待或者睡眠中回来之后,也会处于就绪状态。
 
  运行状态(running):可运行状态(runnable)的线程获得了cpu时间片(timeslice),执行程序代码。注:就绪状态是进入到运行状态的唯一入口,也就是说,线程要想进入运行状态执行,首先必须处于就绪状态中;
 
  阻塞状态(block):处于运行状态中的线程由于某种原因,暂时放弃对 CPU的使用权,停止执行,此时进入阻塞状态,直到其进入到就绪状态,才 有机会再次被 CPU 调用以进入到运行状态。
 
  阻塞的情况:
 
  死亡状态(dead):线程run()、main()方法执行结束,或者因异常退出了run()方法,或调用stop()方法,则该线程结束生命周期。死亡的线程不可再次复生。
 
  图片说明
 
  图片说明
 
  16、Java 中用到的线程调度算法是什么?
 
  计算机通常只有一个 CPU,在任意时刻只能执行一条机器指令,每个线程只有获得CPU 的使用权才能执行指令。所谓多线程的并发运行,其实是指从宏观上看,各个线程轮流获得 CPU 的使用权,分别执行各自的任务。在运行池中,会有多个处于就绪状态的线程在等待 CPU,JAVA 虚拟机的一项任务就是负责线程的调度,线程调度是指按照特定机制为多个线程分配 CPU 的使用权。(Java是由JVM中的线程计数器来实现线程调度)
 
  unix线程切换_四核四线程和四核八线程_4核4线程和4核8线程
 
  有两种调度模型:分时调度模型和抢占式调度模型。
 
  17、线程的调度策略?
 
  线程调度器选择优先级最高的线程运行,但是,如果发生以下情况,就会终止线程的运行:
 
  线程体中调用了 yield 方法让出了对 cpu 的占用权利 线程体中调用了 sleep 方法使线程进入睡眠状态 线程由于 IO 操作受到阻塞 另外一个更高优先级线程出现 在支持时间片的系统中,该线程的时间片用完 18、什么是线程调度器(Thread Scheduler)和时间分片(Time Slicing )?
 
  线程调度器是一个操作系统服务,它负责为 Runnable 状态的线程分配 CPU 时间。一旦我们创建一个线程并启动它,它的执行便依赖于线程调度器的实现。
 
  时间分片是指将可用的 CPU 时间分配给可用的 Runnable 线程的过程。分配 CPU 时间可以基于线程优先级或者线程等待的时间。
 
  线程调度并不受到 Java 虚拟机控制,所以由应用程序来控制它是更好的选择(也就是说不要让你的程序依赖于线程的优先级)。
 
  19、线程同步以及线程调度相关的方法有哪些?
 
  wait():使一个线程处于等待或阻塞状态,并且释放所持有对象的锁
 
  sleep():使一个正在运行的线程处于睡眠状态,是一个静态方法,调用此方法要处理InterruptedException异常
 
  notify():唤醒一个处于等待的线程,在调用此方法的时候,并不能确切的唤醒某一个等待状态的线程,而是由JVM确定唤醒哪一个线程,而且跟线程的优先级有关。
 
  notifyAll():唤醒所有处于等待的线程,该方法并不是将对象的锁给所有线程,而是由他们竞争,只有获得锁的线程才会进入就绪状态。
 
  20、sleep() 和 wait() 有什么区别?
 
  两者都可以暂停线程的执行。
 
  不同sleep()wait()
 
  类的不同
 
  是 Thread线程类的静态方法
 
  是 Object类的方法
 
  是否释放锁
 
  不释放
 
  释放
 
  用途不同
 
  通常被用于暂停执行
 
  通常被用于线程间交互/通信
 
  用法不同
 
  sleep() 方法执行完成后,线程会自动苏醒。或者可以使用wait(long timeout)超时后线程会自动苏醒
 
  被调用后,线程不会自动苏醒,需要别的线程调用同一个对象上的 notify()或者 notifyAll() 方法
 
  21、你是如何调用 wait() 方法的?使用 if 块还是循环?为什么?
 
  处于等待状态的线程可能会收到错误警报和伪唤醒,如果不在循环中检查等待条件,程序就会在没有满足结束条件的情况下退出。
 
  wait() 方法应该在循环调用,因为当线程获取到 CPU 开始执行的时候,其他条件可能还没有满足,所以在处理前,循环检测条件是否满足会更好。
 
  sychronized(monitor){
      //判断条件是否满足
      while(!locked){
          //等待唤醒
          monitor.wait();
      }
      //处理其他业务逻辑
  }
  22、为什么线程通信的方法 wait(), notify()和 notifyAll()被定义在 Object 类里?
 
  因为Java所有类的都继承了Object,Java想让任何对象都可以作为锁,并且 wait(),notify()等方法用于等待对象的锁或者唤醒线程,在 Java 的线程中并没有可供任何对象使用的锁,所以任意对象调用方法一定定义在Object类中。
 
  既然是线程放弃对象锁,那也可以把wait()定义在Thread类里面啊,新定义的线程继承于Thread类,也不需要重新定义wait()方法的实现。然而,这样做有一个非常大的问题,一个线程完全可以持有很多锁,你一个线程放弃锁的时候,到底要放弃哪个锁?当然了,这种设计并不是不能实现,只是管理起来更加复杂。
 
  23、为什么 wait(), notify()和 notifyAll()必须在同步方法或者同步块中被调用?
 
  当一个线程需要调用对象的 wait()方法的时候,这个线程必须拥有该对象的锁,接着它就会释放这个对象锁并进入等待状态直到其他线程调用这个对象上的 notify()方法。
 
  同样的,当一个线程需要调用对象的 notify()方法时,它会释放这个对象的锁,以便其他在等待的线程就可以得到这个对象锁。
 
  由于所有的这些方法都需要线程持有对象的锁,这样就只能通过同步来实现,所以他们只能在同步方法或者同步块中被调用。
 
  24、Thread 类中的 yield 方法有什么作用?
 
  使当前线程从执行状态(运行状态)变为可执行态(就绪状态)。
 
  当前线程到了就绪状态,那么接下来哪个线程会从就绪状态变成执行状态呢?可能是当前线程,也可能是其他线程,看系统的分配了。
 
  25、线程的 sleep()方法和 yield()方法有什么区别?
 
  相同点:sleep()与yield()方法都是静态的
 
  不同点:
 
  不同sleep()yield()
 
  线程机会
 
  会给其他线程运行机会时不考虑线程的优先级,因此会给低优先级的线程以运行的机会
 
  只会给相同优先级或更高优先级的线程以运行的机会
 
  调用后状态
 
  转入阻塞(blocked)状态
 
  转入就绪(ready)状态.
 

(编辑:百客网 - 百科网)

【声明】本站内容均来自网络,其相关言论仅代表作者个人观点,不代表本站立场。若无意侵犯到您的权利,请及时与联系站长删除相关内容!

    推荐文章