根据我以往的面试经验,总结一些有深度的、常问的面试题供大家参考,基本上纯手敲,如果对你有用希望多多点赞多多支持哈。
多线程编程中一般线程的个数都会大于CPU的核心数,而一个CPU核心在任意时刻只能被一个线程使用,为了让这些线程都能得到有效执行,CPU采取的策略是为每个线程分配时间片并流转的形式。当一个线程的时间片用完的时候就会重新处于就绪状态让给其它线程使用,这个过程就属于一次上下文切换。
synchronized关键字的底层原理属于JVM层面,synchronized同步语句块的实现是monitorenter和monitorexit指令,其中monitorenter指向同步代码块开始的位置,monitorexit指向同步代码块结束的位置。当执行monitorenter指令时,线程试图获取monitor(monitor对象存在于每个Java对象的对象头中)的持有权。当计数器为0则可成功获取,获取后将锁计数器设置为1.相应的在执行monitorexit指令后,将锁计数器设为0,表明锁被释放。如果获取对象锁失败,那么当前线程就要阻塞等待,直到锁被另一个线程释放为止。
synchronized修饰方法取而代之的是ACC_SYNCHRONIZED标识,该标识指明了该方法是一个同步方法。
Thread类中有一个threadLocals和一个inheritableThreadLocals变量,它们都是ThreadLocalMap类型的变量,我们可以把ThreadLocalMap类实现的定制化的Hashmap。默认情况下这两个变量都是null,只有当前线程调用ThreadLocal的set或get方法时才会创建他们,实际上我们调用这两个方法时,我们调用的是ThreadLocalMap类对应的get()、set()方法。
ThreadLocalMap中使用的key为ThreadLocal的弱引用。所以当ThreadLocal没有被外部强引用的情况下,在垃圾回收时,key会被清理掉,而value不会被清理掉。这样一来,ThreadLocalMap中就会出现key为null的Entry。假如我们不做任何措施的话,value永远无法被GC回收,这时候就可能发生内存泄漏问题。因此,使用完ThreadLocal方法后,最好手动调用remove()方法。
AQS 核心思想是,如果被请求的共享资源空闲,则将当前请求资源的线程设置为有效的工作线程,并且将共享资源设置为锁定状态。如果被请求的共享资源被占用,那么就需要一套线程阻塞等待以及被唤醒时锁分配的机制,这个机制 AQS 是用 CLH 队列锁实现的,即将暂时获取不到锁的线程加入到队列中。
CLH(Craig,Landin and Hagersten)队列是一个虚拟的双向队列(虚拟的双向队列即不存在队列实例,仅存在结点之间的关联关系)。AQS 是将每条请求共享资源的线程封装成一个 CLH 锁队列的一个结点(Node)来实现锁的分配。
看个 AQS(AbstractQueuedSynchronizer)原理图:
AQS 使用一个 int 成员变量来表示同步状态,通过内置的 FIFO 队列来完成获取资源线程的排队工作。AQS 使用 CAS 对该同步状态进行原子操作实现对其值的修改。
private volatile int state;//共享变量,使用volatile修饰保证线程可见性
状态信息通过 protected 类型的 getState,setState,compareAndSetState 进行操作
//返回同步状态的当前值
protected final int getState() {
return state;
}
//设置同步状态的值
protected final void setState(int newState) {
state = newState;
}
//原子地(CAS操作)将同步状态值设置为给定值update如果当前同步状态的值等于expect(期望值)
protected final boolean compareAndSetState(int expect, int update) ' data-report-query='spm=1001.2101.3001.9621'>立即使用