每天十道面试题-20200401

题目

  • 1、在 java 中守护线程和本地线程区别?
  • 2、线程与进程的区别?
  • 3、什么是多线程中的上下文切换?
  • 4、死锁与活锁的区别,死锁与饥饿的区别?
  • 5、Java 中用到的线程调度算法是什么?
  • 6、什么是线程组,为什么在 Java 中不推荐使用?
  • 7、为什么使用 Executor 框架?
  • 8、在 Java 中 Executor 和 Executors 的区别?
  • 9、如何在 Windows 和 Linux 上查找哪个线程使用的 CPU 时间最长?
  • 10、什么是原子操作?在 Java Concurrency API 中有哪些原子类(atomic classes)?

解答

题目一
  • 题干:在 java 中守护线程和本地线程区别?
  • 分析:
  • 有时候我们需要创建一个线程来执行一些辅助操作,但是又不希望这个线程阻碍JVM的关闭,所以此时我们就需要创建守护线程。
    线程分为两种:守护线程和普通线程,在JVM启动时创建的所有线程中,除了主线程之外的其他所有线程都是守护线程,当创建一个新线程时,新线程会继承创建他的线程的守护状态。因为默认状态下主线程创建的所有线程都是普通线程。
    区别:普通线程和守护线程的区别在于当线程退出时发生的操作,当一个线程退出时,JVM会检查其他正在运行的线程,如果这些线程都是守护线程,那么JVM会正常退出操作。JVM停止时所有仍然存在的守护线程都将被抛弃,意味着即不会执行finally代码块,也不会执行回卷栈,而JVM则是直接退出。(垃圾回收线程就是辅助线程。)

  • 回答:
  • 区别:普通线程都是主线程创建的,此外我们也可以通过方法Thread.setDaemon(bool on);true则把该线程设置为守护线程,反之则为用户线程。Thread.setDaemon()必须在Thread.start()之前调用,否则运行时会抛出异常。普通线程和守护线程的区别在于当线程退出时发生的操作,当一个线程退出时,JVM会检查其他正在运行的线程,如果这些线程都是守护线程,那么JVM会正常退出操作。JVM停止时所有仍然存在的守护线程都将被抛弃。

题目二
  • 题干:线程与进程的区别?
  • 分析:
  • 进程是操作系统分配资源的最小单元,线程是操作系统调度的最小单元。
    一个程序至少有一个进程,一个进程至少有一个线程。

  • 回答:
  • L进程是操作系统分配资源的最小单元,线程是操作系统调度的最小单元。
    一个程序至少有一个进程,一个进程至少有一个线程。

题目三
  • 题干:什么是多线程中的上下文切换?
  • 分析:
  • 多线程会共同使用一组计算机上的CPU,而线程数大于给程序分配的CPU数量时,为了让各个线程都有执行的机会,就需要轮转使用CPU。不同的线程切换使用CPU发生的切换数据等就是上下文切换。

  • 回答:
  • 多线程会共同使用一组计算机上的CPU,而线程数大于给程序分配的CPU数量时,为了让各个线程都有执行的机会,就需要轮转使用CPU。不同的线程切换使用CPU发生的切换数据等就是上下文切换。

题目四
  • 题干:死锁与活锁的区别,死锁与饥饿的区别?
  • 分析:
  • 死锁:是指两个或两个以上的进程(或线程)在执行过程中,因争夺资源而造成的一种互相等待的现象,若无外力作用,它们都将无法推进下去。
    产生死锁的必要条件:
    互斥条件:所谓互斥就是进程在某一时间内独占资源。
    请求与保持条件:一个进程因请求资源而阻塞时,对已获得的资源保持不放。
    不剥夺条件:进程已获得资源,在末使用完之前,不能强行剥夺。
    循环等待条件:若干进程之间形成一种头尾相接的循环等待资源关系。
    活锁:任务或者执行者没有被阻塞,由于某些条件没有满足,导致一直重复尝试,失败,尝试,失败。
    活锁和死锁的区别在于,处于活锁的实体是在不断的改变状态,所谓的“活”, 而处于死锁的实体表现为等待;活锁有可能自行解开,死锁则不能。
    饥饿:一个或者多个线程因为种种原因无法获得所需要的资源,导致一直无法执行的状态。
    Java中导致饥饿的原因:
    高优先级线程吞噬所有的低优先级线程的CPU时间。
    线程被永久堵塞在一个等待进入同步块的状态,因为其他线程总是能在它之前持续地对该同步块进行访问。
    线程在等待一个本身也处于永久等待完成的对象(比如调用这个对象的wait方法),因为其他线程总是被持续地获得唤醒。。

  • 回答:
  • 见分析。

题目五
  • 题干:Java 中用到的线程调度算法是什么?

  • 分析:

  • 采用时间片轮转的方式。可以设置线程的优先级,会映射到下层的系统上面的优先级上,如非特别需要,尽量不要用,防止线程饥饿。

  • 回答:

  • 采用时间片轮转的方式。可以设置线程的优先级,会映射到下层的系统上面的优先级上,如非特别需要,尽量不要用,防止线程饥饿。

题目六
  • 题干:什么是线程组,为什么在 Java 中不推荐使用?
  • 分析:
  • ThreadGroup类,可以把线程归属到某一个线程组中,线程组中可以有线程对象,也可以有线程组,组中还可以有线程,这样的组织结构有点类似于树的形式。
    线程组ThreadGroup对象中的stop,resume,suspend会导致安全问题,主要是死锁问题,已经被官方废弃,多以价值已经大不如以前。
    线程组ThreadGroup不是线程安全的,在使用过程中不能及时获取安全的信息。

  • 回答:
  • ThreadGroup类,可以把线程归属到某一个线程组中,线程组中可以有线程对象,也可以有线程组,组中还可以有线程,这样的组织结构有点类似于树的形式。建议使用线程池。

题目七
  • 题干:为什么使用 Executor 框架?
  • 分析:
  • · 每次执行任务创建线程 new Thread()比较消耗性能,创建一个线程是比较耗时、耗资源的。
    · 调用 new Thread()创建的线程缺乏管理,被称为野线程,而且可以无限制的创建,线程之间的相互竞争会导致过多占用系统资源而导致系统瘫痪,还有线程之间的频繁交替也会消耗很多系统资源。
    · 使用new Thread() 启动的线程不利于扩展,比如定时执行、定期执行、定时定期执行、线程中断等都不便实现。

  • 回答:
  • · 每次执行任务创建线程 new Thread()比较消耗性能,创建一个线程是比较耗时、耗资源的。
    · 调用 new Thread()创建的线程缺乏管理,被称为野线程,而且可以无限制的创建,线程之间的相互竞争会导致过多占用系统资源而导致系统瘫痪,还有线程之间的频繁交替也会消耗很多系统资源。
    · 使用new Thread() 启动的线程不利于扩展,比如定时执行、定期执行、定时定期执行、线程中断等都不便实现。

题目八
  • 题干:在 Java 中 Executor 和 Executors 的区别?
  • 分析:
  • Executors 工具类的不同方法按照我们的需求创建了不同的线程池,来满足业务的需求。
    Executor 接口对象能执行我们的线程任务。
    ExecutorService接口继承了Executor接口并进行了扩展,提供了更多的方法我们能获得任务执行的状态并且可以获取任务的返回值。
    使用ThreadPoolExecutor 可以创建自定义线程池。
    Future 表示异步计算的结果,他提供了检查计算是否完成的方法,以等待计算的完成,并可以使用get()方法获取计算的结果。
    Executors 中方法返回的是ExecutorService 而AbstractExecutorService implements ExecutorService
    ThreadPoolExecutor extends AbstractExecutorService
    一般建议直接使用ThreadPoolExecutor 的有参构造方法来创建线程池。

  • 回答:
  • 见分析

题目九
  • 题干:如何在 Windows 和 Linux 上查找哪个线程使用的 CPU 时间最长?
  • 分析:
  • 1、 终端执行top命令,找出cpu耗用厉害的进程pid, 终端执行top命令,然后按下shift+p 查找出cpu利用最厉害的pid号
    2、 根据上面第一步拿到的pid号,top -H -p pid 。然后按下shift+p,查找出cpu利用率最厉害的线程号
    3、将获取到的线程号转换成16进制,转换一下
    4、使用jstack工具将进程信息打印输出
    5、 编辑/tmp/t.dat文件,查找线程号对应的信息

  • 回答:
  • 见分析

题目十
  • 题干:什么是原子操作?在 Java Concurrency API 中有哪些原子类(atomic classes)?
  • 分析:
  • 原子操作(atomic operation)意为”不可被中断的一个或一系列操作” 。
    处理器使用基于对缓存加锁或总线加锁的方式来实现多处理器之间的原子操作。
    在Java中可以通过锁和循环CAS的方式来实现原子操作。 CAS操作——Compare & Set,或是 Compare & Swap,现在几乎所有的CPU指令都支持CAS的原子操作。
    原子操作是指一个不受其他操作影响的操作任务单元。原子操作是在多线程环境下避免数据不一致必须的手段。
    int++并不是一个原子操作,所以当一个线程读取它的值并加1时,另外一个线程有可能会读到之前的值,这就会引发错误。
    为了解决这个问题,必须保证增加操作是原子的,在JDK1.5之前我们可以使用同步技术来做到这一点。到JDK1.5,java.util.concurrent.atomic包提供了int和long类型的原子包装类,它们可以自动的保证对于他们的操作是原子的并且不需要使用同步。
    java.util.concurrent这个包里面提供了一组原子类。其基本的特性就是在多线程环境下,当有多个线程同时执行这些类的实例包含的方法时,具有排他性,即当某个线程进入方法,执行其中的指令时,不会被其他线程打断,而别的线程就像自旋锁一样,一直等到该方法执行完成,才由JVM从等待队列中选择一个另一个线程进入,这只是一种逻辑上的理解。
    原子类:AtomicBoolean,AtomicInteger,AtomicLong,AtomicReference
    原子数组:AtomicIntegerArray,AtomicLongArray,AtomicReferenceArray
    原子属性更新器:AtomicLongFieldUpdater,AtomicIntegerFieldUpdater,AtomicReferenceFieldUpdater
    解决ABA问题的原子类:AtomicMarkableReference(通过引入一个boolean来反映中间有没有变过),AtomicStampedReference(通过引入一个int来累加来反映中间有没有变过)

  • 回答:
  • 见分析。

发布了122 篇原创文章 · 获赞 32 · 访问量 9万+

猜你喜欢

转载自blog.csdn.net/YangzaiLeHeHe/article/details/105242286