一、CAS:(Compare and swap)
作用:
在没有锁的状态下,保证多个线程对同一个值的更新
实现:
用户态一直循环直到修改成功
do{}while(修改成功);
ABA问题:(你和女朋友分手,之后她和别的男的在一起,之后又和你复合了)
更新值的过程中,其他线程修改了。
解决方法:
-1.给这个值添加一个版本号,每次被修改时增加版本号,当你修改时判断版本号是否和你当初读取的一样
-2.给这个值添加一个Boolean类型,标记是否被修改过。
本质是如何实现的(与synchronized,volatile底层实现一样)
jdk中unsafe调用native方法。
底层有直接的指令支持CAS:lock cmpxchg(lock表示,当执行cmpxchg指令时,不能被打断)
二、JOL(java object layout)
//pem.xml配置文件修改
<!-- https://mvnrepository.com/artifact/org.openjdk.jol/jol-core -->
<dependency>
<groupId>org.openjdk.jol</groupId>
<artifactId>jol-core</artifactId>
<version>0.9</version>
</dependency>
//打印对象布局
public class ObjectTest {
public static void main(String[] args) {
Object o = new Object();
//查看Object对象布局
System.out.println(ClassLayout.parseInstance(o).toPrintable());
}
}
对象在内存中的内存布局
字段 | 大小 |
---|---|
markword(对象头) | 8字节 |
class pointer(类型指针) | 4字节 |
instance data(实例数据) | 如果对象为空,则为0 |
padding(填充符) | 4字节(补充为8的整数倍) |
java查看默认命令行参数
java -XX:+PrintCommandLineFlags -version
-XX:InitialHeapSize=132883072 -XX:MaxHeapSize=2126129152 -XX:+PrintCommandLineFl
ags -XX:+UseCompressedClassPointers -XX:+UseCompressedOops -XX:-UseLargePagesInd
ividualAllocation -XX:+UseParallelGC
java version "1.8.0_121"
Java(TM) SE Runtime Environment (build 1.8.0_121-b13)
Java HotSpot(TM) 64-Bit Server VM (build 25.121-b13, mixed mode)
UseCompressedClassPointers: 会将8字节指针压缩为4字节(为什么8字节,因为操作系统64位)
下面对象内存大小是多少?
计算大小时,要查看是否开启指针压缩(UseCompressedClassPointers)。
Object o = new Object();
o引用:4字节;object对象16字节;总共20字节
markword(对象头) 8字节(锁标志(3),分代年龄(4bit),hashcode(31bit),unusedGC标记)
class pointer(类型指针) 4字节
instance data(实例数据) 如果对象为空,则为0
padding(填充符) 4字节(补充为8的整数倍)
synchronized锁升级
synchronized上锁会优化。
对象new(无锁) -》偏向锁(默认打开,偏于第一个使用的线程) -》轻量级锁(自旋锁,无锁) -》重量级锁
这些变化记录在markword。
查看对象头
public class ObjectTest {
public static void main(String[] args) {
Object o = new Object();
//查看Object对象布局
System.out.println(ClassLayout.parseInstance(o).toPrintable());
synchronized (o){ //锁定对象
System.out.println(ClassLayout.parseInstance(o).toPrintable());
}
}
}
没加锁之前
加锁之后
锁的信息保存在对象头中
过程:
当一个线程对无锁对象上锁时,直接将自己的线程指针写入对象头(偏向锁)。
当存在线程竞争锁时,撤销偏向锁,线程内部生成lockrecord对象,并将这
个对象指针贴到对象头中,并将hashcode保存到lockrecord(自旋锁,CAS操作)。
解决自旋锁等待问题,一定情况下(自旋时间长,JVM自己控制),升级为重量
级锁(将指向互斥量的指针写入对象头)。
锁降级:
GC时候会出现,没有意思(为啥?因为对象都要删除了)。
锁消除(lock eliminate):
public void add(String str1, String str2){
/**
*StringBuilder与StringBuffer区别:
*StringBuffer线程安全,append方法是同步的
*/
StringBuffer str = new StringBuffer();
str.append(str1).append(str2); //str对象不是共享的资源,jvm会消除锁
}
锁粗化(lock coarsening):
public String test(String str){
int i = 0;
StringBuffer buffer = new StringBuffer();
while(i < 10){
buffer.append(str); //会将这里的锁添加到while之外
i++;
}
return buffer.toString();
}
JIT(just in time compiler,即时编译)
synchronized实现过程
1.java 代码: synchronized
2.JVM指令: monitorenter monitorexit
3.执行过程中自动升级
4.机器指令: lock cmpxchg
三、超线程
一个ALU对应的多个PC寄存器的组合。当cpu执行线程2时,ALU直接去另一些寄存器中执行,不需要加载线程2的环境到线程1使用的寄存器中。
四、缓存
cpu有L1,L2缓存,如果多核,那么多个核共享L3。
五、cache line(64字节)
cpu读取内存的时候按块读取。
缓存行对齐,加速程序执行速度
没有考虑cache line
public class Cache1 {
private static class T{
public volatile long x = 0L;
}
public static T[] arr = new T[2];
static{
arr[0] = new T();
arr[1] = new T();
}
public static void main(String[] args) throws InterruptedException {
Thread t1 = new Thread(()->{
for (long i = 0; i < 1000_0000L; i++){
arr[0].x = i;
}
});
Thread t2 = new Thread(()->{
for (long i = 0; i < 1000_0000L; i++){
arr[1].x = i;
}
});
final long start = System.nanoTime();
t1.start();
t2.start();
t1.join();
t2.join();
System.out.println((System.nanoTime() - start)/100_0000);
}
}
考虑cache line
public class Cache2 {
private static class Padding{
public volatile long a1,a2,a3,a4,a5,a6,a7; //让数据不在同一个cache line中
}
private static class T extends Padding{
public volatile long x = 0L;
}
public static T[] arr = new T[2];
static{
arr[0] = new T();
arr[1] = new T();
}
public static void main(String[] args) throws InterruptedException {
Thread t1 = new Thread(()->{
for (long i = 0; i < 1000_0000L; i++){
arr[0].x = i;
}
});
Thread t2 = new Thread(()->{
for (long i = 0; i < 1000_0000L; i++){
arr[1].x = i;
}
});
final long start = System.nanoTime();
t1.start();
t2.start();
t1.join();
t2.join();
System.out.println((System.nanoTime() - start)/100_0000);
}
}
六、volatile
作用:
- 保证变量在线程间的可见性
- 禁止指令重排序(防止指令半初始化)
创建对象时,jvm执行三步。1:堆上分配对象(并设为默认值),2:调用构造函数初始化,3:将初始化好的对象和栈上的引用对应起来。如果不禁止重排序,会发生2,3重排序导致错误。
底层如何实现数据一致性:
- (MESI(Modified,Exclusive,Shared,Invalid) Cache一致性协议) Inter CPU,其他也有对应的。(一个cache line中一个数据改变,通知其他数据)
- MESI不行,锁总线
系统底层如何保证有序性
- 内存屏障(屏障两边的指令不可以重排序)
- 锁总线
volatile如何解决指令重排序
1.代码中的volatile
2.字节码,加ACC_VOLATILE标志
3.JVM加内存屏障
4.hotspot直接把总线锁了(方便移植)。
Java屏障规则:
读读,读写,写读,写写之间可以加屏障
JVM实现细节:
写操作之前和之后加屏障,读操作之前和之后加屏障
七、强软,弱虚引用:
强:栈上的对象引用和堆中内存对象对应,当栈上对象为空时,堆中对象被回收
Object ob = new Object();
ob = null;
软:和强引用类似,但是堆内存对象中又包含一个对象,他们之间是弱引用。
作用:
常常用于缓存中。
SoftReference<byte[]> m = new SoftReference<>(new byte[100102410]);
m.get(); //获取对象地
System.gc(); //没有释放回收
如何释放:当堆内存空间不足时,会自动释放。
弱:类似软引用,但是即使引用在也可以直接回收。一次性使用
作用:
防止内存泄漏。Thread Local使用这个技术。
WeakReference<byte[]> m = new WeakReference<>(new byte[100102410]);
System.gc(); //释放回收
虚:
作用:
管理堆外内存,JVM使用堆外内存(zero copy)
当某个对象关联到堆外内存的时候,这个对象被回收时,堆外内存也要被回收。
PhantomReference<byte[]> m = new PhantomReference<>(new byte[100102410],QUEUE);
m.get(); //获取对象地,但是返回null