面试基础
synchronized关键字的底层原理:
synchronized关键字与JVM的monitor有关,monitor里面有个计数器,一开始计数器为0。如果一个线程要获取monitor锁,就看看他的计数器是不是0,如果是0的话,
那么就说明没有人加锁,他就可以获取锁,然后对计数器加1.加锁,一般来说一个对象加一把锁。
monitor中有相关指令:monitorenter和monitorexit
这个monitor是个可重入锁。
线程1:
public void myObject{
}
synchronized(myObject){
//TODO
synchronized(myObject){
//TODO
}
}
如果还有其他线程对被加锁的对象进行修改,此时这个线程被阻塞,等待线程1释放锁。
CAS的实现原理:
CAS:compare and set
原理图如下:
当多个线程想对一个对象进行修改时,线程会先读取对象当前的值,然后对当前的进行修改完以后,再跟对象进行一次CAS,如果读取的当前值跟对象一致,
就进行修改对象操作,否则重复上述步骤。CAS再底层的硬件级别保证一定是原子的,同一时间只有一个线程可以执行CAS,先比较再设置,其他线程的CAS
同时间去执行的话会失败
ConcurrentHashMap实现原理:
HashMap的底层原理(结构)是:
JDK1.8以前,是数组+链表
JDK1.8以后,是数组+链表+红黑树
JDK1.7以前,对map进行分段加锁
把map中的数组拆分成很多段,对每一段进行加锁
JDK1.8以后优化细粒度,一个数组,每个元素进行CAS,如果失败说明有人正在进行操作,此时synchronized对数组元素加锁,链表+红黑树处理对数组每个元素加锁
同一时间,只有一个线程能成功执行CAS操作
AQS实现原理:
AQS:abstract queue synchronizer抽象队列同步器
原理图:
代码:
ReentrantLock lock = new ReentrantLosk(); //此时new的锁为一个非公平锁
//多个线程过来都尝试
lock.lock();
//TODO
lock.unlock();
在上一个原理图的基础上,此时线程1释放锁并唤醒线程2,在唤醒过程中,来了一个线程3进行加锁
并且线程3加锁成功,线程2继续进入等待队列中。
如何变成公平锁呢?在new锁对象的时候,添加参数true-->ReentrantLock(true)
那么此时new的锁为公平锁,所有新加进来的线程都会先看一下等待队列是否有人,有的话则进入等待队列,而不是直接去加锁
线程池的底层工作原理:
系统在执行任务的时候,会先创建一个线程池,然后这个线程池中有一定量的线程,用这些线程去执行任务;
ExecutorService threadPool = Executors.newFixedThreadPool(3); //3对应的参数是corePoolSize
threeadPool.submit(new Callable(){
//Override
public void run(){}
});
提交任务时,先看一下线程池里的线程数量是否小于corePoolSize,也就是3,如果小于,直接创建一个线程执行任务
执行完任务之后,这个线程不会死掉,他会尝试从一个无界的LinkedBlockingQueue里获取新的任务,如果没有新的任务,此时就会阻塞等待新的任务
持续提交任务,上述流程反复执行,只要线程池的线程数量小于corePoolSize,就会直接创建新线程来执行任务,执行完了就尝试从无界队列中获取任务,知道线程池里有corePoolSize个线程
接着再次提交任务,会发现线程数量已经跟corePoolSize一样大了,此时直接把任务放进队列,线程会争抢获取这个任务执行,如果所有的线程此时都在执行任务,那么这个无界队列里的任务就可能会越来越多