答:java
(1)进程切换时,涉及到当前进程的 CPU 环境的保存和新被调度运行进程的 CPU 环境的设置。程序员
(2)线程切换仅须要保存和设置少许的寄存器内容,不涉及存储管理方面的操做。面试
面试官:进程间如何通信?线程间如何通信?缓存
答:进程间通信依靠 IPC 资源,例如管道(pipes)、套接字(sockets)等;安全
线程间通信依靠 JVM 提供的 API,例如 wait()、notify()、notifyAll() 等方法,线程间还能够经过共享的主内存来进行值的传递。多线程
答:阻塞和非阻塞一般用来形容多线程间的相互影响。好比一个线程占用了临界区资源,那么其余全部须要这个而资源的线程就必须在这个临界区中进行等待。等待会致使线程挂起,这种状况就是阻塞。此时,若是占用资源的线程一直不肯意释放资源,那么其余全部阻塞在这个临界区上的线程都不能工做。框架
非阻塞的意思与之相反,它强调没有一个线程能够妨碍其余线程执行。全部的线程都会尝试不断前向执行。socket
临界区是什么?分布式
答:临界区用来表示一种公共资源或者说是共享资源,能够被多个线程使用。可是每一次,只能有一个线程使用它,一旦临界区资源被占用,其余线程要想使用这个资源,就必须等待。spa
死锁应该是最糟糕的一种状况了,它表示两个或者两个以上的进程在执行过程当中,因为竞争资源或者因为彼此通讯而形成的一种阻塞的现象,若无外力做用,它们都将没法推动下去。此时称系统处于死锁状态或系统产生了死锁,这些永远在互相等待的进程称为死锁进程。
饥饿是指某一个或者多个线程由于种种缘由没法得到所须要的资源,致使一直没法执行。好比:
1)它的线程优先级可能过低,而高优先级的线程不断抢占它须要的资源,致使低优先级的线程没法工做。在天然界中,母鸡喂食雏鸟时,很容易出现这种状况,因为雏鸟不少,食物有限,雏鸟之间的食物竞争可能很是厉害,小雏鸟由于常常抢不到食物,有可能会被饿死。线程的饥饿也很是相似这种状况。
2)另一种多是,某一个线程一直占着关键资源不放,致使其余须要这个资源的线程没法正常执行,这种状况也是饥饿的一种。
与死锁相比,饥饿仍是有可能在将来一段时间内解决的(好比高优先级的线程已经完成任务,再也不疯狂的执行)
活锁是一种很是有趣的状况。不知道你们是否是有遇到过这样一种状况,当你要坐电梯下楼,电梯到了,门开了,这时你正准备出去,但不巧的是,门外一我的挡着你的去路,他想进来。因而你很绅士的靠左走,避让对方,但同时对方也很绅士,但他靠右走但愿避让你。结果,大家又撞上了。因而乎,大家都意识到了问题,但愿尽快避让对方,你当即向右走,他也当即向左走,结果又撞上了!不过介于人类的只能,我相信这个动做重复 二、 3 次后,你应该能够顺利解决这个问题,由于这个时候,你们都会本能的对视,进行交流,保证这种状况再也不发生。
但若是这种状况发生在两个线程间可能就不会那么幸运了,若是线程的智力不够,且都秉承着 “谦让” 的原则,主动将资源释放给他人使用,那么就会出现资源不断在两个线程中跳动,而没有一个线程能够同时拿到全部的资源而正常执行。这种状况就是活锁。
如何避免死锁?(常常接着问这个问题哦~)
答:指定获取锁的顺序,举例以下:
好比某个线程只有得到 A 锁和 B 锁才能对某资源进行操做,在多线程条件下
得到锁的顺序是必定的,好比规定,只有得到 A 锁的线程才有资格获取 B 锁,按顺序获取锁就能够避免死锁!!!
新建(NEW)状态:表示新建立了一个线程对象,而此时线程并无开始执行。
可运行(RUNNABLE)状态:线程对象建立后,其它线程(好比 main 线程)调用了该对象的 start() 方法,才表示线程开始执行。当线程执行时,处于 RUNNBALE 状态,表示线程所需的一切资源都已经准备好了。该状态的线程位于可运行线程池中,等待被线程调度选中,获取 cpu 的使用权。
阻塞(BLOCKED)状态:若是线程在执行过程终于到了 synchronized 同步块,就会进入 BLOCKED 阻塞状态,这时线程就会暂停执行,直到得到请求的锁。
等待(WAITING)状态:当线程等待另外一个线程通知调度器一个条件时,它本身进入等待状态。在调用Object.wait方法或Thread.join方法,或者是等待java.util.concurrent库中的Lock或Condition时,就会出现这种状况;
计时等待(TIMED_WAITING)状态:Object.wait、Thread.join、Lock.tryLock和Condition.await 等方法有超时参数,还有 Thread.sleep 方法、LockSupport.parkNanos 方法和 LockSupport.parkUntil 方法,这些方法会致使线程进入计时等待状态,若是超时或者出现通知,都会切换会可运行状态;
终止(TERMINATED)状态:当线程执行完毕,则进入该状态,表示结束。
注意:从 NEW 状态出发后,线程不能再回到 NEW 状态,同理,处于 TERMINATED 状态的线程也不能再回到 RUNNABLE 状态。
sleep 方法:是 Thread 类的静态方法,当前线程将睡眠 n 毫秒,线程进入阻塞状态。当睡眠时间到了,会解除阻塞,进行可运行状态,等待 CPU 的到来。睡眠不释放锁(若是有的话);
wait 方法:是 Object 的方法,必须与 synchronized 关键字一块儿使用,线程进入阻塞状态,当 notify 或者 notifyall 被调用后,会解除阻塞。可是,只有从新占用互斥锁以后才会进入可运行状态。睡眠时,释放互斥锁。
答:底层实现:
进入时,执行 monitorenter,将计数器 +1,释放锁 monitorexit 时,计数器-1;
当一个线程判断到计数器为 0 时,则当前锁空闲,能够占用;反之,当前线程进入等待状态。
含义:(monitor 机制)
Synchronized 是在加锁,加对象锁。对象锁是一种重量锁(monitor),synchronized 的锁机制会根据线程竞争状况在运行时会有偏向锁(单一线程)、轻量锁(多个线程访问 synchronized 区域)、对象锁(重量锁,多个线程存在竞争的状况)、自旋锁等。
答:能。
一个典型的例子是在类中有一个 long 类型的成员变量。若是你知道该成员变量会被多个线程访问,如计数器、价格等,你最好是将其设置为 volatile。为何?由于 Java 中读取 long 类型变量不是原子的,须要分红两步,若是一个线程正在修改该 long 变量的值,另外一个线程可能只能看到该值的一半(前 32 位)。可是对一个 volatile 型的 long 或 double 变量的读写是原子。
核心线程池内部实现
答:忙循环就是程序员用循环让一个线程等待,不像传统方法 wait(),sleep() 或yield() 它们都放弃了 CPU 控制权,而忙循环不会放弃 CPU,它就是在运行一个空循环。这么作的目的是为了保留 CPU 缓存
在多核系统中,一个等待线程醒来的时候可能会在另外一个内核运行,这样会重建缓存,为了不重建缓存和减小等待重建的时间就可使用它了。
答:wait() 方法应该在循环调用,由于当线程获取到 CPU 开始执行的时候,其余条件可能尚未知足,因此在处理前,循环检测条件是否知足会更好。下面是一段标准的使用 wait 和 notify 方法的代码:
4.双重校验锁
为了达到线程安全,又能提升代码执行效率,咱们这里能够采用DCL的双检查锁机制来完成,代码实现以下:
public class Singleton { private static Singleton singleton; private Singleton() { } public static Singleton getInstance(){ if (singleton == null) { synchronized (Singleton.class) { if (singleton == null) { singleton = new Singleton(); } } } return singleton; } }
这种是用双重判断来建立一个单例的方法,那么咱们为何要使用两个if判断这个对象当前是否是空的呢 ?由于当有多个线程同时要建立对象的时候,多个线程有可能都中止在第一个if判断的地方,等待锁的释放,而后多个线程就都建立了对象,这样就不是单例模式了,因此咱们要用两个if来进行这个对象是否存在的判断。
volatile 修饰符的有过什么实践?
答:
一种实践是用 volatile 修饰 long 和 double 变量,使其能按原子类型来读写。double 和 long 都是64位宽,所以对这两种类型的读是分为两部分的,第一次读取第一个 32 位,而后再读剩下的 32 位,这个过程不是原子的,但 Java 中 volatile 型的 long 或 double 变量的读写是原子的。
volatile 修复符的另外一个做用是提供内存屏障(memory barrier),例如在分布式框架中的应用。简单的说,就是当你写一个 volatile 变量以前,Java 内存模型会插入一个写屏障(write barrier),读一个 volatile 变量以前,会插入一个读屏障(read barrier)。意思就是说,在你写一个 volatile 域时,能保证任何线程都能看到你写的值,同时,在写以前,也能保证任何数值的更新对全部线程是可见的,由于内存屏障会将其余全部写的值更新到缓存。
5)ThreadLocal(线程局部变量)关键字:
答:当使用 ThreadLocal 维护变量时,其为每一个使用该变量的线程提供独立的变量副本,因此每个线程均可以独立的改变本身的副本,而不会影响其余线程对应的副本。
ThreadLocal 内部实现机制:
每一个线程内部都会维护一个相似 HashMap 的对象,称为 ThreadLocalMap,里边会包含若干了 Entry(K-V 键值对),相应的线程被称为这些 Entry 的属主线程;
Entry 的 Key 是一个 ThreadLocal 实例,Value 是一个线程特有对象。Entry 的做用便是:为其属主线程创建起一个 ThreadLocal 实例与一个线程特有对象之间的对应关系;
Entry 对 Key 的引用是弱引用;Entry 对 Value 的引用是强引用。
答:当使用 ThreadLocal 维护变量时,其为每一个使用该变量的线程提供独立的变量副本,因此每个线程均可以独立的改变本身的副本,而不会影响其余线程对应的副本。
ThreadLocal 内部实现机制:
每一个线程内部都会维护一个相似 HashMap 的对象,称为 ThreadLocalMap,里边会包含若干了 Entry(K-V 键值对),相应的线程被称为这些 Entry 的属主线程;
Entry 的 Key 是一个 ThreadLocal 实例,Value 是一个线程特有对象。Entry 的做用便是:为其属主线程创建起一个 ThreadLocal 实例与一个线程特有对象之间的对应关系;
Entry 对 Key 的引用是弱引用;Entry 对 Value 的引用是强引用。