线程池
线程池
synchronized与lock区别
锁
偏向锁
- 解释:
轻锁
重锁
解释:阻塞态内核申请的锁
优势:能够冻结其他处在自旋的锁
劣势:从用户态变成内核态需要消耗大量的资源,没事就不要烦我们的内核老大哥,他很忙的,而且锁的数量也是有限制的。
常用
- sychornized
- lock
名词解释
首先要解释这里的三把锁是什么东西,虽然叫三把锁,其中1、2根本谈不上锁,只有3是真正的锁,他会向内核申请一把真正的大锁,锁掉当前正在被使用的资源,冻结掉没有争抢到资源的进程,防止他们过多的占用系统性能。
-
没有竞争的情况,比如现在只有A线程需要这个资源,A线程会在该资源上贴上自己的名字,就像上厕所,不锁门只是在门上贴个A线程专用,此处没有竞争,自然没有强烈的抢占。
-
有竞争的情况,现在B火急火燎的要来抢占这个坑,那么A在上面贴的A专用,B可不会当回事,现在就是有竞争的情况,此时AB为了争夺这个坑位,自然要加上锁,他们会几乎同时向资源发送一个属于自己的进程ID,资源收到的第一份ID的所有者,现在则拿到了抢占权利,比如B拿到了,那他就可以大大方方的去厕所,A没拿到,就会在外面急的团团转,也就是自旋锁,比如通过while循环就可以写出自旋锁,所以这根本算不上一个锁,但他也确确实实能达到锁的一些效果,所以称为轻量锁,他最大的问题就是在高并发的时候,比如来他10000条线程,只有线程1抢到了资源剩下的9999条线程都在自旋,他们并没有结束进程,而是一直在等待线程1使用完资源,这会造成宕机。
-
竞争比较激烈的情况(高消耗系统性能):所以我们这个时候必须需要一把能够冻结其他处在自旋的锁,而从内核申请的synchronized锁就有这个功能,在第一个线程争抢到资源后,之后的线程都会进入队列状态,并且处于冻结状态,也就是所谓的阻塞态。
-
synchronized这么好那么为什么不直接用synchronize?
从用户态变成内核态需要消耗大量的资源,没事就不要烦我们的内核老大哥,他很忙的,而且锁的数量也是有限制的。
CAS
-
先匹配再交换:两个线程修改同一个变量,number初始值为1,比如A线程欲修改变量number,将其增加1,A线程会先获得一份number的初始值,在改变这个复制的初始值前,他会回过头看看,number的值是否被其他的线程如B给修改了,如果修改了,那么复制的初始值就没有意义了,他就会重新获取内存中的number新值,这步就是匹配,如果回头望了望发现没有被修改,那么就将复制的值改为number,并且赋值到内存中number,完成值的修改
-
如果在比较的过程中后,number的值却被B线程修改了,那么A线程之前复制的初始值就没有了意义,这是不是CAS的漏洞?
实际上在底层汇编代码中有用到LOCK,这个指令会限制当前cpu操作的资源,不能被其他线程修改,只要过了匹配就能保证没有其他线程能够修改这个值
ABA
- cas里面著名的ABA情况,如果number被B线程修改为了2,却又被C线程修改回了1,从A到B再到A,那么这个1已经不是之前的那个1了,这个因该怎么解决呢?
可以增加版本号,时间戳
volatile
- volatile 不保证原子性 指令重排序
- 是Java里提供的一种轻量级的同步机制,乞丐版的synchronized
-
不保证原子性
-
禁止指令重排序
-
可见性
要解释可见性首先要解释JMM(Java内存模型)
JMM
JVM实例是线程,每开一条线程,便会在内存中开启一块栈空间(工作内存)
内存这里我们将其分为栈空间、主内存(内存条8G 16G那种)
我们创建的对象都是存放在主内存里,这里也可以称为共享内存
比如Demo这个对象(里面包含最简单的number变量初始值为0),我们创建Demo对象便会在主内存中创建一个number变量
而我们现在要开启两条线程,开启时会将共享内存(主内存)里的number变量复制一份到栈空间里
如果我们第一条线程要将number修改为60
过程是这样的,首先在栈空间将值改为60,再将栈空间的60覆盖掉主内存中的0
而此时第二条线程获取的number依旧为0,并不是最新修改完的60
什么叫做不可见性?一个线程修改完主内存的值,另一个线程却不知道主内存的值已经修改了
而volatile便具有可见性,如果我们将Demo里的原来的int number = 0;改成volatile int number = 0;
如果第一条修改了主内存的number值,由于volatile的可见性,会及时通知其他线程,主内存中的number已经被修改了
如果验证这个所谓的可见性?我们写个demo
import java.util.concurrent.TimeUnit; /** * 这个Demo一共有三条线程 * AAA、main、GC垃圾回收(这个我们不看) * DateBase变量number初始值为0 * 通过名为AAA将number值修改为60 * AAA线程特意加了sleep 3秒,这个过程是为了让main获取到主内存中的number值将未修改的number = 0 复制到main线程的栈空间里 * 此时main线程栈空间里的number为0,所以逃不出循环程序一直没结束 */ class DataBase { int number = 0; //为加入volatile public void numberTo60() { this.number = 60; } } class demo02 { public static void main(String[] args) { DataBase dataBase = new DataBase(); //Thread(()->{}," ").start(); new Thread(() -> { System.out.println(Thread.currentThread().getName() + "\t come in"); try { TimeUnit.SECONDS.sleep(3); } catch (InterruptedException e) { e.printStackTrace(); } dataBase.numberTo60(); System.out.println(Thread.currentThread().getName() + "\t updated number value:" + dataBase.number); }, "AAA").start(); while (dataBase.number == 0) { } System.out.println(Thread.currentThread().getName() + "\t mission is over"); } } /** * AAA come in * AAA updated number value:60 * 程序未结束进入死循环 */ import java.util.concurrent.TimeUnit; /** * 加入volatile * AAA线程修改完主内存中的number变量会及时通知其他线程 * 这就是可见性 * 最终main跳过while循环 * 程序结束 */ class DataBase { volatile int number = 0; //加入volatile public void numberTo60() { this.number = 60; } } class demo02 { public static void main(String[] args) { DataBase dataBase = new DataBase(); //Thread(()->{}," ").start(); new Thread(() -> { System.out.println(Thread.currentThread().getName() + "\t come in"); try { TimeUnit.SECONDS.sleep(3); } catch (InterruptedException e) { e.printStackTrace(); } dataBase.numberTo60(); System.out.println(Thread.currentThread().getName() + "\t updated number value:" + dataBase.number); }, "AAA").start(); while (dataBase.number == 0) { } System.out.println(Thread.currentThread().getName() + "\t mission is over"); } } /** * AAA come in * AAA updated number value:60 * main mission is over * Process finished with exit code 0 */