1 CAS
- 乐观锁,先修改数据再比较并交换。
- 自旋锁,不断循环获取锁,消耗CPU
- 对比Synchronized,ReentrantLock性能更高。
- ABA问题,只关心结果。添加版本号解决。
CAS具有原子性、无锁、自旋等待和无阻塞的特点
2. ThreadLocal的底层原理
- ThreadLocal线程本地存储机制,能把数据缓存再某一个线程内,该线程可以在任意时刻,方法中获取缓存的数据。
- ThreadLocal底层是通过ThreadLocalMap来实现,在Thread对象中存在一个ThreadLocalMap,key->ThreadLocal对象,value->需要缓存的值
- 使用不当会造成内存溢出。使用后,需要手动remove ThreadLocalMap对应的Entry对象。
- ThreadLocal 应用场景-事务,数据库连接。(一个线程持有一个连接)
3 Synchrouozed和ReentranWriteReadLock的区别?
- synchronized-关键词;ReentrantLock-类
- synchronized-jvm实现;ReentranLock-API实现
- synchronized-自动加解锁;ReentranLock-手动加解锁。
- synchronized-非公平锁;ReentranLock-可公平/非公平锁。
- synchronized-锁信息存在对象头;ReentranLock-锁中的state表示锁的标识。
- synchronized底层有一个锁升级的过程。
4 线程有哪些状态?
- 新建
- 就绪。Thread.start()
- 运行
- 阻塞
- 等待。Object.wait()
- 超时等待.超时自动返回
-
死亡
5 新开线程的三种方式?
- Runnable接口
- Thread类
- Callable
- 线程池
6 Volatile为什么不能保证原子性?
- 保证可见性
- 禁止指令重排(执行顺序不变)
- 不保证原子性
7 ConcurrentHashMap底层实现原理?
jdk1.7 数组+链式存储
jdk1.8 数组+链表+红黑树
8 并发,并行,串行 区别?
- 串行:线程排队执行。
- 并行:2个任务同时执行。
- 并发:2个任务看着是一起执行,由CPU切换执行线程1一部分,再切换上下文执行线程2一部分,来回切换,看着是一起执行。
9 死锁如何避免?
造成死锁的4个要素:
1.一个资源每次只能被一个线程使用。
- 一个线程阻塞等待资源时,不释放已占有资源。
- 一个线程获取资源,且未使用完前,不能被强行剥夺。
- 若干线程头尾相接在等待资源。
只要破坏以上4个要素之一即可。 - 加锁顺序。保持统一顺序。
- 加锁时间。超时时间。
- 死锁检查。
10 线程池的底层工作原理?
- 初始化:线程池会在启动时创建一定数量的线程,并将它们置于等待状态,直到有任务分配给它们。
- 任务分配:当有任务需要执行时,线程池会从线程池中取出一个空闲线程,并将任务分配给它。如果所有线程都正在执行任务,那么任务会被放入任务队列中等待执行。
- 任务执行:线程会执行任务,并在任务完成后返回线程池,并变成空闲状态。如果线程在执行任务时发生异常,则线程会被标记为死亡,并重新创建一个新线程替代它。
- 线程回收:当线程池不再需要某个线程时(例如由于线程空闲时间过长),线程会被回收并从线程池中移除。
扩容缩容:如果任务量较大,线程池可能需要动态扩容以适应负载。反之,如果任务量较少,线程池可能需要缩容以节省系统资源。
总体来说,线程池通过管理线程资源,使得多个并发任务可以在同一时刻进行执行,从而提高了程序的并发性能。
- 线程数量<corePoolSize,即使线程池的线程处于空闲,也创建新线程处理。
- 线程数量>=corePoolSize,缓冲队列workQueue未满,那么任务放到缓冲队列中。
- 如果无法加入workQueue,且线程数<maxPoolSize,创建新线程执行任务。
如果线程数>=maxPoolSize,拒绝执行策略。
11 ConcurrentHashMap扩容?
jdk1.7 基于segment分片实现。segment也是新增数组拷贝。
jdk1.8 多线程同时扩容,先生成新的数组。每一个线程分组转移数组。
12 synchronized偏向锁,轻量锁,重量锁,自旋锁
1.偏向锁:线程获取对象锁后,会把线程id存到对象头,下次可以直接获取
2.轻量级:线程a获取对象锁后,线程b等待获取对象锁。此时偏向锁升级为轻量级锁。不阻塞,自旋。
3.重量级:在轻量级锁的基础上,不断自旋,但却无法竞争到锁,升级成重量级锁。
4.自旋锁,不阻塞,通过CAS乐观锁,不断循环获取预期的标记,若获取了则也获取到锁。
13 sleep,wait,join,yield的区别?
- sleep,Thread静态方法,不释放锁,释放cpu的执行权。
- wait,Object方法,释放锁,进去等待池,需要被唤醒。
- join,在B线程执行A.join(),B进入阻塞状态,等待A的结束。(不释放锁)
- yield,释放cpu的执行权,之后重新竞争cpu执行权。(不释放锁)
14 AQS的理解?
- AbstractQueuedSynchronizer,抽象队列同步器
- AQS是java线程同步的组件,是jdk很多锁工具的核心实现组件。
- AQS维护了一个信号量state和一个线程组成的双向队列。
线程队列-排队,信号量-红绿灯,控制线程的执行和阻塞。 - 在可重入锁的场景下,state表示加锁的次数,0表示无锁,加锁+1,释放锁-1.
AbstractQueuedSynchronizer(简称AQS)是Java并发包中一个很重要的类,它提供了一种基于FIFO等待队列实现阻塞锁和相关同步器的机制。AQS是实现Lock接口、ReentrantLock、Semaphore、CountDownLatch、ReentrantReadWriteLock、FutureTask等类的基础。
AQS的主要作用是为导致供了一系列的方法,可以用于实现各种不同类型的同步器,如独占式锁、共享式锁等。
AQS的设计思路是将同步状态(state)与线程(Thread)构成一个双向链表,当多个线程同时争夺资源时,AQS会将这些线程放入一个FIFO的等待队列中,等待队头的线程获取到资源后,其他线程才有机会继续争夺资源。这种方式避免了“饥饿”情况的出现,确保了线程获取资源的公平性。
在具体实现中,AQS类提供了acquire()和release()两个核心方法,其中acquire()方法用于尝试获取锁或者同步状态,如果获取失败则将当前线程加入到等待队列中;而release()方法用于释放锁或者同步状态,并唤醒等待队列中的下一个线程。
因此,通过继承AQS类,我们可以实现一个可靠的同步工具,如锁、信号量、计数器和队列等,这些同步工具可以用于协调多个线程之间的执行顺序和资源访问,从而提高程序的并发性能和可靠性。
15 线程池中空闲的线程属于哪种状态?
等待或者超时等待
16 线程池的优点?
- 降低线程创建和销毁的开销
- 提高了系统资源的利用
- 控制并发数,避免过多的线程竞争导致系统崩溃。
- 提高代码的可维护性,将线程管理和任务处理逻辑分开。
17 线程池的缺点?
- 增加系统的复杂度
- 短时间大量任务的处理率不如直接使用线程。(执行线程数量>线程数)