共计 7102 个字符,预计需要花费 18 分钟才能阅读完成。
本文收录于《面试小抄》系列,Github 地址(可下载 pdf):https://github.com/cosen1024/… 国内 Gitee(可下载 pdf):https://gitee.com/cosen1024/J…
1. 线程和过程有什么区别?
线程具备许多传统过程所具备的特色,故又称为轻型过程 (Light—Weight Process) 或过程元;而把传统的过程称为重型过程(Heavy—Weight Process),它相当于只有一个线程的工作。在引入了线程的操作系统中,通常一个过程都有若干个线程,至多蕴含一个线程。
基本区别:过程是操作系统资源分配的根本单位,而线程是处理器任务调度和执行的根本单位
资源开销:每个过程都有独立的代码和数据空间(程序上下文),程序之间的切换会有较大的开销;线程能够看做轻量级的过程,同一类线程共享代码和数据空间,每个线程都有本人独立的运行栈和程序计数器(PC),线程之间切换的开销小。
蕴含关系:如果一个过程内有多个线程,则执行过程不是一条线的,而是多条线(线程)共同完成的;线程是过程的一部分,所以线程也被称为轻权过程或者轻量级过程。
内存调配:同一过程的线程共享本过程的地址空间和资源,而过程之间的地址空间和资源是互相独立的
影响关系:一个过程解体后,在保护模式下不会对其余过程产生影响,然而一个线程解体整个过程都死掉。所以多过程要比多线程强壮。
执行过程:每个独立的过程有程序运行的入口、程序执行序列和程序进口。然而线程不能独立执行,必须依存在应用程序中,由应用程序提供多个线程执行管制,两者均可并发执行
2. 创立线程的三种形式的比照?
1)采纳实现 Runnable、Callable 接口的形式创立多线程。
劣势是:
线程类只是实现了 Runnable 接口或 Callable 接口,还能够继承其余类。
在这种形式下,多个线程能够共享同一个 target 对象,所以非常适合多个雷同线程来解决同一份资源的状况,从而能够将 CPU、代码和数据离开,造成清晰的模型,较好地体现了面向对象的思维。
劣势是:
编程略微简单,如果要拜访以后线程,则必须应用 Thread.currentThread()办法。
2)应用继承 Thread 类的形式创立多线程
劣势是:
编写简略,如果须要拜访以后线程,则无需应用 Thread.currentThread()办法,间接应用 this 即可取得以后线程。
劣势是:
线程类曾经继承了 Thread 类,所以不能再继承其余父类。
3)Runnable 和 Callable 的区别
- Callable 规定(重写)的办法是 call(),Runnable 规定(重写)的办法是 run()。
- Callable 的工作执行后可返回值,而 Runnable 的工作是不能返回值的。
- Call 办法能够抛出异样,run 办法不能够。
- 运行 Callable 工作能够拿到一个 Future 对象,示意异步计算的后果。它提供了查看计算是否实现的办法,以期待计算的实现,并检索计算的后果。通过 Future 对象能够理解工作执行状况,可勾销工作的执行,还可获取执行后果。
3. 为什么要应用多线程呢?
- 从计算机底层来说:线程能够比作是轻量级的过程,是程序执行的最小单位,线程间的切换和调度的老本远远小于过程。另外,多核 CPU 时代意味着多个线程能够同时运行,这缩小了线程上下文切换的开销。
- 从当代互联网发展趋势来说:当初的零碎动不动就要求百万级甚至千万级的并发量,而 多线程并发编程正是开发高并发零碎的根底,利用好多线程机制能够大大提高零碎整体的并发能力以及性能。
从计算机底层来说:
- 单核时代:在单核时代多线程次要是为了进步 CPU 和 IO 设施的综合利用率。举个例子:当只有一个线程的时候会导致 CPU 计算时,IO 设施闲暇;进行 IO 操作时,CPU 闲暇。咱们能够简略地说这两者的利用率目前都是 50% 左右。然而当有两个线程的时候就不一样了,当一个线程执行 CPU 计算时,另外一个线程能够进行 IO 操作,这样两个的利用率就能够在现实状况下达到 100% 了。
- 多核时代:多核时代多线程次要是为了进步 CPU 利用率。举个例子:如果咱们要计算一个简单的工作,咱们只用一个线程的话,CPU 只会一个 CPU 外围被利用到,而创立多个线程就能够让多个 CPU 外围被利用到,这样就进步了 CPU 的利用率。
4. 线程的状态流转?
线程的生命周期及五种根本状态:
Java 线程具备五中根本状态
1)新建状态(New):当线程对象对创立后,即进入了新建状态,如:Thread t = new MyThread();
2)就绪状态(Runnable):当调用线程对象的 start()办法(t.start();),线程即进入就绪状态。处于就绪状态的线程,只是阐明此线程曾经做好了筹备,随时期待 CPU 调度执行,并不是说执行了 t.start()此线程立刻就会执行;
3)运行状态(Running):当 CPU 开始调度处于就绪状态的线程时,此时线程才得以真正执行,即进入到运行状态。注:就 绪状态是进入到运行状态的惟一入口,也就是说,线程要想进入运行状态执行,首先必须处于就绪状态中;
4)阻塞状态(Blocked):处于运行状态中的线程因为某种原因,临时放弃对 CPU 的使用权,进行执行,此时进入阻塞状态,直到其进入到就绪状态,才 有机会再次被 CPU 调用以进入到运行状态。依据阻塞产生的起因不同,阻塞状态又能够分为三种:
1. 期待阻塞:运行状态中的线程执行 wait()办法,使本线程进入到期待阻塞状态;
2. 同步阻塞 — 线程在获取 synchronized 同步锁失败(因为锁被其它线程所占用),它会进入同步阻塞状态;
3. 其余阻塞 — 通过调用线程的 sleep()或 join()或收回了 I / O 申请时,线程会进入到阻塞状态。当 sleep()状态超时、join()期待线程终止或者超时、或者 I / O 处理完毕时,线程从新转入就绪状态。
5)死亡状态(Dead):线程执行完了或者因异样退出了 run()办法,该线程完结生命周期。
5. 什么是线程死锁? 如何防止死锁?
死锁
- 多个线程同时被阻塞,它们中的一个或者全副都在期待某个资源被开释。因为线程被无限期地阻塞,因而程序不可能失常终止。
死锁必须具备以下四个条件:
- 互斥条件:该资源任意一个时刻只由一个线程占用。
- 申请与放弃条件:一个过程因申请资源而阻塞时,对已取得的资源放弃不放。
- 不剥夺条件: 线程已取得的资源在末应用完之前不能被其余线程强行剥夺,只有本人应用结束后才开释资源。
- 循环期待条件: 若干过程之间造成一种头尾相接的循环期待资源关系。
如何防止线程死锁?
只有毁坏产生死锁的四个条件中的其中一个就能够了
- 毁坏互斥条件
这个条件咱们没有方法毁坏,因为咱们用锁原本就是想让他们互斥的(临界资源须要互斥拜访) - 毁坏申请与放弃条件
一次性申请所有的资源。 - 毁坏不剥夺条件
占用局部资源的线程进一步申请其余资源时,如果申请不到,能够被动开释它占有的资源。 - 毁坏循环期待条件
靠按序申请资源来预防。按某一程序申请资源,开释资源则反序开释。毁坏循环期待条件。 - 锁排序法:(必须答复进去的点)
指定获取锁的程序,比方某个线程只有取得 A 锁和 B 锁,能力对某资源进行操作,在多线程条件下,如何防止死锁?
通过指定锁的获取程序,比方规定,只有取得 A 锁的线程才有资格获取 B 锁,按程序获取锁就能够防止死锁。这通常被认为是解决死锁很好的一种办法。 - 应用显式锁中的 ReentrantLock.try(long,TimeUnit)来申请锁
6. 常见的比照
Runnable vs Callable
- Callable 仅在 Java 1.5 中引入, 目标就是为了来解决 Runnable 不反对的用例。Callable 接口能够返回后果或抛出查看异样
- Runnable 接口不会返回后果或抛出查看异样,
- 如果工作不须要返回后果或抛出异样举荐应用 Runnable 接口,这样代码看起来会更加简洁
- 工具类 Executors 能够实现 Runnable 对象和 Callable 对象之间的互相转换。(Executors.callable(Runnable task)或 Executors.callable(Runnable task,Object resule))
execute() vs submit()
- execute()办法用于提交不须要返回值的工作,所以无奈判断工作是否被线程池执行胜利与否;
- submit()办法用于提交须要返回值的工作。线程池会返回一个 Future 类型的对象,通过这个 Future 对象能够判断工作是否执行胜利(能够通过 Future 的 get()办法来获取返回值,get()办法会阻塞以后线程直到工作实现,而应用 get(long timeout,TimeUnit unit)办法则会阻塞以后线程一段时间后立刻返回,这时候有可能工作没有执行完。)
shutdown()VSshutdownNow()
- shutdown(): 敞开线程池,线程池的状态变为 SHUTDOWN。线程池不再承受新工作了,然而队列里的工作得执行结束。
- shutdownNow(): 敞开线程池,线程的状态变为 STOP。线程池会终止以后正在运行的工作,并进行解决排队的工作并返回正在期待执行的 List。
shutdownNow 的原理是遍历线程池中的工作线程,而后一一调用线程的 interrupt 办法来中断线程,所以无奈响应中断的工作可能永远无奈终
isTerminated() VS isShutdown()
- isShutDown 当调用 shutdown() 办法后返回为 true。
- isTerminated 当调用 shutdown() 办法后,并且所有提交的工作实现后返回为 true
7. sleep() 办法和 wait() 办法区别和共同点?
区别
- sleep 办法:是 Thread 类的静态方法,以后线程将睡眠 n 毫秒,线程进入阻塞状态。当睡眠工夫到了,会解除阻塞,进入可运行状态,期待 CPU 的到来。睡眠不开释锁(如果有的话)。
- wait 办法:是 Object 的办法,必须与 synchronized 关键字一起应用,线程进入阻塞状态,当 notify 或者 notifyall 被调用后,会解除阻塞。然而,只有从新占用互斥锁之后才会进入可运行状态。睡眠时,会开释互斥锁。
- sleep 办法没有开释锁,而 wait 办法开释了锁。
- sleep 通常被用于暂停执行 Wait 通常被用于线程间交互 / 通信
- sleep() 办法执行实现后,线程会主动昏迷。或者能够应用 wait(long timeout)超时后线程会主动昏迷。wait() 办法被调用后,线程不会主动昏迷,须要别的线程调用同一个对象上的 notify() 或者 notifyAll() 办法
雷同
- 两者都能够暂停线程的执行。
8. 为什么咱们调用 start() 办法时会执行 run() 办法,为什么咱们不能间接调用 run() 办法
- new 一个 Thread,线程进入了新建状态; 调用 start() 会执行线程的相应筹备工作,而后主动执行 run() 办法的内容,(调用 start() 办法,会启动一个线程并使线程进入了就绪状态,当调配到工夫片后就能够开始运行了。)这是真正的多线程工作。
- 间接执行 run() 办法,会把 run 办法当成一个 main 线程下的一般办法去执行,并不会在某个线程中执行它,所以这并不是多线程工作。
调用 start 办法方可启动线程并使线程进入就绪状态,而 run 办法只是 thread 的一个一般办法调用,还是在主线程里执行。
9.ThreadLocal 是什么?有什么用?
ThreadLocal 是一个本地线程正本变量工具类。次要用于将公有线程和该线程寄存的正本对象做一个映射,各个线程之间的变量互不烦扰,在高并发场景下,能够实现无状态的调用,特地实用于各个线程依赖不通的变量值实现操作的场景。
简略说 ThreadLocal 就是一种以 空间换工夫 的做法,在每个 Thread 外面保护了一个以开地址法实现的 ThreadLocal.ThreadLocalMap,把数据进行隔离,数据不共享,天然就没有线程平安方面的问题了。
10. Thread 类中的 yield 办法有什么作用?
Yield 办法能够暂停以后正在执行的线程对象,让其它有雷同优先级的线程执行。它是一个静态方法而且只保障以后线程放弃 CPU 占用而不能保障使其它线程肯定能占用 CPU,执行 yield()的线程有可能在进入到暂停状态后马上又被执行。
11. Java 中的 fork join 框架是什么?
fork join 框架是 JDK7 中呈现的一款高效的工具,Java 开发人员能够通过它充分利用古代服务器上的多处理器。它是专门为了那些能够递归划分成许多子模块设计的,目标是将所有可用的解决能力用来晋升程序的性能。fork join 框架一个微小的劣势是它应用了工作窃取算法,能够实现更多任务的工作线程能够从其它线程中窃取工作来执行。
12. synchronized 和 ReentrantLock 的区别
1. 两者都是可重入锁
可重入锁:重入锁,也叫做递归锁,可重入锁指的是在一个线程中能够屡次获取同一把锁,比方:
一个线程在执行一个带锁的办法,该办法中又调用了另一个须要雷同锁的办法,则该线程能够间接执行调用的办法,而无需从新取得锁,
两者都是同一个线程每进入一次,锁的计数器都自增 1,所以要等到锁的计数器降落为 0 时能力开释锁。
2.synchronized 依赖于 JVM 而 ReentrantLock 依赖于 API
- synchronized 是依赖于 JVM 实现的,后面咱们也讲到了 虚拟机团队在 JDK1.6 为 synchronized 关键字进行了很多优化,然而这些优化都是在虚拟机层面实现的
- ReentrantLock 是 JDK 层面实现的(也就是 API 层面,须要 lock() 和 unlock() 办法配合 try/finally 语句块来实现)
3.ReentrantLock 比 synchronized 减少了一些高级性能
相比 synchronized,ReentrantLock 减少了一些高级性能。次要来说次要有三点:①期待可中断;②可实现偏心锁;③可实现选择性告诉(锁能够绑定多个条件)
- 期待可中断. 通过 lock.lockInterruptibly()来实现这个机制。也就是说正在期待的线程能够抉择放弃期待,改为解决其余事件。
- ReentrantLock 能够指定是偏心锁还是非偏心锁。而 synchronized 只能是非偏心锁。所谓的偏心锁就是先期待的线程先取得锁。ReentrantLock 默认状况是非偏心的,能够通过 ReentrantLock 类的 ReentrantLock(boolean fair)构造方法来制订是否是偏心的。
- ReentrantLock 类线程对象能够注册在指定的 Condition 中,从而能够有选择性的进行线程告诉,在调度线程上更加灵便。在应用 notify()/notifyAll()办法进行告诉时,被告诉的线程是由 JVM 抉择的,用 ReentrantLock 类联合 Condition 实例能够实现“选择性告诉”
4. 应用抉择
- 除非须要应用 ReentrantLock 的高级性能,否则优先应用 synchronized。
- synchronized 是 JVM 实现的一种锁机制,JVM 原生地反对它,而 ReentrantLock 不是所有的 JDK 版本都反对。并且应用 synchronized 不必放心没有开释锁而导致死锁问题,因为 JVM 会确保锁的开释
13. 谈谈 volatile 的应用及其原理?
volatile 的两层语义:
1、volatile 保障变量对所有线程的可见性:当 volatile 变量被批改,新值对所有线程会立刻更新。或者了解为多线程环境下应用 volatile 润饰的变量的值肯定是最新的。
2、jdk1.5 当前 volatile 完全避免了指令重排优化,实现了有序性。
volatile 的原理:
获取 JIT(即时 Java 编译器,把字节码解释为机器语言发送给处理器)的汇编代码,发现 volatile 多加了 lock addl 指令,这个操作相当于一个内存屏障,使得 lock 指令后的指令不能重排序到内存屏障前的地位。这也是为什么 JDK1.5 当前能够应用双锁检测实现单例模式。
lock 前缀的另一层意义是使得本线程工作内存中的 volatile 变量值立刻写入到主内存中,并且使得其余线程共享的该 volatile 变量有效化,这样其余线程必须从新从主内存中读取变量值。
具体原理见这篇文章:https://www.javazhiyin.com/61…
14. synchronized 关键字和 volatile 关键字的区别?
- volatile 关键字是线程同步的轻量级实现,所以 volatile 性能必定比 synchronized 关键字要好。
- volatile 关键字只能用于变量而 synchronized 关键字能够润饰办法以及代码块。理论开发中应用 synchronized 关键字的场景还是更多一些。
- 多线程拜访 volatile 关键字不会产生阻塞,而 synchronized 关键字可能会产生阻塞
- volatile 关键字能保证数据的可见性,但不能保证数据的原子性。synchronized 关键字两者都能保障
- volatile 关键字次要用于解决变量在多个线程之间的可见性,而 synchronized 关键字解决的是多个线程之间拜访资源的同步性。
这里也举荐一个我收集的计算机书籍仓库,仓库目前有上百本经典 cs 电子书,看经典的书籍会更悟得深~
点此链接即可中转书单,计算机必看经典书籍(含 pdf 下载)
Github 也有相应仓库,https://github.com/cosen1024/…
欢送 star。