乐趣区

关于java:JAVA宝典面试题多线程篇含答案

上面是 Java 线程相干的高频面试题(含答案),你能够用它来好好筹备面试。

[TOC]

1. 并行和并发有什么区别?

  • 并发:是指多个线程工作在同一个 CPU 上疾速地轮换执行,因为切换的速度十分快,给人的感觉就是这些线程工作是在同时进行的,但其实并发只是一种逻辑上的同时进行;
  • 并行:是指多个线程工作在不同 CPU 上同时进行,是真正意义上的同时执行。

2. 过程和线程的区别与分割?

区别

  • 并发性:不仅过程之间能够并发执行,同一个过程的多个线程之间也可并发执行。
  • 领有资源:过程是领有资源的一个独立单位,线程不领有系统资源,但能够拜访隶属于过程的资源。
  • 零碎开销:多过程的程序要比多线程的程序强壮,但在过程切换时,消耗资源较大,效率要差一些。

线程和过程在应用上各有优缺点:线程执行开销小,但不利于资源的治理和爱护;而过程正相反。同时,线程适宜于在 SMP 机器上运行,而过程则能够跨机器迁徙。

分割

  • 一个线程只能属于一个过程,而一个过程能够有多个线程,但至多有一个线程;
  • 资源分配给过程,同一过程的所有线程共享该过程的所有资源;
  • 处理机分给线程,即真正在处理机上运行的是线程;
  • 线程在执行过程中,须要合作同步。不同过程的线程间要利用音讯通信的方法实现同步。

3. 守护线程是什么?

  • 护线程是程序运行的时候在后盾提供一种通用服务的线程。所有用户线程进行,过程会停掉所有守护线程,退出程序。
  • 守护线程领有主动完结本人生命周期的个性,而非守护线程不具备这个特点

利用场景:

JVM 中的垃圾回收线程就是典型的守护线程,当 JVM 要退出时,垃圾回收线程也会完结本人的生命周期.

4. 创立线程有哪几种形式?

1. 继承 Thread 类

2. 实现 Runnable 接口

3. 实现 Callable 接口

4. 通过线程池创立线程(ThreadPollExecutor,ExecutorService..)

5. 说一下 runnable 和 callable 有什么区别?

  • Runnable 和 Callable 都是接口,别离提供 run 办法和 call 办法
  • Runnable 的 run 办法无返回值,Callable 的 call 办法提供返回值来示意工作运行后果
  • Runnable 无奈通过 throws 抛出异样,所有 CheckedException 必须在 run 办法外部解决。Callable 可间接抛出 Exception 异样.
  • Runnable 能够作为 Thread 结构器的参数,通过开启新的线程来执行,也能够通过线程池来执行。而 Callable 通过提交给线程池执行

6. 线程有哪些状态?

这里要留神审题, 是 零碎线程状态 还是Java 中线程的状态?

Java 中线程的状态

java.lang.Thread 类

public enum State {

    NEW,


    RUNNABLE,


    BLOCKED,

 
    WAITING,


    TIMED_WAITING,


    TERMINATED;
}

操作系统中线程的状态

  • 初始状态(NEW)

    对应 Java 中的 NEW

  • 可运行状态(READY)

    对应 Java 中的 RUNNBALE 状态

  • 运行状态(RUNNING)

    对应 Java 中的 RUNNBALE 状态

  • 期待状态(WAITING)

    该状态在 Java 中被划分为了 BLOCKEDWAITINGTIMED_WAITING 三种状态

    当线程调用阻塞式 API 时,过程 (线程) 进入期待状态,这里指的是操作系统层面的。从 JVM 层面来说,Java 线程依然处于 RUNNABLE 状态

    JVM 并不关怀操作系统线程的理论状态,从 JVM 看来,期待 CPU 使用权(操作系统状态为可运行态)与期待 I/O(操作系统处于期待状态)没有区别,都是在期待某种资源,所以都纳入 RUNNABLE 状态

  • 终止状态(DEAD)

    对应 TERMINATED

7.sleep() 和 wait() 有什么区别?

sleep()和 wait()都是线程暂停执行的办法。

  • sleep 办法属于 Thread 类中的静态方法,wait 属于 Object 的成员办法。
  • sleep()不波及线程通信,调用时会暂停此线程指定的工夫,但监控仍然放弃,不会开释对象锁 ,到工夫 主动复原
  • wait() 用于线程间的通信,调用时会 放弃对象锁 ,进入 期待队列,待调用 notify()/notifyAll()唤醒指定的线程或者所有线程,才进入对象锁定池筹备从新取得对象锁进入运行状态。
  • wait,notify 和 notifyAll 只能在 同步控制办法 或者 同步控制块 外面应用,而 sleep 能够在任何中央应用(应用范畴)
  • sleep()办法必须捕捉异样 InterruptedException,而 wait()\notify()以及 notifyAll()不须要捕捉异样。

8.notify()和 notifyAll()有什么区别?

  • notify() 办法随机唤醒对象的 期待池 中的一个线程,进入 锁池
  • notifyAll() 唤醒对象的期待池中的所有线程,进入 锁池
  • 期待池:假如线程 A 调用了某个对象的 wait()办法,线程 A 就会开释该对象的锁,并进入该对象的期待池,期待池中的线程不会去竞争该对象的锁。
  • 锁池:只有获取了对象的锁,线程能力执行对象的 synchronized 代码,对象的锁每次只有一个线程能够取得,其余线程只能在锁池中期待

9. 线程的 run() 和 start() 有什么区别?

  • 调用 start() 办法是用来启动线程的,轮到该线程执行时,会主动调用 run();
  • 调用 run() 办法,无奈达到启动多线程的目标,相当于主线程线性执行 Thread 对象的 run() 办法。
  • 一个线程对线的 start() 办法只能调用一次,屡次调用会抛出 java.lang.IllegalThreadStateException 异样;而 run() 办法没有限度。

10. 创立线程池有哪几种形式?

  • 通过 Executors 工厂办法创立 (阿里巴巴开发规约中不倡议应用此种形式创立线程池)
  • 通过 new ThreadPoolExecutor(int corePoolSize, int maximumPoolSize, long keepAliveTime, TimeUnit unit, BlockingQueue<Runnable> workQueue) 自定义创立(举荐)

11.Java 线程池中 submit() 和 execute()办法的区别

两个办法都能够向线程池提交工作

  • execute()办法的返回类型是 void,它定义在 Executor 接口中。
  • submit()办法能够返回持有计算结果的 Future 对象,它定义在 ExecutorService 接口中,它扩大了 Executor 接口,其它线程池类像 ThreadPoolExecutor 和 ScheduledThreadPoolExecutor 都有这些办法。

12. 在 java 程序中怎么保障多线程的运行平安?

程序中保障多线程运行平安的形式:

1. 应用安全类,比方 Java. util. concurrent 下的类。

2. 应用主动锁 synchronized。

3. 应用手动锁 Lock 例如 Reentrantlock。

4. 保障一个或者多个操作在 CPU 执行的过程中不被中断。(原子性)

5. 保障一个线程对共享变量的批改,另外一个线程可能立即看到。(可见性)

6. 保障程序执行的程序依照代码的先后顺序执行。(有序性)

留神答复中不能短少这 3 种个性

线程的安全性问题体现在:

  • 原子性:一个或者多个操作在 CPU 执行的过程中不被中断的个性
  • 可见性:一个线程对共享变量的批改,另外一个线程可能立即看到
  • 有序性:程序执行的程序依照代码的先后顺序执行

13. 多线程锁的降级原理是什么?

锁的级别从低到高:

无锁 -> 偏差锁 -> 轻量级锁 -> 重量级锁

锁分级别起因:

没有优化以前,synchronized 是重量级锁(乐观锁),应用 wait 和 notify、notifyAll 来切换线程状态十分耗费系统资源;线程的挂起和唤醒距离很短暂,这样很浪费资源,影响性能。所以 JVM 对 synchronized 关键字进行了优化,把锁分为 无锁、偏差锁、轻量级锁、重量级锁 状态。

在学习并发编程常识 synchronized 时,咱们总是难以了解其实现原理,因为偏差锁、轻量级锁、重量级锁都波及到对象头,所以理解 java 对象头是咱们深刻理解 synchronized 的前提条件. 这篇文章蕴含了对象头的解析以及锁收缩过程的解析:

JAVA 对象布局之对象头(Object Header)

14. 什么是死锁?什么是活锁? 什么是线程饥饿?

JAVA 并发之加锁导致的活跃性问题分析

15.ThreadLocal 是什么?有哪些应用场景?

1.ThreadLocal 介绍

2.ThreadLocal 利用

3.ThreadLocal 源码解析

3.1 解决 Hash 抵触

4.ThreadLocal 个性

5..ThreadLocal 内存泄露问题

java 并发之无同步计划 -ThreadLocal

16. 说一下 synchronized 底层实现原理?

前置常识, 须要理解 对象头.–>JAVA 对象布局之对象头(Object Header)

  • 同步代码块是通过 monitorenter 和 monitorexit 指令获取线程的执行权

    monitorenter,如果以后 monitor 的进入数为 0 时,线程就会进入 monitor,并且把进入数 +1,那么该线程就是 monitor 的拥有者(owner)。

    如果该线程曾经是 monitor 的拥有者,又从新进入,就会把进入数再次 +1。也就是可重入的。

    执行 monitorexit 的线程必须是 monitor 的拥有者,指令执行后,monitor 的进入数减 1,如果减 1 后进入数为 0,则该线程会退出 monitor。其余被阻塞的线程就能够尝试去获取 monitor 的所有权。

    monitorexit 指令呈现了两次,第 1 次为同步失常退出开释锁;第 2 次为产生异步退出开释锁;

  • 同步办法通过加 ACC_SYNCHRONIZED 标识实现线程的执行权的管制

    标记位 ACC_SYNCHRONIZED,作用就是一旦执行到这个办法时,就会先判断是否有标记位,如果有这个标记位,就会先尝试获取 monitor,获取胜利能力执行办法,办法执行实现后再开释 monitor。在办法执行期间,其余线程都无奈获取同一个 monitor。归根结底还是对 monitor 对象的抢夺,只是同步办法是一种隐式的形式来实现。

总的来说,synchronized 的底层原理是通过 monitor 对象来实现的

17.synchronized 和 volatile 的区别是什么?

作用:

  • synchronized 示意只有一个线程能够获取作用对象的锁,执行代码,阻塞其余线程。
  • volatile 示意变量在 CPU 的寄存器中是不确定的,必须从主存中读取。保障多线程环境下变量的可见性;禁止指令重排序。

区别:

  • synchronized 能够作用于变量、办法、对象;volatile 只能作用于变量。
  • synchronized 能够保障线程间的有序性、原子性和可见性;volatile 只保障了可见性和有序性,无奈保障原子性。
  • synchronized 线程阻塞,volatile 线程不阻塞。

18.synchronized 和 Lock 的区别是什么?

在多线程状况下,锁是线程管制的重要途径。Java 为此也提供了 2 种锁机制,synchronized 和 lock。

咱们这里不探讨具体的实现原理和细节, 只探讨它们的区别

如果有小伙伴有趣味更深刻理解它们, 请关注公众号:JAVA 宝典

区别

  • lock 是一个接口,而 synchronized 是 java 的一个关键字。
  • synchronized 在产生异样时会主动开释占有的锁,因而不会呈现死锁;而 lock 产生异样时,不会被动开释占有的锁,必须手动来开释锁,可能引起死锁的产生(也称隐式锁和显式锁)
  • lock 期待锁过程中能够用 interrupt 来中断期待,而 synchronized 只能期待锁的开释,不能响应中断;
  • Lock 能够通过 trylock 来晓得有没有获取锁,而 synchronized 不能;
  • Lock 能够进步多个线程进行读操作的效率。(能够通过 readwritelock 实现读写拆散)
  • 在性能上来说,如果竞争资源不强烈,两者的性能是差不多的,而当竞争资源十分强烈时(即有大量线程同时竞争),此时 Lock 的性能要优于 synchronized。在应用时要依据适当状况抉择。
  • synchronized 是 JVM 层面实现的;Lock 是 JDK 代码层面实现
  • synchronized 应用 Object 对象自身的 wait、notify、notifyAll 调度机制,而 Lock 能够应用 Condition 进行线程之间的调度
  • 继上一条,synchronized 只有一个阻塞队列, 而 Lock 应用 Condition 能够有多个阻塞队列

synchronized 和 lock 的用法区别

  • synchronized:在须要同步的对象中退出此管制,synchronized 能够加在办法上,也能够加在特定代码块中,括号中示意须要锁的对象。
  • lock:个别应用 ReentrantLock 类做为锁。在加锁和解锁处须要通过 lock()和 unlock()显示指出。所以个别会在 finally 块中写 unlock()以防死锁。

19. 说一下 你对 atomic 的了解 ?

在 JDK5.0 之前,想要实现无锁无期待的算法是不可能的,除非用本地库,自从有了 Atomic 变量类后,这成为可能。

在 java.util.concurrent.atomic 包下有这些类:

  • 标量类:AtomicBoolean,AtomicInteger,AtomicLong,AtomicReference
  • 数组类:AtomicIntegerArray,AtomicLongArray,AtomicReferenceArray
  • 更新器类:AtomicLongFieldUpdater,AtomicIntegerFieldUpdater,AtomicReferenceFieldUpdater
  • 复合变量类:AtomicMarkableReference,AtomicStampedReference

拿 AtomicInteger 来举例, 其外部实现不是简略的应用 synchronized,而是一个更为高效的形式 CAS (compare and swap) + volatile 和 native 办法,从而防止了 synchronized 的高开销,执行效率大为晋升。

/** 
 * Atomically increments by one the current value. 
 * 
 * @return the previous value 
 */  
public final int getAndIncrement() {return unsafe.getAndAddInt(this, valueOffset, 1);  
} 

这里间接调用一个叫 Unsafe 的类去解决, 这个类是用于执行低级别、不平安操作的办法汇合。只管这个类和所有的办法都是公开的(public),然而这个类的应用依然受限,你无奈在本人的 java 程序中间接应用该类,因为只有授信的代码能力取得该类的实例。所以咱们平时的代码是无奈应用这个类的,因为其设计的操作过于偏底层,如若操作不慎可能会带来很大的劫难,所以间接禁止一般代码的拜访,当然 JDK 应用是没有问题的。

对于 CAS 在我的另一篇文章: 什么是 CAS,ABA 问题怎么解决?

20. 说一下 你对 Semaphore 的了解 ?

  1. Semaphore 就是一个信号量,它的作用是限度某段代码块的并发数
  2. Semaphore 有一个构造函数,能够 传入一个 int 型整数 n,示意某段代码最多只有 n 个线程能够拜访
  3. 如果超出了 n,那么请期待,等到某个线程执行结束这段代码块,下一个线程再进入
  4. 由此能够看出如果 Semaphore 构造函数中传入的 int 型整数 n =1,相当于变成了一个 synchronized 了。

    Semaphore 类位于 java.util.concurrent 包下,它提供了 2 个结构器:

// 参数 permits 示意许可数目,即同时能够容许多少线程进行拜访  
public Semaphore(int permits) {sync = new NonfairSync(permits);  
}  
// 这个多了一个参数 fair 示意是否是偏心的,即等待时间越久的越先获取许可  
public Semaphore(int permits, boolean fair) {sync = (fair)? new FairSync(permits) : new NonfairSync(permits);  
}  
  • Semaphore 类中比拟重要的几个办法,首先是 acquire()、release()办法:
  • acquire()用来获取一个许可,若无许可可能取得,则会始终期待,直到取得许可。
  • release()用来开释许可。留神,在开释许可之前,必须先获取得许可。
// 尝试获取一个许可,若获取胜利,则立刻返回 true,若获取失败,则立刻返回 false  
public boolean tryAcquire() {};  
// 尝试获取一个许可,若在指定的工夫内获取胜利,则立刻返回 true,否则则立刻返回 false  
public boolean tryAcquire(long timeout, TimeUnit unit) throws InterruptedException { };   
// 尝试获取 permits 个许可,若获取胜利,则立刻返回 true,若获取失败,则立刻返回 false  
public boolean tryAcquire(int permits) { };   
// 尝试获取 permits 个许可,若在指定的工夫内获取胜利,则立刻返回 true  
public boolean tryAcquire(int permits, long timeout, TimeUnit unit) throws InterruptedException { };  
// 失去以后可用的许可数目  
public int availablePermits(); 

关注公众号:java 宝典

退出移动版