- 类加载及执行子系统
- tomcat
- 在原来的层级上 tomcat扩展了加载器层级
- common加载的类类库tomcat和所有web应用程序共同使用,要加载的类放到common目录下
- catalina加载的类库tomcat自己使用,所有web应用程序无法使用,要加载的类放在server目录下
- shared加载的类库tomcat不能使用,所有web应用程序可以使用,要加载的类放到shared目录下
- webapp加载的类库tomcat不能使用,其他的web应用程序也无法使用,仅仅该web程序,要加载的类放到/webApp/WEB-INF目录下
- 每个webApp类加载器对应一个web应用程序,每个jsp文件
- JasperLoader的加载范围仅仅是这个JSP文件所编译出来的那一个Class,它出现的目的就是为了被丢弃:当服务器检测到JSP文件被修改时,会替换掉目前的JasperLoader的实例,并通过再建立一个新的Jsp类加载器来实现JSP文件的HotSwap功能
- 编译器优化
- 编译过程
- 解析和填充符号表的过程
- 词法、语法分析
- 填充符号表,符号表时地址分配的依据
- 插入式注解处理器的注解处理过程
- 分析与字节码生成的过程
- 内存模型
- 对应关系
- 主内存 工作内存
- 堆 栈
- 内存 寄存器和高速缓存
- 原子操作
- lock 作用于主内存的变量,把一个变量标示成某条线程独占的状态
- unlock 作用于主内存的变量,把一个处以锁定状态的变量释放出来,释放后的变量才能被其他线程锁定
- read 作用于主内存的变量,把一个变量的值传输到线程的工作内存中,以便随后的load动作使用
- load 作用于工作内存的变量,把read操作从主内存中得到的变量值放入工作内存的变量副本中,两者只保证顺序,不一定连续
- use 作用于工作内存的变量,把工作内存中一个变量的值传递给执行引擎,每当虚拟机遇到一个需要使用到变量的值的字节码指令时将会执行这个操作
- assign 作用于工作内存的变量,把一个从执行引擎接收到的值赋给工作内存的变量,每当虚拟机遇到一个给变量赋值的字节码指令时执行这个操作
- store 作用于工作内存的变量,把工作内存中一个变量的值送到主内存中,一边随后的write操作使用
- write 作用于主内存的变量,把store操作从工作内存中得到的变量的值放入主内存的变量中
- 操作限定
- 不允许read和load、store和write操作之一单独出现,即不允许一个变量从主内存读取了但工作内存不接受,或者从工作内存发起回写了但主内存不接受的情况出现。
- 不允许一个线程丢弃它的最近的assign操作,即变量在工作内存中改变了之后必须把该变化同步回主内存。
- 不允许一个线程无原因地(没有发生过任何assign操作)把数据从线程的工作内存同步回主内存中。
- 一个新的变量只能在主内存中“诞生”,不允许在工作内存中直接使用一个未被初始化(load或assign)的变量,换句话说,就是对一个变量实施use、store操作之前,必须先执行过了assign和load操作。
- 一个变量在同一个时刻只允许一条线程对其进行lock操作,但lock操作可以被同一条线程重复执行多次,多次执行lock后,只有执行相同次数的unlock操作,变量才会被解锁。
- 如果对一个变量执行lock操作,那将会清空工作内存中此变量的值,在执行引擎使用这个变量前,需要重新执行load或assign操作初始化变量的值。
- 如果一个变量事先没有被lock操作锁定,那就不允许对它执行unlock操作,也不允许去unlock一个被其他线程锁定住的变量。
- 对一个变量执行unlock操作之前,必须先把此变量同步回主内存中(执行store、write操作)。
- volatile
- 可见性:volatile变量对所有线程是立即可见的,对volatile变量所有的写操作都能立刻反应到其他线程之中,只保证了可见性,但同一时间都对该值做了修改的话还是会冲突,也就是说其他线程的修改还来不及在你的操作执行之前通知到你的话,那么修改就会冲突
- 因此只适用于:
- 运算结果并不依赖变量的当前值,或者能够确保只有单一的线程修改变量的值。
- 变量不需要与其他的状态变量共同参与不变约束。
- 即所有操作顺序可以打乱
- 另外可以用来禁止指令重排序,因为指令重排序仅保证重排序后线程内结果一致,但若是并行时,重排序会导致某些出乎意料的问题
- 实现:编译时,在对volatile变量的赋值操作指令后加个加锁的空操作了强制把修改立即写回主内存,同时清空了其他工作内存的相应副本,即为内存可见性,另外修改同步回内存,意味着所有之前的操作都已经执行完成,这样便形成了“指令重排序无法越过内存屏障”的效果了。
- 先行原则
- 程序次序规则:在一个线程内,按照程序代码顺序,书写在前面的操作先行发生于书写在后面的操作。准确地说,应该是控制流顺序而不是程序代码顺序,因为要考虑分支、循环等结构。
- 管程锁定规则:一个unlock操作先行发生于后面对同一个锁的lock操作。这里必须强调的是同一个锁,而“后面”是指时间上的先后顺序。
- volatile变量规则:对一个volatile变量的写操作先行发生于后面对这个变量的读操作,这里的“后面”同样是指时间上的先后顺序。
- 线程启动规则:Thread对象的start()方法先行发生于此线程的每一个动作。
- 线程终止规则:线程中的所有操作都先行发生于对此线程的终止检测,我们可以通过Thread.join()方法结束、Thread.isAlive()的返回值等手段检测到线程已经终止执行。
- 线程中断规则:对线程interrupt()方法的调用先行发生于被中断线程的代码检测到中断事件的发生,可以通过Thread.interrupted()方法检测到是否有中断发生。
- 对象终结规则:一个对象的初始化完成(构造函数执行结束)先行发生于它的finalize()方法的开始。
- 传递性:如果操作A先行发生于操作B,操作B先行发生于操作C,那就可以得出操作A先行发生于操作C的结论。
- 线程安全
- 线程安全级别
- 不可变 如String,枚举,Number的部分子类
- 绝对线程安全 难以达到,我们通常讲的线程安全的类只是但个操作线程安全而已,整体不一定线程安全
- 相对线程安全 通常讲的线程安全
- 线程兼容 对象本身不是线程安全的,但在调用端正确使用同步手段来保证在并发环境线程安全
- 线程对立 无论调用端是否采取了线程同步都无法在多线程环境中并发使用,如suspend和resume,所以被废弃
- 解决方法
- 阻塞同步 悲观锁
- 非阻塞同步 乐观锁,若是用原值作为检测标准,会发生ABA,若另加个版本号的话,大部分情况下ABA问题不会影响程序并发的正确性,如果需要解决ABA问题,改用传统的互斥同步可能会比原子类更高效。
- 无同步方案
- 代码可重入,即可以任意线程切换的代码
- 它不依赖存储在堆上的数据和公用的系统资源、用到的状态量都由参数中传入、不调用非可重入的方法等
- 线程本地存储,数据线程私用,不会出现数据争用的问题
- 锁优化
- 自旋锁 无法得到锁时,让线程执行一个忙循环(自旋),等待一下但又避免放弃处理器的执行时间,看看持有锁的线程会不会释放锁,避免直接被挂起,因为挂起和恢复都要转入内核态中完成,性能消耗多(保存和恢复现场,切换地址映射),固定次数自旋或自适应次数
- 锁消除 通过逃逸分析,发现没有数据共享,这段代码中,堆上的所有数据都不会逃逸出去从而被其他线程访问到,相当于栈上数据
- 锁粗化 合并连续的多个对相同对象的反复加锁和解锁
- 轻量级锁 在无竞争的情况下使用CAS操作去消除同步使用的互斥量
- 偏向锁 在无竞争的情况下把整个同步都消除掉,连CAS操作都不做了。
纯粹地读书,只为好奇心