线程池

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
  1. 不保证原子性

  2. 禁止指令重排序

  3. 可见性

    要解释可见性首先要解释JMM(Java内存模型)

    JMM

    img

    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
    */