多线程从头复习到自闭
首先是线程的基本感念:
-
核心数是最重要的,大白话就是有几个核,那么就有多少线程同时能跑。当然并不是一个线程从头跑到尾,而是有一个时间片算法,让每一个线程都有机会跑。但是每次进行上下文切换的时候(就是时间片算法)都会花掉一部分时间,一般都会花掉百分之20的时间。所以多线程有时还没有单线程快。
-
在明确一下线程和进程,进程是cpu分配资源的最小单位,而线程是cpu调度的最小单位。线程咱们想想都有什么呢?
如这个图:
其实这些东西都是要理解的,理解了之后其实都不太会忘记,就如说你在方法里面call了另外一个方法,那么刚开始你push到stack里面的一定是参数嘛,其次就是return address,紧接着就是ebp和esp了,然后就是buffer,当然这个是linux的,但是jvm的其实也都差不多,没什么差距的。 -
线程的调度:
一般有两种,一种是协同式的,就是线程在跑完自己的东西之后,主动去通知其他线程。
另一种是抢占式的,线程运行的时间不可控,全部由kernel来控制。然而java的线程是直接映射到操作系统线程上的,也就是说当前操作系统是抢占式的,那么java线程也是抢占式,同理协作式也是一样。
一些多线程里面的实际运用
1.volatile关键字
这个玩意一定要个CAS原子操作不会混淆,这个是最轻级的同步机制,他只能确保变量的可见性,但是不能保存线程安全。其实volatile就是用了memory barrier的感念,加了一个readbeforewrite之类的内存语义,确保了他能从一级缓存直接刷新到二级缓存。这个其实也挺复杂的。
比如这个图的话就是说,你有一个volatile变量然后 volatile a = 1; 这个时候他就会在写之前插入一个storestore屏障,就是为了让之前的事情不可能重排序到volatile之前,然后storeload,就是在下一次load这个volatile值之前,会刷新到内存,当然也会防止重排序。
这个的话呢就是在读之后如果你想load或者是store的话,一定会让volatile的读在这两个之前,不然值就会被后面改变了嘛如果不防止重排序的话.
- threadlocal的含义
这个东西说白了就是为了给每个线程都有一个变量的副本而来的一个东西。
这个图里面,每一个thread都有一个map,这个map的key的值就是当前的threadLocal,然后值就是每个线程每个值的副本。需要注意的一点就是
这个里面要注意的是这个key是弱引用。是可以被gc回收的,但是如果只是简简单单的吧这个threadlocal = null这样子的话,是会有内存垃圾的,如果当前线程迟迟不结束,那么这个value也会迟迟不会被回收。为什么呢?是因为你的threadlocal变量的key的reference虽然是null,但是你线程里面的map里面有entry然后entry又指向了value而且还是强引用,也就是说,除非当前线程结束,那么永远不会消失。当然写jdk的那群人也想到了这个,所以不管你是在call get()还是set还是remove,里面都会有一个expungeStaleEntry的一个方法帮你去清除。
在这里复习一下四种引用:
- 强引用,尽管要oom也不会被回收
- 软引用,这个引用一般只会用在缓存中,因为只有在内存不足的状况下,才会被回收。
- 弱引用,只要是gc都会被清除掉
- 虚引用,这个其实就是为了让这个要回收的东西在回收之前收到一个通知,就比如说日志。