一些JUC面试题

线程池有什么作用?创建线程池的时候需要哪些参数?使用线程池的时候有哪些注意事项?

一个Java的线程对应了操作系统内核的线程,直接new一个线程是非常消耗资源的,创建线程池便于统一管理线程,可以大大降低资源消耗。

ThreadPool有七大参数

阿里开发手册:不要new,可能存在OOM内存溢出的风险,

sleep()方法和wait()方法的区别

sleep()方法它是Thread类的一个静态方法,主要用于暂停线程的执行,可以把该线程占用的CPU让给其他线程,但是仍然保持对锁的使用。

wait()方法是Object类的一个方法,通常与同步方法或者同步代码块一起使用。当一个线程在在调用wait()方法之后,它会释放对象的锁,并进入该对象的等待队列waitset中,调用wait()方法的线程会一直等待,直到其他线程调用同一个对象的notify()或者notifyAll()方法。wait()方法多用于线程之间的协作。

Blocked是正在等待锁的线程,在EntryList里面

Waiting是获得了锁,但是不满足某个条件调用了wait方法的线程,在WaitSet里面

Waiting线程在Owner线程调用notify()或者notifyAll()方法时唤醒,进入EntryList重新竞争锁

Blocked线程在Owner线程释放锁时,会唤醒EntryList中的方法

必须要获得锁synchronized(obj)才能使用wait方法和notify方法

synchronized(lock) {
    while (!condition) {
        lock.wait();
    }
    //do something
}

//another Thread

synchronized(lock) {
    lock.notifyAll();
}

线程的生命周期

五种状态(操作系统)

初始状态:相当于Java里面new了一个Thread对象,但是没有调用它的start方法

可运行状态:就绪状态,可以被操作系统的调度程序调度,时刻准备着被CPU分到时间片去运行

运行状态:获得CPU时间片运行的状态

        运行状态的线程在CPU时间片用完之后,会转化为可运行状态,会导致线程的上下文切换

阻塞状态:如果调用阻塞API,比如说BIO读写文件,这时候该线程实际上不会用到CPU,会导致线程的上下文切换。在BIO结束之后,操作系统会将线程转换为可运行状态,阻塞状态没有获得CPU时间片的能力

终止状态:线程执行完毕

六种状态(JUC API)

NEW:线程被状态但是没有调用start方法

RUNNABLE:操作系统的可运行状态、运行状态和阻塞状态都是Runnable状态

BLOCKED:和操作系统的阻塞状态不是一个状态,操作系统的阻塞状态是指没有获得时间片,BLOCKED是指没有获得对象锁,进入了这个锁monitor得entryList里面等待获得锁而阻塞

WAITING:如果调用obj.wait方法或者join方法,等待其他线程notify它

TIMED_WAITING:有时限的等待

1new Thread().start()6t.join(n)/t.interrupt()
2obj.wait()/obj.notify/t.interrupt()7LockSupport.parkNanos()/
3t.join()/t.interrupt()8Thread.sleep()
4LockSupport.park()/unpark()9获取锁对象竞争失败
5obj.wait(n)/10当前线程运行完毕

Java对象头的保存方式

1,Mark Word

2,指向类的指针

3,数组长度(只有数组对象才有)

 synchronized实现原理

synchronized就是对象锁,它能够实现同一时刻至多只有一个线程持有对象锁,其他对象再想要获得这把锁就会阻塞住,在底层,就是这个对象关联了一个monitor,持有这把锁的线程是owner,让后没有竞争到的线程会在entryList中等待,处于blocked状态。如果线程因为缺少条件而调用obj.wait()方法,就会进入waitset,处于wait状态,只有其他线程在持有锁的过程中调用obj.notify()或者notifyAll()方法时,才会重新进入entryList,获得竞争锁的能力。

轻量级锁实现原理

轻量级锁的使用场景:如果一个对象虽然有多线程要加锁,但加锁的时间是错开的(也就是没有竞争),那么可以使用轻量级锁来优化。优先会采用轻量级锁加锁,尝试把对象头改为00
轻量级锁对使用者是透明的,即语法仍然是 synchronized ,假设有两个方法同步块,利用同一个对象加锁。
  1. 创建锁记录(Lock Record)对象,每个线程都的栈帧都会包含一个锁记录的结构,内部可以存储锁定对象的 Mark Word。
  2. 让锁记录中 Object reference 指向锁对象,并尝试用 cas 替换 Object Mark Word,将 Mark Word 的值存入锁记录。
  3. 如果 cas 替换成功,对象头中存储了 锁记录地址和状态 00 ,表示由该线程给对象加锁。
  4. 如果 cas 失败,有两种情况:如果是其它线程已经持有了该 Object 的轻量级锁,这时表明有竞争,进入锁膨胀过程;如果是本线程执行了 synchronized 锁重入,那么再添加一条 LockRecord 作为重入的计数。
  5. 当退出 synchronized 代码块(解锁时)如果有取值为 null 的锁记录,表示有重入,这时重置锁记录,表示重入,计数减一; 当退出 synchronized 代码块(解锁时)锁记录的值不为 null ,这时使用 cas Mark Word 的值恢复给对象 头。 成功,则解锁成功; 失败,说明轻量级锁进行了锁膨胀或已经升级为重量级锁,进入重量级锁解锁流程。

锁膨胀实现原理

如果在尝试加轻量级锁的过程中, CAS 操作无法成功,这时一种情况就是有其它线程已经为此对象加上了轻量级锁(有竞争),这时需要进行锁膨胀,将轻量级锁变为重量级锁。
Thread-1 进行轻量级加锁时, Thread-0 已经对该对象加了轻量级锁。
这时 Thread-1 加轻量级锁失败,进入锁膨胀流程
即为 Object 对象申请 Monitor 锁,让 Object 指向重量级锁地址
然后Thread-1 进入 Monitor EntryList BLOCKED
当 Thread-0 退出同步块解锁时,使用 cas Mark Word 的值恢复给对象头,失败。这时会进入重量级解锁流程,即按照 Monitor 地址找到 Monitor 对象,设置 Owner null ,唤醒 EntryList BLOCKED 线程

线程加锁的优先级

偏向锁-轻量级锁-重量级锁

为什么调用对象的hashcode之后就不再是偏向锁状态?

因为在正常状态,对象的对象头中保存了31位的hashcode,而使用偏向锁之后,54位的空间都被分配给了线程ID,没有空间存储hashcode。而对于轻量级锁和重量级锁,虽然对象头会指向锁记录或者monitor,但也把hashcode保存在了锁记录或者monitor中,等锁释放的时候会交换回来。

HashMap并发死链问题

只有在JDK7版本下才会发生

先在HashMap里面放12个元素,放第13个元素的时候会触发扩容机制

用两个线程分别放第13个元素,两个线程在操作一个哈希槽的链表产生了死循环问题

ConcurrentHashMap实现原理

1.8

带有forwarding node表示是处理过的,从后往前进行处理,其他线程看到这个node之后就会知道这个node已经被处理过,在get过程中发现某一个node是forwarding node就会到新的table里面去找相应的key

在链表长度大于8且node长度大于64时,会触发树化操作,TreeBin是头结点,TreeNode是红黑树里面的每一个节点

初始化initTable:

get方法:

put方法:

chm不允许有空的键值

cas来保证不会有多个线程重复创建哈希表

只有在node下标冲突时才会加锁,锁的是node头结点,

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值