- ThreadLocal是Java中所提供的的线程本地存储机制,可以利用该机制将数据缓存在某个线程内部,该线程在任意时刻、任意方法中获取缓存的数据
- 底层是通过ThreadLocalMap来实现的,每个Thread对象中都存在一个ThreadLocalMap,Map的key为ThreadLocal对象,value为需要缓存的值
- 可能会存在内存泄露,因为当ThreadL对象使用完成之后,需要把key和value都回收掉,也就是Entry对象进行回收,但线程池中的线程不会回收,而线程对象是通过强引用指向ThreadLocalMap的,ThreadLocalMap也是通过强引用指向Entry对象,线程不被回收,Entry对象也不会被回收,从而出现内存泄露,解决方案就是,使用完ThreadLocal对象之后,手动调用ThreadLocal的remove方法,手动清楚Entry对象
- 使用场景大多数是连接管理,一个线程持有一个连接对象,该对象在不同的方法之间进行传递,线程之间不共享同一个连接
- 禁止指令重排
- 保证属性线程之间可见性
volatile关键字来修饰对象的属性,在并发环境下保证属性的可见性,对于加了volatile关键字的属性,在对这个属性进行修改的时候,会直接将CPU缓存中的数据写会到主内存中,对于这个变量的读取也会直接从主内存中读取,从而保证了可见性,底层是通过操作系统的内存屏障来实现的,禁止指令重排,保证有序性。
3.几种锁概念介绍这些都只是设计或者说概念上的东西,具体到JAVA上面的锁,可能属于其中的一种或者多种,比如synchronize属于可重入锁和非公平锁。
- 公平锁:不直接尝试加锁,直接加入排队队列
- 非公平锁 :先尝试加锁,失败了再加入排队队列
- 可重入锁:同一个线程,多次尝试加锁,都能加锁成功,或者说在一个方法里面加了锁,调用了另外一个方法,尝试加同样的锁,也能加锁成功
- 自旋锁:循环尝试去加锁,一直自旋…,不会阻塞线程
- 偏向锁:一个线程加了锁后,后续这个线程再次加锁,可以直接获取到
默认非公平,底层实现都是使用AQS来进行排队,属于可重入锁。
公平锁和非公平锁区别:在使用lock()方法加锁时,如果是公平锁,会先检查AQS队列中是否有线程在排队,有的话直接加入到排队队列上,如果是非公平锁,则先去竞争锁,尝试获取锁,获取不到的时候再加入排队队列。
lock():阻塞加锁,等待加锁完成后,再执行后续代码;
tryLock():非阻塞加锁,tryLock();方法返回一个布尔值,不用非得等到加锁成功,可以继续执行其他代码,一般用于自旋锁,循环判断tryLock是否成功,然后再去做其他事情
- java关键字,可以修饰方法,变量,类等等。
- 底层是通过CAS【比较并交换】的方式加锁,最底层汇编层面主要是lock cmxchg指令
内部有锁升级机制:
无锁状态====》偏向锁====》轻量级锁====》重量级锁
- 一开始默认是没有锁状态的
- 偏向锁:一个线程进来之后加锁,先加偏向锁,这样该线程下次如果再次获取该锁就可以直接获取到了
- 轻量级锁:一个线程加上偏向锁后,如果此时有第二个线程来竞争所,偏向锁就会升级为轻量级锁,轻量级锁底层是通过锁自旋来实现的,并不会阻塞线程
- 如果自旋次数过多【默认10次】,则会升级为重量级锁(Mutex Lock),重量级锁会导致线程阻塞,重量级锁的话,对象关联的Monitor对象,加上重量级锁后,该对象头中的MarkWord设置指向Monitor对象的指针。重量级锁属于操作系统级别的锁,涉及到用户态和内核态的切换,成本比较高。
锁的状态是存放在对象头的MarkWord上的;锁的标志位。
6.ReentrantLock和synchronize区别- synchronize是java关键字,ReentrantLock是一个类
- synchronize会自动加锁和释放锁,ReentrantLock需要手动加解锁
- synchronize底层是jvm层面的锁,ReentrantLock是API层面的锁
- synchronize锁的是对象,锁信息保存在对象头上,ReentrantLock是通过代码中int类型的state标识来标识锁的状态
- 都属于可重入锁,synchronize是非公平锁,ReentrantLock默认非公平,但可以选择公平锁
- synchronize底层有所升级过程
CountDownLatch表示计数器,一般给CountDownLatch设置一个数字,一个线程调用CountDownLatch的await()方法将会阻塞当前线程,其他线程可以调用CountDownLatch的countDown()方法来对CountDownLatch中的数字减一,当数字被剪成0后,所有await的线程都将被唤醒。底层实现的话,调用await()方法会利用AQS排队,一旦数字被减为0,则会将AQS中排队的线程依次唤醒。
Semaphore表示信号量,可以设置许可的个数,表示同时允许最多多少个线程使用该信号量,通过acquire()方法来获取许可,如果没有许可可用则线程阻塞,并通过AQS来排队,可以通过release()方法来释放许可,当某个线程释放了某个许可后,会从AQS中正在排队的第一个线程开始依次唤醒,知道没有空闲许可。
8. 线程池底层工作原理线程池内部是通过队列+线程实现的,当我们利用线程池执行任务时:
- 如果此时线程池中的线程数量笑愚corePoolSize,即线程池中的线程都处于空闲状态,也要创建新的线程来处理被添加的任务
- 如果此时线程池中的数量等于corePoolSize,但是缓冲队列workQueue未满,那么任务被放入缓冲队列
- 如果此时线程池中的线程数量大于等于corePoolSize,缓冲队列workQueue满,兵器线程池中的数量笑愚maxnumPoolSize,建新的线程来处理被添加的任务
- 如果此时线程池中的线程数量大于corePoolSize,缓冲队列workQueue满,并且线程池中的数量小于maximumPoolSize,那么通过handler所制定的策略来处理此任务
- 当线程池中的线程数量大于corePoolSize时,如果某空闲时间超过KeepAliveTime,线程将被终止,这样,线程池可以动态调整池中的线程数。



