重排序是指编译器和处理器为了提高程序执行效率,在不改变程序语义的前提下,对程序中的指令进行重新排列。
重排序可以分为三种类型:
编译器重排序: 编译器在不改变单线程程序语义的前提下,可以重新安排语句的执行顺序。
指令级并行重排序: 处理器可以使用指令级并行技术,将多条指令重叠执行,如果不存在数据依赖性,处理器可以改变语句对应机器指令的执行顺序。
内存系统重排序: 由于处理器使用缓存和读/写缓冲区,这使得加载和存储操作看上去可能是乱序执行。
JMM 通过以下机制防止重排序导致的问题:
happens-before 规则: JMM 定义了 happens-before 规则,它定义了操作之间的执行顺序,指导编译器和处理器进行合法的重排序。例如,happens-before 规则要求,对 volatile 变量的写操作 happens-before 对该变量的读操作,从而确保对 volatile 变量的修改对其他线程可见。
内存屏障: JMM 在适当的位置插入内存屏障指令,禁止特定类型的重排序。例如,JMM 在 volatile 变量的读/写操作前后插入内存屏障,确保内存操作的顺序。
数据依赖性: JMM 确保编译器和处理器在重排序时遵守数据依赖性,不会改变存在数据依赖关系的两个操作的执行顺序。
JMM 通过以下机制保证内存可见性,确保线程对共享变量的修改对其他线程可见:
volatile 关键字:
确保变量的读/写操作具有原子性: 对于单个 volatile 变量的读/写操作,JMM 会确保它们具有原子性。
禁止重排序:
JMM 会禁止特定类型的编译器和处理器重排序,例如禁止将 volatile 写操作重排序到 volatile 写之后。
引入内存屏障:
JMM 在 volatile 变量的读/写操作前后插入内存屏障,确保内存操作的顺序。
synchronized 关键字:
锁机制: 线程进入同步代码块或方法时,会获取对象的监视器锁,确保只有一个线程能够执行同步代码块或方法。
Java 内存模型(JMM)是一种规范,用于定义在 Java 多线程环境中,线程如何访问共享变量以及如何进行同步操作。
它明确了主内存和线程工作内存之间的关系。主内存存储着所有线程共享的变量,而每个线程都有自己的工作内存,用于缓存从主内存读取的变量值。
JMM 定义了一系列的规则,包括原子性、可见性、有序性等。原子性确保对基本数据类型的单个读/写操作是不可分割的。可见性规定了一个线程对共享变量的修改对于其他线程是可见的。有序性则约束了指令重排序不能影响多线程环境下程序执行的正确性。
链表的插入方式从头插法改成了尾插法,简单来说就是插入时,如果数组位置上已经有元素,1.7将新元素放到数组中, 原始节点作为新节点的后继节点,1.8则是遍历链表将元素放到链表的最后。
数据结构由数组+链表改成了数组+链表或红黑树。
扩容时1.7需要对原数组中的元素进行重新hash定位在新数组的位置,1.8采用更简单的判断逻辑,位置不变或索引+旧容量大小。
在插入时,1.7先判断是够需要扩容,再插入,1.8先插入,插入完成后再判断是够需要扩容。
当length = 2^n时,不同的hash值发生碰撞的概率比较小,这样就会使得数据在table数组中分布较均匀,查询速度也较快。
如果length是奇数,length - 1的二进制最后一位必然是0,而0与任何数进行&运算结果都是0,这样会增加hash碰撞,效率低。