面试记录
JVM线程属于用户态还是内核态
当进程运行在ring3级别时为用户态,ring0级别时为内核态
有些操作需要有内核权限才能进行,那么有三种由用户态切换到内核态的情况:
- 系统调用:操作系统封装内核指令,统一管理硬件资源,然后向用户程序提供系统服务,用户程序进行系统调用,操作系统进行检查确保安全然后再进行相应的资源访问操作。比如malloc(),print()调用write()系统输出字符串
- 异常事件:当cpu正在运行用户态程序,发生不可预知的异常事件,就会转用户态,比如缺页中断。
- 外围设备的中断:当外围设备完成请求就会向CPU发出中断信号,此时cpu暂停下一条要执行的指令,去执行中断信号所对应的程序
相同点和不同点:都是中断,但是系统调用是主动,其他都是被动
用户态和内核态线程的映射关系
一对一(内核线程实现)
程序使用轻量级进程和内核线程产生映射
缺点:轻量级进程的数量有限制,执行效率低
多对一(用户线程实现)
优点:用户线程数量几乎无限制,执行效率高
缺点:一个用户线程阻塞,其他线程也会阻塞
多对多
UT 用户态线程 LWP 轻量级线程 KLT 内核态线程
优点:
- 一个用户线程的阻塞不会导致所有线程的阻塞,因为此时还有别的内核线程被调度来执行。
- 多对多模型对用户线程的数量没有限制。
- 在多处理器的操作系统中,多对多模型的线程也能得到一定的性能提升,但提升的幅度不如一对一模型的高。在现在流行的操作系统中,大都采用多对多的模型。
用户态线程:切换代价小,高并发但是容易阻塞
内核态线程:处理能力高,切换代价大
Java线程的实现
虚拟机规范中并没有限定java线程需要使用哪种线程模型,要根据不同的平台来说,但是无论使用哪种线程模型,java程序的编码和运行都是没有差异的
Java线程调度
线程调度有两种:协同调度和抢占调度
协同:自己分配时间,自己切换
抢占:系统分配时间,系统决定线程的切换
java线程采用抢占调度
java线程的6种状态
新建———-运行———无限期等待——–限期等待———阻塞——结束
操作系统线程的几种状态
新建———就绪————等待————运行———–结束
java和操作系统的线程对应关系
1.2之前(绿色线程 1:N),程序员为jvm开发了一个线程调度内核,映射到操作系统层面就是用户态线程;
1.2(1:1)之后,jvmU型安泽了操作系统原生线程模型,映射到操作系统层面就是内核态线程,通过系统调用,将程序的线程交给了操作系统内核进行调度。
通过创建过程来理解
Java的Thread对象:仅仅是一个Java对象
JVM的JavaThread对象:连接着java的Thread对象与OS对象
JVM的OSThread对象:一个工具类,对OS线程API进行了功能性封装
流程图:
JVM_StartThread核心做了两件事情:
1.创建JavaThread对象
(1) 设置jvm执行run方法的跳板
(2) 调用os::create_thread创建OSThread对象及操作系统线程完成三者的关联
os::create_thread做了一下这些:
创建OSThread对象,将JavaThread对象与OSThread对象进行关联
线程库
为开发人员提供创建和管理线程的一套API
三个主要的线程库:
1)POSIX Pthreads:可以作为用户或内核库提供,作为 POSIX 标准的扩展
2)Win32 线程:用于 Window 操作系统的内核级线程库
3)Java 线程:Java 线程 API 通常采用宿主系统的线程库来实现,也就是说在 Win 系统上,Java 线程 API 通常采用 Win API 来实现,在 UNIX 类系统上,采用 Pthread 来实现。
操作系统对于锁的实现
在硬件层面,CPU提供了原子操作、关中断(可解决单核情况下两个线程同时获得锁)、锁内存总线的机制(解决多核情况下两个线程同时获得锁);OS基于这几个CPU硬件机制,就能够实现锁;再基于锁,就能够实现各种各样的同步机制(信号量、消息、Barrier等等等等)
synchronized的底层实现
是通过对象内部的一个监视器锁(monitor)实现的,监视器锁有时通过操作系统的互斥锁来实现的,而且现在主流的java虚拟机实现中,java的线程是映射到操作系统原生的内核线程中的,那么线程的阻塞或唤醒,就涉及到用户态和内核态的转换中,所以是重量级锁。
是一种块结构的同步语法,经过javac反编译之后,会在同步块的前后分别生成monitorenter和monitorexit两个字节码指令,这两个指令都需要一个reference类型的参数来指明加锁解锁的对象,如果synchronized明确了对象参数。就以这个对象的引用作为reference,如果没有指定,那就根据修饰的方法类型,来决定是区代码所在的对象实例还是相应的class对象。
反射的性能优化
两个地方导致性能差:getMethod和invoke,反射是一个解释操作,临时告诉jvm应该做什么
优化思路1:缓存Method,不重复调用getMethod
优化思路2:借助ASM框架,使用reflectAsm,让invoke变成直接调用
借反射的getDeclaredMethods获取目标类的所有方法,然后动态生成一个继承于MethodAccess 的子类SimpleBeanMethodAccess,动态生成一个Class文件并load到JVM中。
SimpleBeanMethodAccess中所有方法名建立index索引,index跟方法名是映射的,根据方法名获得index,SimpleBeanMethodAccess内部建立的switch直接分发执行相应的代码,这样methodAccess.invoke的时候,实际上是直接调用。
hashmap在jdk1.7的死循环
多线程头插法造成的,线程2已经完成扩容散列,链表变成了倒序,线程1再进行扩容,将倒序链表变成了一个正序链表,从而形成环形链表,在使用get方法时造成死循环。
常用集合
ArrayList
首先有三种构造方法(有参,无参,指定集合参数组成的列表)
主要就是三个方法:
1.ensureCapacityInternal得到最小扩容量,并进行扩容
2.ensureExplicitCapacity//判断是否需要扩容,如果最小扩容量大于数组现在的长度就调用grow方法
3.grow 进行位运算,扩容1.5倍,并进行判断是否超出数组的最大容量。
始化数据量为0,add时变为10,当 要 add 进第 1 个元素时,minCapacity 为 1,在 ensureCapacityInternal的Math.max()方法比较后,minCapacity 为 10 ,
最好在 add 大量元素之前用 ensureCapacity 方法(因为是public修饰),以减少增量重新分配的次数
Set Map