1. sleep(0)的意义
他的意义在于
调用Thread.sleep(0)的当前线程确实的被冻结了一下,让其他线程有机会优先执行。相当于一个让位动作
。在线程没退出之前,线程有三个状态:就绪态、运行态、等待态。sleep(n) 之所以在n秒内不会参与CPU竞争,是因为,当线程调用sleep(n)的时候,线程是由运行态转入等待态,线程被放入等待队列中,等待定时器n秒后的中断事件,当到达n秒计时后,线程才重新由等待态转入就绪态,被放入就绪队列中,等待队列中的线程是不参与cpu竞争的。只有就绪队列中的线程才会参与cpu竞争,所谓cpu调度,就是根据一定的算法,从就绪队列中选择一个线程来分配cpu事件。
sleep(0) 之所以马上回去参与cpu竞争,是因为调用sleep(0)后,因为0的原因,线程直接回到就绪队列,参与cpu竞争。
2. synchronized原理及应用
synchronized可以保证方法或者代码块在运行时,同一时刻只有一个方法可以进入到临界区,同时还可以保证共享变量的内存可见性。
Java中每一个对象都可以作为锁,这是sychronized实现同步的基础。
- 普通同步方法:锁的是当前实例对象
- 静态同步方法:锁的是当前类的class对象
- 同步方法块:锁的是括号里面的对象。
同步方法块时使用monitorenter和monitorexit指令来实现,而对于同步方法使用acc_sychronized来完成。无论采用哪种方式,本质都是对一个对象的监视器进行获取,而这个获取过程是排他的,也就是同一时刻只能有一个线程获取到由synchronized所保护对象的监视器。任何一个对象都有自己的监视器,执行方法的线程必须先获取到该对象的监视器才能进入方法块和同步方法,而没有获取到监视器的线程将会被阻塞。
Java对象头和monitor是实现synchronized的基础,Java对象头中主要包括两部分数据,标记字段和类型指针。标记字段用于存储对象自身的运行时数据,包括哈希码、GC分代年龄、锁状态标志、偏向线程ID等。虚拟机通过类型指针来确定对象是哪个类的实例。
锁的主要存在四种状态,依次是:无锁、偏向锁、轻量级锁、重量级锁
。他们会随着竞争的激烈而逐渐升级,注意锁可以升级不可以降级,这种策略是为了提高获得锁和释放锁的效率。
3. 用过哪些原子类,他们的原理是什么
Atomic包下的类,基本都是使用Unsafe实现的包装类,在底层就是CAS操作实现的。
4. 线程池的关闭方式有几种,各自的区别是什么
可以调用线程池的shutdown或者shutdownNow方法来关闭线程池。他们的原理是遍历线程池中的工作线程,然后逐个调用线程的interrupt方法来中断线程,所以
无法响应中断的任务可能永远无法终止
。
5. Spring的Controller是单例还是多例,怎么保证并发的安全
Spring中的Controller默认是单例的,也就是singleton模式。所以如果Controller中有一个私有变量a,所有请求到同一个Controller时,使用的变量a都是公用的,即若是某个请求修改了这个变量a,则在别的请求中能够读到这个修改的内容。
为了保证并发安全,常见有两种解决方法
- 在Controller中使用ThreadLocal变量
- 在Spring配置文件Controller中声明为scope="prototype",每次都创建新的controller,不再使用单例模式。
6. 如何实现一个并发安全的链表
- 采用粗粒度锁,完全锁住链表。
- 采用细粒度锁,只锁住需要修改的节点。
- 利用CAS来修改节点。
7. 有哪些无锁的数据结构,怎么做
要实现一个线程安全的队列有两种方式:阻塞和非阻塞。阻塞队列就是锁的应用如sychronized、ReentrantLock。而无阻塞就是CAS算法的应用(无锁),比较常见的是ConcurrentLinkedQueue,这是一个基于链表节点的无边界的线程安全队列,采用FIFO原则对元素进行排序,采用CAS算法实现。
8. 对AbstractQueuedSychronizer的了解,其加锁以及解锁流程,独占锁和公平锁加锁有什么不同
- AQS即队列同步器,是用来构建锁或者其他同步组件的基础框架,使用了一个int成员变量表示同步状态,通过内置的FIFO队列来完成资源获取线程的排队工作。
- AQS使用一个int成员变量state来表示同步状态,当state>0时表示已经获取了锁,当state=0时表示释放了锁。他提供了三个方法getState()、setState(int newState)、compareAndSetState(int expect,int update)来对同步状态state进行操作,当然AQS对state的操作是安全的。
- AQS通过内置的FIFO同步队列来完成线程获取资源的排队工作,如果当前线程获取同步状态失败时,AQS会将当前线程以及等待状态等信息构造成一个节点(Node)并将其加入队列,同时会阻塞当前线程,当同步状态释放时,则会把节点中的线程唤醒,使其在次尝试获取同步状态。
共享式获取和独占式获取最主要的区别在于同一时刻能否有多个线程同时获取到同步状态。读写锁ReentrantReadWriteLock,他的读取锁ReadLock是共享式的,但是他的WriteLock就是独占式的。
公平锁和非公平锁,公平的获取锁也就是等待时间最长的线程最优先获得锁。其关键在于获取锁的时候是否按照FIFO的顺序来。
9. ConcurrentLinkedQueue和LinkedBlockingQueue的用处和不同之处
ConcurrentLinkedQueue采用CAS操作Node节点(Node里的元素也使用volatile修饰)来保证元素的一致性。LinkedBlockingQueue使用一个独占锁来保证线程安全,然后用Condition来做阻塞操作。实现了先进先出特性,是作为生产者消费者的首选。
10. 谈谈对读写锁的理解
- 和排他锁ReentrantLock不同,读写锁ReentrantReadWriteLock在同一时刻可以允许多个读线程访问,但是在写线程访问时,所有的读线程和其他写线程均被阻塞。读写锁维护了一个对锁,一个读锁和一个写锁,通过分离读锁和写锁,使得并发性相比一般的排他锁有了很大的提升。
- 写锁是一个支持可重入的排他锁,写锁的状态获取最终会调用tryAcquire(int arg)方法,在判断重入时加入了一项条件:读锁是否存在,因为要确保写锁的操作对读锁是可见的,因此只有等读锁完全释放后,写锁才能够被当前线程所获取。一旦写锁获取了,所有其他读、写线程均会被阻塞。
- 读锁是一个可重入的共享锁,他能够被多个线程同时获取,在没有其他写线程访问时,读锁总是获取成功。
11. 可以创建volatile数组吗
Java中可以创建volatile数组,不过只是一个指向数组的引用,而不是整个数组。如果改变引用指向的数组,将会收到volatile保护,但是,如果多个线程同时改变数组的元素,volatile标识符就不能起到保护作用。
同理,对于Pojo类,使用volatile修饰,只能保证这个引用的可见性,不能保证其内部的属性。
12. 一个线程池设计的最大线程数应该考虑哪些因素
要想合理的配置线程池的大小,首先要分析任务的特性,可以从以下几个角度分析
- 任务的性质:计算密集型任务、IO密集型任务
- 任务的优先级:高、中、低
- 任务的执行时间:长、中、短
- 任务的依赖性:是否依赖其他系统资源,如数据库操作。
- 在有N个CPU的系统上,计算密集型任务应配置尽可能少的线程,可以将线程池大小设置为N+1.
- IO密集型任务应该配置尽可能多的线程,因为IO操作不占用CPU,应加大线程数量,如线程大小设置为2N+1.
- 结论:
线程等待时间所占比例越高,需要越多线程。线程CPU时间所占比例越高,需要越少线程。
13. 为什么线程池中线程等待时间所占比例越高,需要越多线程,CPU所占比例越高,需要越少线程
1. 线程等待时间所占比例高,需要越多线程
- 减少等待延迟:当线程执行的任务包含大量的等待时间(如IO操作、数据库查询、网络通信等),单个线程在执行过程中会有大量时间处于非CPU执行状态。这种情况下,增加线程数量可以让更多的线程在等待时同时执行,从而减少因等待导致的延迟和资源闲置。
- 提高系统吞吐量:在多线程环境下,每个线程都可能在等待外部资源时暂停执行,通过增加线程数量,即使每个线程都在等待,也能有更多的线程在处理不同的任务,从而增加系统的整体吞吐量。
- 并行执行优势:在IO密集型应用中,并行执行多个线程可以减少总体执行时间,因为多个IO操作可以同时进行,不会相互阻塞。
2. 线程CPU时间所占比例高,需要越少线程
- 避免上下文切换开销:当线程主要进行CPU密集型任务时,增加线程数量会增加CPU上下文切换的频率。上下文切换是CPU从一个线程切换到另一个线程时保存和恢复状态的过程,这一过程需要消耗一定的时间。
- 提高CPU利用率:在CPU密集型任务中,每个线程都能有效利用CPU资源。此时,较少的线程就能保持较高的CPU利用率,无需引入额外的线程来减少等待时间。
- 简化系统复杂性:过多的线程在CPU密集型任务中不仅不会带来性能提升,反而会增加系统的复杂性和管理难度。
14. Java中用到的线程调度算法是什么
分时调度模型和抢占式调度模型
- 分时调度模型:指让所有的线程轮流获得cpu的使用权,并且平均分配每个线程占用的cpu的时间片。
- 抢占式调度模型:优先让可运行池中优先级高的线程占用cpu,如果可运行池中的线程优先级相同,就随机选择一个线程,使其占用cpu。