一.进程,线程,协程
进程是操作系统分配资源的最小单位;
线程是程序执行得最小单位;
协程是更轻量级,资源占用更小的线程;比方说100万个线程占大概1T,100万个协程占1G。
线程该如何创建呢
- new Thread(()->{}); thread.start()
- 实现 Runnable;
- 实现Callable,带有返回值,使用的时候submit;
- 线程池。
thread.start的运行过程是这样的
线程的状态如图
那么既然是多线程,就会存在安全问题,那么该如何保证线程的安全性呢
二. 锁
1.CAS
CPU速度是远远大于硬盘的速度,大概100万倍,所以其中就得加缓存来缓解这种尴尬
java内存模型
如图所示,可以看的出来,当多个线程操作一个数据时,是把数据读到自己的内存中进行操作的,那么这种闭门造车的方式来操作数据是肯定会有数据安全的问题的,如何解决这种问题呢?第一种就是锁先看一下CAS
cas顾名思义,compare and swap,比较并替换。但是这样会产生ABA的问题,意思就是你女朋友跟你分了又复合了,但是期间又谈了几个男朋友,但是你不知道。如何解决呢,记录一下,多谈了一个就加一。cas的就整个乐观锁,版本号加一的那种。自己跟自己玩,是一种轻量级锁。
2.Synchronize
跟上边的锁不一样,这个锁是一种要麻烦操作系统老大哥的操作,等老大哥闲下来了,会给他一把锁,所以,对比于上边性能低,是一把重量级锁。
synchronize1.6之后呢,做了一个优化,是存在一个锁升级的过程的即 无锁->偏向锁->轻量级锁->重量级锁,如图
介绍几个概念
适应性自旋锁:获取成功的下次自旋时间就长一点,没获取到的下次就短一点
锁消除: 如果不存在多线程竞争情况下的加锁,将会消除锁,判断依据是变量逃逸
锁粗化: 例如循环加锁,会将锁从循环里拿出来放在外边
再来说一下原理吧
1. 代码层面,Synchronize
2. 优化层面,锁升级 无锁->偏向锁->轻量级锁->重量级锁
3. 字节码层面,monitorenter,monitorexit
4. native层面,lock cmpex
5. 硬件层面,锁住了北桥信号
3.基于AQS实现的锁
ReentrantLock,ReentrantReadWriteLock,Condition
AQS底层其实就是一个双向链表,根据status的值来判断是否当前代码被加锁,如果等于0,就获得锁,大于0, 是重入锁;
如果是公平锁,就放入队列中,如果是非公平锁就tryLock一下,如果失败了再放入队列,其实就是一个插队的区别。
属于一种轻量级锁。锁粒度更小。
CyclicBarrier和CountDownLatch的区别是,CountDownLatch是1个或者多个线程等其他的线程完成;CyclicBarrier是N个线程互相等待,CountDownLatch计数器不能重置,CyclicBarrier计数器可以重置。
接下来介绍另一种解决数据可见性的方案
三 volatile
volatile就是让自己内存中指定的数据失效,不得不从主存中拿数据,这样就可以了,但是缺点也很显然,一个是性能低,一个是多线程下无法保证数据安全。
操作系统层面来说原理的话,由于cpu,内存,硬盘速度差别特大,数据不一致,所以采用的协议是MESI缓存一致性协议。
除了保证数据可见性,还能防止重排序。重排序会导致什么问题呢,咱们比方说
防止重排序呢实现原理就是加了一个内存屏障,类似的还有
比如happen-before原则如下
- 依赖情况下,顺序执行
- 传递性原则
- Thread.start
- Thread.join
- interrupt
- synchronized
- volatile
- finanzied
as-if-serial的意思是不管咋重排序都不能对结果产生影响。
四 ThreadLocal原理
ThreadLocal是一个线程传值的工具。
说一下java 的四种引用
1.强引用 : ThreadLocal t = new ThreadLocal();宁愿抛出OOM也不回收这个对象
2.软引用 : 内存不够了就会回收这个对象
3.弱引用 : 垃圾回收器一看到它就会回收
4.虚引用 : 比弱引用更弱,幽灵引用,一般被用来管理堆外内存
如图,threadlocal对象有两个引用,一个是new的强引用,一个是entry的弱引用,当强引用不存在时,GC看到弱引用会干掉他,导致key为null;而map的生命周期跟thread是一样的,所以无法回收data导致内存泄漏,一般不用的时候在调用一下remove方法