目录
线程数应该设置多少?
多核CPU
线程撕裂者
寄存器:用来保存计算所需要的数据
ALU:计算单元:维护一组寄存器
多线程:一个CPU维护多组寄存器,需要完成线程切换
从CPU到内存之间:工业上最多有三层缓存
一颗CPU里包含多个CPU核
线程可见性
public static void main(String[] args) {
boolean running = true;
Thread t = new Thread(() ->{
while(true){
System.out.print("running");
}
});
t.start();
t.sleep(2);
running = false;
}
即使主线程把running设置为了false,但是线程t并不会立马停止,主要是线程可见性的问题,因为running如果读到核1执行,放到L1缓存中,但是核2又是在其自己的缓存中进行,什么时候重新从内存中读取到核1中,又需要指令的触发。
局部性原理:空间局部性,时间局部性。
工业上读取一次数据是64个字节,当程序需要读取变量x的时候,会一次读取64个字节(cache line)
缓存一致性协议,修改了一个x,通知其他线程读取的这行数据已经失效了
abstract class RingBufferePad{
protected long p1;
protected long p2;
protected long p3;
protected long p4;
protected long p5;
protected long p6;
protected long p7;
}
RingBufferePad(){
}
volatile:对内存的修改就需要通知所有用到这块内存的线程全都再读取一遍
线程:有序性
在什么情况下会输出x = 0, y = 0(不可能;只有x = b, y = a, a = 1, b = 1,也就是两句执行换了顺序) 但是多次运行之后,会出现x = 0, y = 0的情况,因为指令重排序现象的发生,重排序的目的只是为了提高效率,压榨CPU,比如(指令1实现i++,指令2需要从内存中读取数据,但指令2在指令1前面,可以在指令2执行的过程中同时执行指令1,主要是为了提高效率)
指令重排
什么情况下能发生重排序?:两条指令换了顺序以后不影响最终一致性。(注意,这个重排序的优化只是针对单线程而存在的)
线程的as-if-serial:看上去像是序列化(单线)执行的
多线程会产生你不希望看到的结果。
new:申请一块内存(占多少字节,就需要申请多大的内存空间),设置默认值,创建一个对象不是原子性的
invokespecial:特殊调用,调用构造方法
astore:用栈里的t和堆里的对象建立关联
临界区;持有这把锁只允许一个线程执行,而且只能单线程执行,如果临界区太大(锁粒度太粗),严重影响效率。
源码(设计模式,多线程)
DCL写法
//业务逻辑代码省略
if(INSTANCE == null){//先判断
synchronized(Main.class){//再上锁
if(INSTANCE == null){//再判断一次
try{
INSTANCE = new Main();
}
}
}
}
astore和invokespecial发生了交换,t指向一个半初始化的对象。
如何修正这个问题?volatile
synchronized不能保证指令之间的有序性,只是保证线程之间的有序性。synchronized和非synchronized的代码可以同时执行。
如果用了volatile,为什么第一个检查还需要?:提高性能,如果没有第一个检查,来了10000个线程,那么这些线程都会加入锁的竞争,大大降低效率,但如果我加了判断的话,只要单例被创建出来了,不会出现竞争的情况。
锁加在方法上锁定的是this对象,但如果加上代码上,锁定的是class对象
为什么使用volatile可以禁止指令的重排序?
屏障(fence, barrier):一条特殊指令,不允许上下两条指令换顺序,不同的CPU有不同的屏障指令,JVM也有自己的屏障指令。共4条
load:是读;store:写 loadload:表示的是在读读之间加屏障,不可以换顺序。在JVM中,只要不用volatile就不会加屏障,volatile会加屏障。
Jvm中不可重排的8种指令 HappensBefore
volatile的作用:保证可见性和有序性
对于JVM有很多的开源实现
hotspot dragenwell(阿里的实现)