JAVA
JVM,JRE,JDK
- JVM
类加载子系统
运行时数据区
栈(线程)
- 栈的先进后出符合函数的调用顺序
- 局部变量表:存储函数体内的局部变量。
- 操作数栈:临时存储操作数,例如a=1,先将1压入操作数栈中,局部变量表中添加a变量,将操作数栈栈顶元素赋值给a。
- 程序计数器:记录下条指令的位置,每个线程都会有程序计数器,为了上下文切换能够找到加载的位置。
- 本地方法栈:native修饰的方法存储
- 堆:
- full GC :老年区满了做full GC,范围:整个堆和方法区。
- JVM调优
- 分析核心业务流程。
- 调整年轻代和老年代内存大小。
- 尽量减少Full GC
- 执行引擎
- JRE
- java运行时环境
- 包括JVM和java内库:math,Logging等。
- JDK
- 包括JRE,命令行工具javac等,IDE等。
JMM-java线程内存模型
JMM数据原子操作
volatile
并发编程三大特性:可见性,原子性,有序性
volatile保证可见性和有序性,但原子性需要借助synchronized这样的锁机制
Volatile可见性
- volatile作用
- 避免指令重排(单例模式)
- 保持内存可见性
- 多线程测试代码
public class VolatileTest { private static volatile boolean initFlag=false; public static void main(String[] args) { new Thread(new Runnable() { @Override public void run() { System.out.println("Wailting ..."); while (!initFlag){ } System.out.println("=========success"); } }).start(); new Thread(new Runnable() { @Override public void run() { prepareDate(); } }).start(); } private static void prepareDate() { System.out.println("prepareing ..."); initFlag=true; System.out.println("prepare end ..."); } }
- 代码执行流程
- volatile可见性底层实现
- 底层实现主要是通过汇编Lock前缀指令。他会锁定这块内存区域的缓存,并写回主内存
- IA-32对Lock的解释
- 将当前处理器的缓存行的数据立即写回到系统内存
- 写回操作会引起其他其他CPU里缓存该内存地址的数据无效(MESI协议)
- volatile作用
volatile 原子性问题
- 多线程对
num=0
进行num++
操作。会出现某次操作丢失的情况。结果小于等于正常情况
public class VolatileTest2 { public static volatile int num=0; public static void increase(){ num++; } //创建10个线程,每个线程进行1000次num++ public static void main(String[] args) throws InterruptedException { Thread[] threads=new Thread[10]; for (int i =0;i<threads.length;i++){ threads[i]=new Thread(new Runnable() { @Override public void run() { for( int i=0;i<1000;i++){ increase(); } } }); threads[i].start(); } for (Thread t:threads) { t.join(); } System.out.println(num); } } // <=10000
- 多线程对
HashMap
数组+链表+红黑数(java8加入)
- 数组方便按下标查询,链表避免了数组插入和删除
put操作
- 如果key为null,放到Entry[0]的位置
- 对key值hash计算,在对hash值取模确定数组中的index
- 如果index位置发送冲突
- java7,头插法插入链表
// 方法一,jdk1.8 & jdk1.7都有: static final int hash(Object key) { int h; return (key == null) ? 0 : (h = key.hashCode()) ^ (h >>> 16); } // 方法二,jdk1.7有,jdk1.8没有这个方法,但是实现原理一样的: static int indexFor(int h, int length) { return h & (length-1); //位运算效率高 }123456789
- 这里的Hash算法本质上就是三步:
(1) 取key的hashCode值,h = key.hashCode();
(2) 高位参与运算,h ^ (h >>> 16);
(3) 取模运算,h & (length-1)。
扩容机制
当HashMap中的元素越来越多的时候,hash冲突的几率也就越来越高,因为数组的长度是固定的。所以为了提高查询的效率,就要对HashMap的数组进行扩容。
HashMap的容量由一下几个值决定:
int threshold; // 扩容阈值 final float loadFactor; // 负载因子 transient int modCount; // 出现线程问题时,负责及时抛异常 transient int size; // HashMap中实际存在的Node数量 HashMap的容量size乘以负载因子[默认0.75] = threshold; // threshold即为开始扩容的临界值
当HashMap中的元素个数超过数组threshold时,就会进行数组扩容, 数组默认大小为16,那么当HashMap中元素个数超过16*0.75=12(这个值就是代码中的threshold值,也叫做临界值)的时候,就把数组的大小扩大一倍,然后重新计算每个元素在数组中的位置。
HashMap不是无限扩容的,当达到了实现预定的MAXIMUM_CAPACITY,就不再进行扩容。
负载因子为什么是0.75?
- 空间与时间的均衡。
初始容量是2的指数次幂?
- 方便取模和扩容,同时减少冲突。
java7线程不安全?
- 数据丢失
- 死锁,多线程rehash时可能会发生死锁
- java8取消了rehash的操作
- 添加了loHead,loTail和hiHead,hiTail。
链表转红黑数
- 链表长度达到8时,先判断数组长度是否大于64,大于:转变红黑树,小于:扩容
线程底层原理
- ULT,KLT(java用)
- 线程生命周期
- 线程池的优势
- 重用存在的线程,减少线程的创建,消亡的开销
- 提高响应速度,任务到达时不需要创建线程就可以执行。
- 提高可管理性。