zoukankan      html  css  js  c++  java
  • 14_JUC

    前言

    本次内容全部来自

    环境准备

    1、打开IDEA,新建一个maven项目

    2、查看

    1585916550309

    1585916563287

    1585916614172

    什么是JUC

    java.util.concurrent简称JUC,也就是下面的三个包

    1585917338720


    为什么要学习JUC

    1. JUC面试高频问,所以一定要掌握

    2. 之前使用Thread,非常普通的线程类

      Runnable,没有返回类型,效率相比于Callable相对较低

    3. 之前也学过Lock锁,但是没有深入的系统地学习JUC

    面试的时候:单例模式,排序算法,生产者和消费者,死锁


    线程和进程

    一句话说明线程和进程?

    1个进程至少包含1个线程

    JAVA默认有几个线程?

    2个:Main+GC垃圾回收

    JAVA真的可以开启线程吗?

    JAVA不可以开启线程,如果我们调用底层,我们会发现:

    public synchronized void start() {
            if (threadStatus != 0)	throw new IllegalThreadStateException();
        			group.add(this);
            boolean started = false;
            try {
                start0();
                started = true;
            } finally {
                try {
                    if (!started) {
                        group.threadStartFailed(this);
                    }
                } catch (Throwable ignore) {
                    /* do nothing. If start0 threw a Throwable then
                      it will be passed up the call stack */
                }
            }
        }
    

    这个start最后调用了一个很诡异的方法start0(),这个其实是调用的本地方法,所以JAVA是没有资格直接操作硬件的

    并发和并行

    • 并发:多个线程操作一个资源
      • CPU一核,但是要模拟出来多线程的操作,只能通过快速的交替来模拟这样的效果
      • 一种假象
    • 并行:多个人一起行走
      • 多核CPU下面,多个线程可以同时执行

    并发编程的本质:充分利用CPU的资源

    public class JUC {
        public static void main(String[] args) {
            //获取cpu的核数
            System.out.println(Runtime.getRuntime().availableProcessors());
        }
    }
    

    获得CPU的核数

    线程有几个状态?

    public enum State {
    
            NEW,		//新生
    
            RUNNABLE,	//运行
    
            BLOCKED,	//阻塞
    
            WAITING,	//等待,永远等待
    
            TIMED_WAITING,//超时等待,过时不候
    
            TERMINATED;	//终止
        }
    

    6个

    wait和sleep的区别?

    1. 来自不同的类
      • wait来自Object
      • sleep来自Thread
    2. 锁的释放
      • wait会释放锁
      • sleep不会释放锁
    3. 使用范围
      • wait:必须要在同步代码块中
      • sleep:可以在任何地方使用
    4. 是否需要捕获异常
      • wait和sleep都要捕获中断异常:InterruptedException

    Lock锁

    我们先聊传统:synchronized

    public class SaleTicketDemo {
        public static void main(String[] args) {
            new Thread(new MyThread()).start();
        }
    }
    
    class MyThread implements Runnable{
        @Override
        public void run() {}
    }
    

    以前我们就是这么用的,但是今天开始要推翻

    真正的多线程开发中

    线程就是一个单独的资源类,没有任何附属的操作

    public class SaleTicketDemo {
        public static void main(String[] args) {
            //多线程
    
            //并发:多个线程操作同一个资源类
            Ticket ticket = new Ticket();
    
            new Thread(()->{
                for (int i = 0; i < 40; i++) {
                    ticket.sale();
                }
            },"A").start();        
            
            new Thread(()->{
                for (int i = 0; i < 40; i++) {
                    ticket.sale();
                }
            },"B").start();        
            
            new Thread(()->{
                for (int i = 0; i < 40; i++) {
                    ticket.sale();
                }
            },"C").start();
    
        }
    }
    
    //这就是资源类,对这个类进行操作叫做真正的OOP编程
    //假如在这里实现一个Runnable,这就不叫面向对象了,就变成了一个线程类了,而且耦合性高
    class Ticket{
        //属性和方法
        //属性
        private int number = 50;
        //方法,使用同步方法:synchronized
        public synchronized void sale(){
            if (number>0){
                System.out.println(Thread.currentThread().getName() + "-->" + (number--) + ",剩余" + number);
            }
        }
    }
    

    上面是传统方式简单回顾

    下面要用JUC

    java.util.concurrent.locks

    我们看到有三个接口,其中有一个Lock接口

    • Interface Lock,三个实现类
      • ReentrantLock:可重用锁(常用)
      • ReadLock:读锁
      • WriteLock:写锁

    我们查看ReentrantLock,我们发现这样一段源码

        public ReentrantLock() {
            sync = new NonfairSync();	//new一个新的非公平锁
        }
    
        public ReentrantLock(boolean fair) {	//ReentrantLock的构造参数
            sync = fair ? new FairSync() : new NonfairSync();
        }
    //假如为真,则为公平锁,否则为非公平锁
    

    公平锁和非公平锁

    • 公平锁:十分公平,必须先来后到
    • 非公平锁:十分不公平,可以插队(默认)

    默认为非公平锁,这是因为:

    假如有两个线程,A执行3秒,B执行3小时,B先来的

    假如为公平锁,那么A就要等3个小时

    我们使用一下Lock锁,来举一个例子:

    import java.util.concurrent.locks.Lock;
    import java.util.concurrent.locks.ReentrantLock;
    
    public class SaleTicketDemo2 {
        public static void main(String[] args) {
    
            Ticket ticket = new Ticket();
    
            new Thread(()->{ for (int i = 0; i < 40; i++) ticket.sale(); },"A").start();
            
            new Thread(()->{ for (int i = 0; i < 40; i++) ticket.sale(); },"B").start();
            
            new Thread(()->{ for (int i = 0; i < 40; i++) ticket.sale(); },"C").start();
    
        }
    }
    
    //Lock
    class Ticket2{
    
        private int number = 50;
    
        Lock lock = new ReentrantLock();
    
        public void sale(){
            lock.lock();
    
            try {
                if (number>0){
                    System.out.println(Thread.currentThread().getName() + "-->" + (number--) + ",剩余" + number);
                }
            } finally {
                lock.unlock();//在finally里面解锁
            }
        }
    }
    

    Lock和Synchronized的区别

    1、Synchronized是内置的Java关键字,而Lock是一个Java类

    2、Synchronized可重入锁,不可以中断的,非公平锁。Lock,可重入锁,可以判断锁是否中断,可以自己设置为公平锁或者非公平锁

    3、Synchronized线程1(获得锁),线程2(死死地等待)。Lock就不一定会等待下去:lock.tryLock()尝试获取锁,获取不到就算了

    4、Synchronized无法判断获取锁的状态,Lock可以判断是否获取到了锁

    5、Synchronized会自动释放锁,而Lock必须手动释放。不释放可能会导致死锁

    6、Synchronized适合锁少量的代码同步问题,Lock适合大量的同步代码

    可重入锁的意思是,现在有两把锁,一外一里,拿到外面的锁就拿到了里面的锁,这也叫做递归锁

    生产者和消费者问题

    这是个大有门道的问题

    老版方式

    线程同步

    老版方式:Synchronized

    //等待,业务,通知:判断是否要进行等待,如果不等待就干活,干完活就通知另一方
    class Data{//数字,资源类
    
        private int number = 0;
    
        //+1,对应生产者
        public synchronized void increment() throws InterruptedException {
            if (number!=0){
                //等待
                this.wait();
            }
            number++;
            System.out.println(Thread.currentThread().getName() + "=>" + number);
            //通知其他线程
            this.notifyAll();
        }
    
        //-1,对应消费者
        public synchronized void decrement() throws InterruptedException {
            if (number==0){
                //等待
                this.wait();
            }
            number--;
            System.out.println(Thread.currentThread().getName() + "=>" + number);
            //通知其他线程
            this.notifyAll();
        }
    }
    
    /*
        线程之间的通信问题:生产者和消费者问题
        线程交替执行,A和B操作同一个变量,num
        A num+1
        B num-1
     */
    public class A {
        public static void main(String[] args) {
            Data data = new Data();
    
            new Thread(()->{
                for (int i = 0; i < 10; i++) {
                    try {
                        data.increment();
                    } catch (InterruptedException e) {
                        e.printStackTrace();
                    }
                }
            },"A").start();
    
            new Thread(()->{
                for (int i = 0; i < 10; i++) {
                    try {
                        data.decrement();
                    } catch (InterruptedException e) {
                        e.printStackTrace();
                    }
                }
            },"B").start();
            
    /*
        A=>1    B=>0    A=>1    B=>0    A=>1    B=>0
        A=>1    B=>0    A=>1    B=>0    A=>1    B=>0
        A=>1    B=>0    A=>1    B=>0    A=>1    B=>0
        A=>1    B=>0
    */
    
        }
    }
    

    虚假唤醒

    问题来了:现在只有两个线程,但是我们加到了四个线程,甚至八个线程,结果呢?

    我们让A和C加,B和D减

    public class A {
        public static void main(String[] args) {
            Data data = new Data();
    
            new Thread(()->{
                for (int i = 0; i < 5; i++) {
                    try {
                        data.increment();
                    } catch (InterruptedException e) {
                        e.printStackTrace();
                    }
                }
            },"A").start();
    
            new Thread(()->{
                for (int i = 0; i < 5; i++) {
                    try {
                        data.decrement();
                    } catch (InterruptedException e) {
                        e.printStackTrace();
                    }
                }
            },"B").start();
    
            new Thread(()->{
                for (int i = 0; i < 5; i++) {
                    try {
                        data.increment();
                    } catch (InterruptedException e) {
                        e.printStackTrace();
                    }
                }
            },"C").start();
    
            new Thread(()->{
                for (int i = 0; i < 5; i++) {
                    try {
                        data.decrement();
                    } catch (InterruptedException e) {
                        e.printStackTrace();
                    }
                }
            },"D").start();
    
            /*
                A=>1    B=>0    C=>1    A=>2    C=>3
                B=>2    B=>1    B=>0    C=>1    A=>2
                C=>3    B=>2    C=>3    A=>4    D=>3
                D=>2    D=>1    D=>0    A=>1    D=>0
             */
    
        }
    }
    
    //等待,业务,通知:判断是否要进行等待,如果不等待就干活,干完活就通知另一方
    class Data{//数字,资源类
    
        private int number = 0;
    
        //+1,对应生产者
        public synchronized void increment() throws InterruptedException {
            if (number!=0){
                //等待
                this.wait();
            }
            number++;
            System.out.println(Thread.currentThread().getName() + "=>" + number);
            //通知其他线程
            this.notifyAll();
        }
    
        //-1,对应消费者
        public synchronized void decrement() throws InterruptedException {
            if (number==0){
                //等待
                this.wait();
            }
            number--;
            System.out.println(Thread.currentThread().getName() + "=>" + number);
            //通知其他线程
            this.notifyAll();
        }
    }
    

    结果来了,同步凉了

    那么为什么会出现这种问题呢?因为用了if判断,有的时候就会出现这种问题,这种问题叫做虚假唤醒

    查看jdk文档:java.lang-->Object-->wait/notify

    1585923695398

    注意点,这里要防止虚假唤醒

    虚假唤醒就是当一个条件满足时,很多线程都被唤醒了,但是只有部分是有用的唤醒,其余的都是无用功

    解决方案:条件判断的时候,把if改为while

    public class A {
        public static void main(String[] args) {
            Data data = new Data();
    
            new Thread(()->{
                for (int i = 0; i < 5; i++) {
                    try {
                        data.increment();
                    } catch (InterruptedException e) {
                        e.printStackTrace();
                    }
                }
            },"A").start();
    
            new Thread(()->{
                for (int i = 0; i < 5; i++) {
                    try {
                        data.decrement();
                    } catch (InterruptedException e) {
                        e.printStackTrace();
                    }
                }
            },"B").start();
    
            new Thread(()->{
                for (int i = 0; i < 5; i++) {
                    try {
                        data.increment();
                    } catch (InterruptedException e) {
                        e.printStackTrace();
                    }
                }
            },"C").start();
    
            new Thread(()->{
                for (int i = 0; i < 5; i++) {
                    try {
                        data.decrement();
                    } catch (InterruptedException e) {
                        e.printStackTrace();
                    }
                }
            },"D").start();
    
    
        }
    }
    
    class Data{
    
        private int number = 0;
    
        public synchronized void increment() throws InterruptedException {
            while (number!=0){  //这里为了防止虚假唤醒,改为使用while
    
                this.wait();
            }
            number++;
            System.out.println(Thread.currentThread().getName() + "=>" + number);
    
            this.notifyAll();
        }
    
    
        public synchronized void decrement() throws InterruptedException {
            while (number==0){  //这里为了防止虚假唤醒,改为使用while
    
                this.wait();
            }
            number--;
            System.out.println(Thread.currentThread().getName() + "=>" + number);
    
            this.notifyAll();
        }
    }
    

    JUC方式

    新老三剑客对应

    传统三剑客:Synchronized,wait,notify

    其中synchronizedLock替换了,那么根据逻辑来讲,其余两个也是有替换的

    java.util.concurrent.locks

    • Condition
    • Lock
    • ReadWriteLock

    那么Condition就是配套的三剑客之一,对应老版的waitnotify

    1585924497345

    从官方文档我们可以看出来,Condition替代了对象的监视器的方法,原来我们使用wait和notify,现在就要使用Condition

    1585924658142

    那么对应关系来了:

    1585924697974

    代码实现一下:

    public class B {
        public static void main(String[] args) {
            Data2 data = new Data2();
    
            new Thread(()->{
                for (int i = 0; i < 5; i++) {
                    try {
                        data.increment();
                    } catch (InterruptedException e) {
                        e.printStackTrace();
                    }
                }
            },"A").start();
    
            new Thread(()->{
                for (int i = 0; i < 5; i++) {
                    try {
                        data.decrement();
                    } catch (InterruptedException e) {
                        e.printStackTrace();
                    }
                }
            },"B").start();
    
            new Thread(()->{
                for (int i = 0; i < 5; i++) {
                    try {
                        data.increment();
                    } catch (InterruptedException e) {
                        e.printStackTrace();
                    }
                }
            },"C").start();
    
            new Thread(()->{
                for (int i = 0; i < 5; i++) {
                    try {
                        data.decrement();
                    } catch (InterruptedException e) {
                        e.printStackTrace();
                    }
                }
            },"D").start();
        }
    }
    
    class Data2{
    
        private int number = 0;
    
        Lock lock = new ReentrantLock();
    
        Condition condition = lock.newCondition();//取代了wait和notify
    
        public void increment() throws InterruptedException {
        	lock.lock();
            try {
                while (number!=0){
                    //等待
                    condition.await();
                }
                number++;
                System.out.println(Thread.currentThread().getName() + "=>" + number);
    
                //通知
                condition.signalAll();
            } finally {
                lock.unlock();
            }
        }
    
    
        public void decrement() throws InterruptedException {
    
    		lock.lock();
            try {
                while (number==0){
                    //等待
                    condition.await();
                }
                number--;
                System.out.println(Thread.currentThread().getName() + "=>" + number);
                //通知
                condition.signalAll();
            } finally {
                lock.unlock();
            }
        }
    }
    

    上面这个可以了,但是好像没有什么区别

    那么这个好像和原来的技术没什么区别,那么我为什么要用新技术?


    精准通知,有序执行

    Condition 新技术可以让线程有序的执行,精准的通知和唤醒线程

    //资源类使用Lock锁,要求A执行完调用B,B执行完调用C,C执行完调用A
    class Data3{
        private Lock lock = new ReentrantLock();
    
        //因为一个同步监视器只能监视一个线程,所以我们使用三个监视器,然后通过监视器来判断我们来唤醒什么
        private Condition condition1 = lock.newCondition();
        private Condition condition2 = lock.newCondition();
        private Condition condition3 = lock.newCondition();
    
        //我们让number为1的时候执行A,number为2执行B,number为3执行C
        private int number = 1;
    
        public void printA(){
            lock.lock();
            try {
                //判断是否等待
                while (number!=1){
                    //等待
                    condition1.await();
                }
                //执行
                System.out.println(Thread.currentThread().getName()+"=>A");
                //通知,唤醒B
                number=2;
                condition2.signal();
            } catch (InterruptedException e) {
                e.printStackTrace();
            } finally {
                lock.unlock();
            }
        }
    
        public void printB(){
            lock.lock();
            try {
                //判断是否等待
                while (number!=2){
                    //等待
                    condition2.await();
                }
                //执行
                System.out.println(Thread.currentThread().getName()+"=>B");
                //通知,唤醒B
                number=3;
                condition3.signal();
            } catch (InterruptedException e) {
                e.printStackTrace();
            } finally {
                lock.unlock();
            }
        }
    
        public void printC(){
            lock.lock();
            try {
                //判断是否等待
                while (number!=3){
                    //等待
                    condition3.await();
                }
                //执行
                System.out.println(Thread.currentThread().getName()+"=>C");
                //通知,唤醒B
                number=1;
                condition1.signal();
            } catch (InterruptedException e) {
                e.printStackTrace();
            } finally {
                lock.unlock();
            }
        }
    }
    
    import java.util.concurrent.locks.Condition;
    import java.util.concurrent.locks.Lock;
    import java.util.concurrent.locks.ReentrantLock;
    
    public class C {
        public static void main(String[] args) {
            Data3 data = new Data3();
            new Thread(()->{ for (int i = 0; i < 3; i++) data.printA(); },"A").start();
            new Thread(()->{for (int i = 0; i < 3; i++) data.printB();},"B").start();
            new Thread(()->{for (int i = 0; i < 3; i++) data.printC();},"C").start();
            /*
                A=>A    B=>B    C=>C
                A=>A    B=>B    C=>C
                A=>A    B=>B    C=>C
             */
        }
    }
    

    八锁现象彻底理解锁

    如何判断锁的谁 ?,什么是锁?

    锁只会锁两个东西:对象,Class模板

    先发短信还是先打电话?

    八锁其实是八个问题,我们以先发短信还是先打电话作为这个问题,在不同的情况下分析八次

    1. 标准情况下,两个线程先打印发短信还是先打印打电话
    class Phone{
    
        public synchronized void sendMessage(){
            System.out.println("发短信");
        }
    
        public synchronized void call(){
            System.out.println("打电话");
        }
    }
    
    import java.util.concurrent.TimeUnit;
    
    public class Lock1 {
        public static void main(String[] args) {
            Phone phone = new Phone();
    
            new Thread(()->{
                phone.sendMessage();
            },"A").start();
    
            try {
                //公司中使用这个工具类来实现休眠,这里休眠一秒
                TimeUnit.SECONDS.sleep(1);
            } catch (InterruptedException e) {
                e.printStackTrace();
            }
    
            new Thread(()->{
                phone.call();
            },"B").start();
    
        }
    }
    

    这里应该是和先发短信,然后再打电话

    原因不是在于谁先调用的,而是在于谁先获得的

    这里的锁是锁住的对象,而两者都是同一个对象的方法

    所以为了同步,谁先获得锁,谁就先执行

    中间加了那一秒的延迟其实是个陷阱

    1. 发短信延迟4秒,谁先执行?
    class Phone2{
    
        public synchronized void sendMessage(){
            try {
                TimeUnit.SECONDS.sleep(4);
            } catch (InterruptedException e) {
                e.printStackTrace();
            }
            System.out.println("发短信");
        }
    
        public synchronized void call(){
            System.out.println("打电话");
        }
    
    
    }
    
    import java.util.concurrent.TimeUnit;
    
    public class Lock2 {
        public static void main(String[] args) {
            Phone2 phone = new Phone2();
    
            new Thread(()->{
                phone.sendMessage();
            },"A").start();
    
    
            new Thread(()->{
                phone.call();
            },"B").start();
    
        }
    }
    

    还是先发短信,然后再打电话

    这个问题和第一个问题没有本质区别,都是考验的问题

    锁是锁的对象,所以两者的锁都是同一把

    谁先获得锁,谁就先执行

    中间停顿的4秒是陷阱

    1. 改变一个为普通方法之后,谁先执行?
    class Phone3{
    
        public synchronized void sendMessage(){
            try {
                TimeUnit.SECONDS.sleep(4);
            } catch (InterruptedException e) {
                e.printStackTrace();
            }
            System.out.println("发短信");
        }
    
        public void call(){
            System.out.println("打电话");
        }
    }
    
    import java.util.concurrent.TimeUnit;
    
    public class Lock3 {
        public static void main(String[] args) {
            Phone3 phone = new Phone3();
    
            new Thread(()->{
                phone.sendMessage();
            },"A").start();
    
    
            new Thread(()->{
                phone.call();
            },"B").start();
    
        }
    }
    

    这个答案是先打电话

    因为打电话不是一个同步方法,所以不用获取锁

    这次的延迟不是一个陷阱

    1. 两个对象,两个方法,谁先执行
    class Phone4{
    
        public synchronized void sendMessage(){
            try {
                TimeUnit.SECONDS.sleep(4);
            } catch (InterruptedException e) {
                e.printStackTrace();
            }
            System.out.println("发短信");
        }
    
        public synchronized void call(){
            System.out.println("打电话");
        }
    }
    
    public class Lock4 {
        public static void main(String[] args) {
            Phone4 phone1 = new Phone4();
            Phone4 phone2 = new Phone4();
    
            new Thread(()->{
                phone1.sendMessage();
            },"A").start();
    
    
            new Thread(()->{
                phone2.call();
            },"B").start();
    
        }
    }
    

    这里应该是先打电话

    本质上的问题还是锁

    这个锁是锁的对象,所以两个对象的锁并不是同一把

    但是因为发短信延迟了四秒钟,所以先打电话

    1. 一个对象,两个静态同步方法,谁先执行?
    class Phone5{
    
        public static synchronized void sendMessage(){
            try {
                TimeUnit.SECONDS.sleep(4);
            } catch (InterruptedException e) {
                e.printStackTrace();
            }
            System.out.println("发短信");
        }
    
        public static synchronized void call(){
            System.out.println("打电话");
        }
    
    
    }
    
    import java.util.concurrent.TimeUnit;
    
    public class Lock5 {
        public static void main(String[] args) {
            Phone5 phone = new Phone5();
    
            new Thread(()->{
                phone.sendMessage();
            },"A").start();
    
    
            new Thread(()->{
                phone.call();
            },"B").start();
    
        }
    }
    

    这个题的答案应该是先执行发短信

    但是如果是按照之前根据同一把锁,锁的对象来回答的话,那么答案不正确

    因为加入了static关键字,表明了这是一个静态加载,说明了类一加载就有了

    所以这个锁的是Class模板,就是Phone5.class,而不是锁的对象

    打电话和发短信虽然争抢的是同一把锁,但是他们争抢的是Class的锁而不是对象的锁

    1. 两个对象,两个静态同步方法,谁先执行?
    class Phone6{
    
        public static synchronized void sendMessage(){
            try {
                TimeUnit.SECONDS.sleep(4);
            } catch (InterruptedException e) {
                e.printStackTrace();
            }
            System.out.println("发短信");
        }
    
        public static synchronized void call(){
            System.out.println("打电话");
        }
    }
    
    import java.util.concurrent.TimeUnit;
    
    public class Lock6 {
        public static void main(String[] args) {
            Phone6 phone1 = new Phone6();
            Phone6 phone2 = new Phone6();
    
            new Thread(()->{
                phone1.sendMessage();
            },"A").start();
    
    
            new Thread(()->{
                phone2.call();
            },"B").start();
    
        }
    }
    

    还是先发短信再打电话

    因为他们两个争抢的是Class模板的锁,而不是对象的锁

    所以有几个对象都无所谓

    谁先抢到Class的锁,谁就先执行

    1. 一个静态同步方法,一个普通同步方法是,一个对象,谁先执行?
    class Phone7{
    
        public static synchronized void sendMessage(){
            try {
                TimeUnit.SECONDS.sleep(4);
            } catch (InterruptedException e) {
                e.printStackTrace();
            }
            System.out.println("发短信");
        }
    
        public synchronized void call(){
            System.out.println("打电话");
        }
    
    
    }
    
    import java.util.concurrent.TimeUnit;
    
    public class Lock8 {
        public static void main(String[] args) {
            Phone7 phone = new Phone7();
    
            new Thread(()->{
                phone.sendMessage();
            },"A").start();
    
    
            new Thread(()->{
                phone.call();
            },"B").start();
    
        }
    }
    

    应当是先打电话,在发短信

    因为这个问题他们争抢的不是一把锁

    发短信抢的是Class模板锁,打电话争抢的是对象锁

    又因为发短信延迟4秒执行

    所以先执行打电话

    1. 两个对象,一个静态同步,一个普通同步,谁先执行?
    class Phone8{
    
        public static synchronized void sendMessage(){
            try {
                TimeUnit.SECONDS.sleep(4);
            } catch (InterruptedException e) {
                e.printStackTrace();
            }
            System.out.println("发短信");
        }
    
        public synchronized void call(){
            System.out.println("打电话");
        }
    
    
    }
    
    import java.util.concurrent.TimeUnit;
    
    public class Lock8 {
        public static void main(String[] args) {
            Phone7 phone1 = new Phone7();
            Phone7 phone2 = new Phone7();
    
            new Thread(()->{
                phone1.sendMessage();
            },"A").start();
    
    
            new Thread(()->{
                phone2.call();
            },"B").start();
    
        }
    }
    

    先打电话,然后发短信

    因为两个争抢的不是一把锁

    又因为发短信慢四秒

    所以先打电话


    集合类不安全

    List不安全的解决

    一个普通的List集合

    public class ListTest {
        public static void main(String[] args) {
            List<String> list = new ArrayList<>();
    
            //报错了:java.util.ConcurrentModificationException
            for (int i = 1; i <= 10; i++) {
                new Thread(()->{
                    list.add(UUID.randomUUID().toString().substring(0,5));
                    System.out.println(list);
                },String.valueOf(i)).start();
            }
        }
    }
    

    这个是不安全的,结果就是报错了,报错:java.util.ConcurrentModificationException

    并发修改异常

    并发下ArrayList是不安全的

    解决方案(普通层面)

    1. 使用VectorList<String> list = new Vector<>();

      但是Vector虽然可行,但是面试不会给高分的,因为Vector比ArrayList出现的时间早

      那么为什么JDK要出现一个线程不安全的ArrayList来替代Vector呢?

      肯定是比Vector更加高效,那么现在我回退了版本使用旧技术,简直是自寻死路

    2. 通过工具类转换:Collections

      public class ListTest {
          public static void main(String[] args) {
              List<String> list = Collections.synchronizedList(new ArrayList<>());
      
              //报错了:java.util.ConcurrentModificationException
              for (int i = 1; i <= 10; i++) {
                  new Thread(()->{
                      list.add(UUID.randomUUID().toString().substring(0,5));
                      System.out.println(list);
                  },String.valueOf(i)).start();
              }
          }
      }
      

      既然ArrayList不安全,那么我们让它变得安全不就行了么?

      集合的老大Collections,可以帮助我们解决这个问题,进行同步

      转变为Synchronized

      但是其实这个答案和Vector并没有区别,因为Vector也是使用了Synchronized

    解决方案(JUC)

    打开jdk文档,翻到java.util.concurrent包下,找到对应的class:CopyOnWriteArrayList

    这个就是JUC下面的解决方案

    import java.util.concurrent.CopyOnWriteArrayList;
    
    public class ListTest {
        public static void main(String[] args) {
            List<String> list = new CopyOnWriteArrayList<>();
    
            //报错了:java.util.ConcurrentModificationException
            for (int i = 1; i <= 10; i++) {
                new Thread(()->{
                    list.add(UUID.randomUUID().toString().substring(0,5));
                    System.out.println(list);
                },String.valueOf(i)).start();
            }
        }
    }
    

    CopyOnWrite:写入时复制,简称COW,是计算机程序设计领域的一种优化策略

    有多个线程调用的时候,比如调用list,list是唯一的,在读取的时候是固定的,但是写入的时候不能让他们同时写,因为A写完之后可能B就把A给覆盖了,那么现在就有:在写入的时候复制一份,写完之后交给调用者,这样就在写入的时候避免覆盖,造成数据问题

    这里涉及到一个读写分离的思想

    走底层,发现

    private transient volatile Object[] array;
    

    这里又看不懂了,但是下面会讲


    那么CopyOnWriteArrayListVector牛逼在哪里

    我们找一下Vector的源码

        public synchronized boolean add(E e) {
            modCount++;
            ensureCapacityHelper(elementCount + 1);
            elementData[elementCount++] = e;
            return true;
        }
    

    我们不可避免的发现了Vector使用了synchronized

    这在意料之中

    我们再查看CopyOnWriteArrayList

        public boolean add(E e) {
            final ReentrantLock lock = this.lock;
            lock.lock();
            try {
                Object[] elements = getArray();
                int len = elements.length;
                Object[] newElements = Arrays.copyOf(elements, len + 1);	//拿过来的时候复制一份
                newElements[len] = e;
                setArray(newElements);	//还回去
                return true;
            } finally {
                lock.unlock();
            }
        }
    

    我们看到它使用了Lock锁

    其中在拿过来的时候去复制一份

    还回去的时候交给他


    Set不安全的解决

    1586081771233

    首先做一个铺垫:List,Set,BlockingQueue

    我们知道有List,Set,但是BlockingQueue(阻塞队列)是第一次听,和List,Set同级

    先知道有这个东西就好了


    Set其实和List没有区别

    public class SetTest {
        public static void main(String[] args) {
            Set<String> set = new HashSet<>();
    
            for (int i = 1; i <= 10; i++) {
                new Thread(()->{
                    set.add(UUID.randomUUID().toString().substring(0,5));
                    set.forEach(System.out::println);
                }).start();
            }
        }
    }
    

    Exception in thread "Thread-3" java.util.ConcurrentModificationException

    并发修改异常

    解决方案(类似List)

    1. Set没有类似于Vector这样顶替的方式,所以直接上工具类

      public class SetTest {
          public static void main(String[] args) {
      //        Set<String> set = new HashSet<>();
              Set<String> set = Collections.synchronizedSet(new HashSet<>());
      
              for (int i = 1; i <= 10; i++) {
                  new Thread(()->{
                      set.add(UUID.randomUUID().toString().substring(0,5));
                      set.forEach(System.out::println);
                  }).start();
              }
          }
      }
      

      通过工具类转为Synchronized

    2. JUC的解决方法

      public class SetTest {
          public static void main(String[] args) {
      //        Set<String> set = new HashSet<>();
      //        Set<String> set = Collections.synchronizedSet(new HashSet<>());
              Set<String> set = new CopyOnWriteArraySet();
      
              for (int i = 1; i <= 10; i++) {
                  new Thread(()->{
                      set.add(UUID.randomUUID().toString().substring(0,5));
                      set.forEach(System.out::println);
                  }).start();
              }
          }
      }
      

    Set的底层是什么

    查看HashSet的第层:

        public HashSet() {
            map = new HashMap<>();
        }
    

    清楚了吧,HashSet的第层其实是new了一个HashMap

    但是还没完

        public boolean add(E e) {
            return map.put(e, PRESENT)==null;
        }
    

    这次看清楚了吧,HashSet的add方法其实就是hashmap的put进去的值的key,也就是键

    而这个PRESENT

    private static final Object PRESENT = new Object();
    

    随便来了一个Object

    所以HashSet的本质就是HashMap的key,因为key是无法重复的

    所以JDK官方也是够坑爹的


    HashMap不安全的解决

    首先有两个问题:

            //map是这样用的吗?默认等价于什么?
            Map<String, String> map = new HashMap<>();
    

    1、工作中不用HashMap

    2、默认等价于new HashMap(16,0.75);

    看到这里我们需要先看一看源码

    public HashMap(int initialCapacity, float loadFactor) {
    if (initialCapacity < 0)
       throw new IllegalArgumentException("Illegal initial capacity: " +
                                          initialCapacity);
    if (initialCapacity > MAXIMUM_CAPACITY)
       initialCapacity = MAXIMUM_CAPACITY;
    if (loadFactor <= 0 || Float.isNaN(loadFactor))
       throw new IllegalArgumentException("Illegal load factor: " +
                                          loadFactor);
    this.loadFactor = loadFactor;
    this.threshold = tableSizeFor(initialCapacity);
    }
    
    public HashMap(int initialCapacity) {
    this(initialCapacity, DEFAULT_LOAD_FACTOR);
    }
    
    public HashMap() {
    this.loadFactor = DEFAULT_LOAD_FACTOR; // all other fields defaulted
    }
    

    这三个重载其中有两个重载的变量需要重视:

    loadFactor:加载因子,默认加载因子0.75

    initialCapacity:初始容量,默认16,这个16是位运算的16

    static final int DEFAULT_INITIAL_CAPACITY = 1 << 4; // aka 16
    

    重点是这个不安全

    public class MapTest {
        public static void main(String[] args) {
            //map是这样用的吗?默认等价于什么?
            Map<String, String> map = new HashMap<>();
            for (int i = 0; i < 10; i++) {
                new Thread(()->{
                    map.put(Thread.currentThread().getName(), UUID.randomUUID().toString().substring(0,5));
                    System.out.println(map);
                },String.valueOf(i)).start();
            }
        }
    }
    

    java.util.ConcurrentModificationException

    并发修改异常

    解决方案

    1. Collections

      public class MapTest {
          public static void main(String[] args) {
              //map是这样用的吗?默认等价于什么?
              Map<String, String> map = Collections.synchronizedMap(new HashMap<>());
              for (int i = 0; i < 10; i++) {
                  new Thread(()->{
                      map.put(Thread.currentThread().getName(), UUID.randomUUID().toString().substring(0,5));
                      System.out.println(map);
                  },String.valueOf(i)).start();
              }
          }
      }
      
    2. JUC中没有CopyOnWritexxx,注意,名字变了,叫做ConcurrentHashMap

      1586083793286

      public class MapTest {
          public static void main(String[] args) {
              //map是这样用的吗?默认等价于什么?
              Map<String, String> map = new ConcurrentHashMap();
              for (int i = 0; i < 10; i++) {
                  new Thread(()->{
                      map.put(Thread.currentThread().getName(), UUID.randomUUID().toString().substring(0,5));
                      System.out.println(map);
                  },String.valueOf(i)).start();
              }
          }
      }
      

    对于ConcurrentHashMap,看源码

        public ConcurrentHashMap(int initialCapacity,float loadFactor, int concurrencyLevel) {
            if (!(loadFactor > 0.0f) || initialCapacity < 0 || concurrencyLevel <= 0)
    			//假如负载因子>0或者初始化的长度<0或者并发级别<=0,那么就抛出非法异常
                throw new IllegalArgumentException();
            if (initialCapacity < concurrencyLevel)   //假如初始化长度<并发级别
                initialCapacity = concurrencyLevel;   // 令初始化长度=并发级别
            long size = (long)(1.0 + (long)initialCapacity / loadFactor);
            int cap = (size >= (long)MAXIMUM_CAPACITY) ?
                MAXIMUM_CAPACITY : tableSizeFor((int)size);
            this.sizeCtl = cap;
        }
    

    Callable

    什么是Callable

    java.util.concurrent .Interface Callable<V>

    • Callable接口类似于Runnable

      然而, Runnable不返回结果,也不能抛出被检查的异常

    1. Callable可以有返回值

    2. Callable可以抛出异常

    3. Callable方法不同,Thread中继承的叫做run(),Callable中继承的叫做call()

      @FunctionalInterface
      public interface Callable<V> {
          V call() throws Exception;
      }
      

      泛型就是返回值

    现在有一个问题:

    Thread没法直接启动Callable

    因为Thread只能直接和Runnable挂上钩,但是没法和Callable挂上钩,从源码中可以看出

        public Thread(Runnable target) {
            init(null, target, "Thread-" + nextThreadNum(), 0);
        }
    

    那么我们知道了,因为Callable没法直接和Thread挂上钩,但是如果我们想办法,把Callable和Runnable挂上钩,那么不就间接的实现目的了么?

    java也是这么想的

    java.lang.Runnable中有

    1586087699277

    我们查看FutureTask这个Runnable的实现类

    1586087818712

    我们看到了,构造方法可以和Runnable挂上关系,也可以和Callable挂上关系

    所以结果清晰明了:

    Callable勾搭上了FutureTask,而FutureTask是Runnable的实现类,而Runnable和Thread有关系

    这就完美的实现了Callable<————>Thread

    new Thread(()->{new FutureTask<V>(Callable)}).start()

    所以Thread就可以启动Callable了

    import java.util.concurrent.Callable;
    import java.util.concurrent.ExecutionException;
    import java.util.concurrent.FutureTask;
    
    public class CallableTest {
        public static void main(String[] args) throws ExecutionException, InterruptedException {
            FutureTask futureTask = new FutureTask(new MyThread());
            new Thread(futureTask,"A").start();
    
            Integer i = (Integer)futureTask.get();
    
            System.out.println(i);
        }
    }
    
    class MyThread implements Callable<Integer> {
        @Override
        public Integer call() throws Exception {
            System.out.println("call()");
            return 123123;
        }
    }
    

    注意点:

    futureTask.get()这个方法可能会造成阻塞,结果可能会阻塞

    所以一般我们有两种解决方案:

    1. 放到最后一行
    2. 异步通信

    注意点,假如我们启用两个线程:

    public class CallableTest {
     public static void main(String[] args) throws ExecutionException, InterruptedException {
           FutureTask futureTask = new FutureTask(new MyThread());
           new Thread(futureTask,"A").start();
           new Thread(futureTask,"B").start();
    
           Integer i = (Integer)futureTask.get();
    
           System.out.println(i);
     }
    }
    
    class MyThread implements Callable<Integer> {
     @Override
     public Integer call() throws Exception {
           System.out.println("call()");
           return 123123;
     }
    }
    

    结果却是:

    call()
    123123

    这个说明结果有缓存,这是一个小坑


    常用的辅助类

    java.util.concurrent

    CountDownLatch

    减法计数器,两个方法

    • countDownLatch.countDown():每当有一个线程执行一次,减法计数器减一(计数)
    • countDownLatch.await():等待计数器归零,才能执行这行代码之后的代码(拦截器)
    import java.util.concurrent.CountDownLatch;
    
    public class CountDownLatchDemo {
        public static void main(String[] args) {
            //假设我们设置计数器为6
            CountDownLatch countDownLatch = new CountDownLatch(6);
    
            for (int i = 0; i < 6; i++) {
                final int temp = i;
                new Thread(()->{
                    System.out.println("这是第"+temp+"个");    //这里要注意了,因为lambda最终是new了一个接口,所以不能直接操作i
                    countDownLatch.countDown();//计数器减一
                },String.valueOf(i)).start();
            }
            try {
                countDownLatch.await(); //这里是等待计数器归零之后才可以执行后面的代码
            } catch (InterruptedException e) {
                e.printStackTrace();
            }
            System.out.println("计数器归零");
        }
    }
    

    CyclicBarrier

    加法计数器,两个方法

    两个构造函数,其中一个构造函数的作用是指定好计数器的个数,并且指定在达到计数器的个数的时候执行的线程

    • cyclicBarrier.await():等待线程执行完毕之后执行后面的方法
    import java.util.concurrent.BrokenBarrierException;
    import java.util.concurrent.CyclicBarrier;
    
    public class CyclicBarrierDemo {
        public static void main(String[] args) {
            CyclicBarrier cyclicBarrier = new CyclicBarrier(7,()->{
                System.out.println("执行后来的线程");//当七个线程都执行的时候,那么就开始执行这个线程
            });
    
            for (int i = 0; i < 7; i++) {
                int temp = i+1;
                new Thread(()->{
                    System.out.println("这里是第"+temp+"个线程");
                    try {
                        cyclicBarrier.await();//等待7个线程
                    } catch (InterruptedException e) {
                        e.printStackTrace();
                    } catch (BrokenBarrierException e) {
                        e.printStackTrace();
                    }
                }).start();
            }
        }
    }
    

    假如它要传递多余七个线程,但是其实达不到这个数量,那么程序就会卡在这里

    Semaphore

    信号量,让线程可以等待,让线程在有限的情况下有秩序的执行,限流可以使用

    就像抢车位,假如有6辆车,三个车位,那就需要抢车位

    • acquire():得到,假设得不到那么等待信号量被释放位置
    • release():释放当前的信号量,唤醒等待的线程
    import java.util.concurrent.Semaphore;
    import java.util.concurrent.TimeUnit;
    
    public class SemaphoreDemo {
        public static void main(String[] args) {
            //信号量,这里可以理解为车位
            Semaphore semaphore = new Semaphore(3);
    
            //有6个线程需要争抢
            for (int i = 1; i <= 6; i++) {
                new Thread(()->{
                    try {
                        semaphore.acquire();//得到
                        System.out.println(Thread.currentThread().getName()+"得到");
                        TimeUnit.SECONDS.sleep(2);//模拟线程操作,休息两秒钟
                    } catch (InterruptedException e) {
                        e.printStackTrace();
                    }finally {
                        semaphore.release();//释放
                    }
                },String.valueOf(i)).start();
            }
        }
    }
    

    ReadWriteLock

    读写锁,java.util.concurrent

    为了提高工作的效率,读写锁规定:读取可以由多个线程读取,写则只能有一个线程去写

    • writeLock():写锁,也称独占锁
      • lock()
      • unlock()
    • readLock():读锁,也称共享锁
      • lock()
      • unlock()

    这个意思是说:

    读写锁中,要么进行读取,要么进行写入,不可以两个一起。

    然而读锁是共享锁,所以可以使用多个线程一起读。

    写锁是独占锁,只能有一个线程进行写入操作。

    //自定义缓存
    class MyCache{
        private volatile Map<String,Object> map = new HashMap<>();
    
        //这个是读写锁,可以实现更加细粒度的操作,就是读取可以多线程操作,写入只能一个线程
        private ReadWriteLock readWriteLock = new ReentrantReadWriteLock();
    
        //写
        public void put(String key,Object value){
            readWriteLock.writeLock().lock();//加了一把写锁,更加细粒度的划分了读锁和写锁
            try {
                System.out.println(Thread.currentThread().getName()+"写入"+key);
                map.put(key,value);
                System.out.println(Thread.currentThread().getName()+"写入ok");
            } catch (Exception e) {
                e.printStackTrace();
            } finally {
                readWriteLock.writeLock().unlock();//解锁
            }
        }
    
        //读
        public void get(String key){
            readWriteLock.readLock().lock();    //读取锁
            try {
                System.out.println(Thread.currentThread().getName()+"读取"+key);
                Object o = map.get(key);
                System.out.println(Thread.currentThread().getName()+"读取ok");
            } catch (Exception e) {
                e.printStackTrace();
            } finally {
                readWriteLock.readLock().unlock();//解锁
            }
        }
    }
    
    import java.util.HashMap;
    import java.util.Map;
    import java.util.concurrent.locks.ReadWriteLock;
    import java.util.concurrent.locks.ReentrantReadWriteLock;
    
    public class ReadWriteLockDemo {
        public static void main(String[] args) {
    
            MyCache cache = new MyCache();
    
            for (int i = 0; i < 5; i++) {
                final int temp = i+1;
                new Thread(()->{
                    cache.put(temp+"",temp+"");
                },String.valueOf(temp)).start();
            }
    
            for (int i = 0; i < 5; i++) {
                final int temp = i+1;
                new Thread(()->{
                    cache.get(temp+"");
                },String.valueOf(temp)).start();
            }
    
        }
    }
    

    阻塞队列

    BlockingQueue:java.util.concurrent

    阻塞队列由两个词构成

    • 阻塞

    • 队列

    队列分为:写,读

    如果队列是满的,要写东西必须阻塞等待取出

    如果队列是空的,要取东西必须阻塞等待生产

    1586167772105

    阻塞队列并不是新东西,他也是Collection的子类

    阻塞队列属于队列家族,属于庞大分支的一小部分

    我们什么时候使用BlockingQueue

    线程池,多线程并发处理


    学会使用队列

    添加,移除

    四组API

    1. 抛出异常
    2. 不抛出异常
    3. 阻塞等待
    4. 超时等待
    方式 有返回值,抛出异常 有返回值,不抛出异常 阻塞等待 超时等待
    添加 add offer put offer(重载)
    移除 remove poll take poll(重载)
    判断队列首 element peek null null
    • 抛出异常
    import java.util.concurrent.ArrayBlockingQueue;
    
    public class BlockQueueDemo {
        public static void main(String[] args) {
            test1();
            test2();
        }
    
        //抛出异常
        public static void test1(){
    
            //队列的大小设为3
            ArrayBlockingQueue blockingQueue = new ArrayBlockingQueue(3);
            System.out.println(blockingQueue.add("A"));	//true
            System.out.println(blockingQueue.add("B"));//true
            System.out.println(blockingQueue.add("C"));//true
    
            //查看队首
            System.out.println(blockingQueue.element());    //A
    
            //因为队列大小为3,所以添加第四个,这里我们需要的API是抛出异常的API:java.lang.IllegalStateException: Queue full
            System.out.println(blockingQueue.add("D"));
        }
    
    
        //抛出异常
        public static void test2(){
    
            //队列的大小设为3
            ArrayBlockingQueue blockingQueue = new ArrayBlockingQueue(3);
    
            //直接取出元素,因为什么也没有,所以不能取出,这里我们使用的是抛出异常的API:java.util.NoSuchElementException
            System.out.println(blockingQueue.remove());
        }
    }
    
    • 有返回值,不抛出异常
    public class BlockQueueDemo {
        public static void main(String[] args) {
            test3();
            test4();
        }
    
        //返回值
        public static void test3(){
    
            //队列的大小设为3
            ArrayBlockingQueue blockingQueue = new ArrayBlockingQueue(3);
    
    
            System.out.println(blockingQueue.offer("A"));//true
            System.out.println(blockingQueue.offer("B"));//true
            System.out.println(blockingQueue.offer("C"));//true
    
            System.out.println(blockingQueue.peek());   //查看队首:A
    
            //不抛出异常,有返回值
            System.out.println(blockingQueue.offer("D"));//false
        }
    
        //有返回值
        public static void test4(){
    
            //队列的大小设为3
            ArrayBlockingQueue blockingQueue = new ArrayBlockingQueue(3);
    
            System.out.println(blockingQueue.poll());   //null
        }
    }
    
    • 一直阻塞等待(put进去没有返回值)
    public class BlockQueueDemo {
        public static void main(String[] args) throws InterruptedException {
    //        test5();
            test6();
        }
    
        //阻塞(一直等待)
        public static void test5() throws InterruptedException {
    
            //队列的大小设为3
            ArrayBlockingQueue blockingQueue = new ArrayBlockingQueue(3);
    
    
            blockingQueue.put("A");
            blockingQueue.put("B");
            blockingQueue.put("C");
    
            //一直等待
            blockingQueue.put("D");
        }
    
        //阻塞(一直等待)
        public static void test6() throws InterruptedException {
    
            //队列的大小设为3
            ArrayBlockingQueue blockingQueue = new ArrayBlockingQueue(3);
    
            System.out.println(blockingQueue.take());   //一直等待
        }
    }
    
    • 超时退出
    public class BlockQueueDemo {
        public static void main(String[] args) throws InterruptedException {
            test7();
            test8();
        }
    
        //阻塞(超时等待)
        public static void test7() throws InterruptedException {
    
            //队列的大小设为3
            ArrayBlockingQueue blockingQueue = new ArrayBlockingQueue(3);
    
    
            blockingQueue.offer("A");
            blockingQueue.offer("B");
            blockingQueue.offer("C");
    
            //超时等待
            blockingQueue.offer("D", 2,TimeUnit.SECONDS);
        }
    
        //阻塞(超时等待)
        public static void test8() throws InterruptedException {
    
            //队列的大小设为3
            ArrayBlockingQueue blockingQueue = new ArrayBlockingQueue(3);
    
            System.out.println(blockingQueue.poll(2,TimeUnit.SECONDS));   //null
        }
    }
    
    

    同步队列

    SynchronouseQueue:java.util.concurrent

    只能存储一个元素,进去一个元素必须等待取出来之后才能往里面存另一个元素

    所以同步队列和其他的阻塞队列是不一样的

    import java.util.concurrent.BlockingQueue;
    import java.util.concurrent.SynchronousQueue;
    import java.util.concurrent.TimeUnit;
    
    public class SynchronousQueueDemo {
        public static void main(String[] args) {
            //同步队列
            BlockingQueue<String> synchronousQueue = new SynchronousQueue<String>();
    
            new Thread(()->{
                try {
                    System.out.println(Thread.currentThread().getName()+"-->put 1");
                    synchronousQueue.put("1");
    
                    System.out.println(Thread.currentThread().getName()+"-->put 2");
                    synchronousQueue.put("2");
    
                    System.out.println(Thread.currentThread().getName()+"-->put 3");
                    synchronousQueue.put("3");
                } catch (InterruptedException e) {
                    e.printStackTrace();
                }
            }).start();
    
            new Thread(()->{
                try {
                    //为了保证上面的插入完了,这里等待三秒
                    TimeUnit.SECONDS.sleep(3);
                    System.out.println(Thread.currentThread().getName()+"-->get 1");
                    synchronousQueue.take();
    
                    TimeUnit.SECONDS.sleep(3);
                    System.out.println(Thread.currentThread().getName()+"-->get 2");
                    synchronousQueue.take();
    
                    TimeUnit.SECONDS.sleep(3);
                    System.out.println(Thread.currentThread().getName()+"-->get 3");
                    synchronousQueue.take();
                } catch (InterruptedException e) {
                    e.printStackTrace();
                }
            }).start();
        }
    }
    /*
        Thread-0-->put 1
        Thread-1-->get 1
        Thread-0-->put 2
        Thread-1-->get 2
        Thread-0-->put 3
        Thread-1-->get 3
    */
    

    线程池

    线程池的东西:三大方法,七大参数,四种拒绝策略

    池化技术:事先准备好一些资源,有人要用,就来我这里拿,用完之后还给我

    程序的运行,本质:占用系统的资源!

    优化资源的使用!=> 池化技术

    线程池、连接池、内存池、对象池等等都是池

    线程池的好处:

    1、降低资源的消耗

    2、提高响应的速度

    3、方便管理

    线程复用,可以控制最大并发数,管理线程


    1586245177278


    三大方法

    import java.util.concurrent.Executors;
    
    public class PoolDemo {
        public static void main(String[] args) {
            Executors.newSingleThreadExecutor();//线程池只有单个线程
            Executors.newFixedThreadPool(5);//创建一个固定的线程池大小
            Executors.newCachedThreadPool();//可伸缩的线程池,遇强则强
        }
    }
    

    使用线程池的使用案例

    import java.util.concurrent.ExecutorService;
    import java.util.concurrent.Executors;
    
    public class PoolDemo {
        public static void main(String[] args) {
    //        ExecutorService threadPool = Executors.newSingleThreadExecutor();//线程池只有单个线程
    //        ExecutorService threadPool =  Executors.newFixedThreadPool(5);//创建一个固定的线程池大小
            ExecutorService threadPool =  Executors.newCachedThreadPool();//可伸缩的线程池,遇强则强
    
            //new Thread(()->{}).start();这个是以前我们创建的线程,但是从今天开始,要使用线程池来创建
            //execute()里面也是一个Runnable
            try {
                for (int i = 0; i < 10; i++)    threadPool.execute(()->System.out.println(Thread.currentThread().getName()+"   ok"));
            } catch (Exception e) {
                e.printStackTrace();
            } finally {
                threadPool.shutdown();//注意,最后要关闭线程池
            }
        }
    }
    

    从上面的例子中分析:

    第一个单个线程中,10个语句只有一条线程执行

    第二个线程中,10个语句最多有第5条线程执行,也就是说最多到了5条线程同时执行

    第三个线程中,10个语句最多有第10条线程执行,也就是说10条线程同时执行了,所以只要你CPU可以撑住,想多少就多少


    七大参数

    源码分析:

    public static ExecutorService newSingleThreadExecutor() {
        return new FinalizableDelegatedExecutorService
            (new ThreadPoolExecutor(1, 1,
                                    0L, TimeUnit.MILLISECONDS,
                                    new LinkedBlockingQueue<Runnable>()));
    }
    
    public static ExecutorService newFixedThreadPool(int nThreads) {
        return new ThreadPoolExecutor(nThreads, nThreads,
                                      0L, TimeUnit.MILLISECONDS,
                                      new LinkedBlockingQueue<Runnable>());
    }
    
    public static ExecutorService newCachedThreadPool() {
        return new ThreadPoolExecutor(0, Integer.MAX_VALUE,
                                      60L, TimeUnit.SECONDS,
                                      new SynchronousQueue<Runnable>());
    }
    

    观察这三个线程,我们发现他们三个本质都是调用的new ThreadPoolExecutor

    七大参数

    public ThreadPoolExecutor(
        int corePoolSize,	//核心线程池容量
        int maximumPoolSize,	//线程池的最大容量
        long keepAliveTime,	//没有人调用则超时释放
        TimeUnit unit,	//超时单位
        BlockingQueue<Runnable> workQueue,	//阻塞队列
        ThreadFactory threadFactory,	//线程工厂,创建线程的,一般不用动
        RejectedExecutionHandler handler 	//拒绝策略
    ) {
        
        if (corePoolSize < 0 || maximumPoolSize <= 0 || maximumPoolSize < corePoolSize || keepAliveTime < 0)	throw new IllegalArgumentException();
        
        if (workQueue == null || threadFactory == null || handler == null)	throw new NullPointerException();
    
        this.acc = System.getSecurityManager() == null ? null : AccessController.getContext();
        
        this.corePoolSize = corePoolSize;
        
        this.maximumPoolSize = maximumPoolSize;
        
        this.workQueue = workQueue;
        
        this.keepAliveTime = unit.toNanos(keepAliveTime);
        
        this.threadFactory = threadFactory;
        
        this.handler = handler;
    }
    

    我们找到了这样的源码,这里有七个参数

    看完了这个我们再去分析一下上面的三个线程池

    public static ExecutorService newSingleThreadExecutor() {
        return new FinalizableDelegatedExecutorService
            (new ThreadPoolExecutor(1, 1,	//核心线程为1,共有1个线程
                                    0L, TimeUnit.MILLISECONDS,
                                    new LinkedBlockingQueue<Runnable>()));
    }
    
    public static ExecutorService newFixedThreadPool(int nThreads) {
        return new ThreadPoolExecutor(nThreads, nThreads,	//核心线程和线程总数都由外界传过来,比如我们刚才的5
                                      0L, TimeUnit.MILLISECONDS,
                                      new LinkedBlockingQueue<Runnable>());
    }
    
    public static ExecutorService newCachedThreadPool() {	
        return new ThreadPoolExecutor(0, Integer.MAX_VALUE,	//核心线程为0,但是最大线程可以有Integer.MAX_VALUE:21亿
                                      60L, TimeUnit.SECONDS,
                                      new SynchronousQueue<Runnable>());
    }
    

    如果我们使用缓存机制的线程池,电脑一定会爆出OOM

    这样我们就知道了为什么阿里巴巴强制不允许使用Executors来创建,而是通过ThreadPoolExecutor

    因为可能会因为内存过大导致溢出


    线程池的形象解释

    在生活中,我们会遇到银行排队的情况

    假设现在银行中有5个窗口,候客区中有三个座位

    平常人不多的时候银行窗口并不是全都开着,比如只开着两个窗口,剩下三个窗口

    1586246784863

    如上图所示,假如现在开着的窗口已经满了,又来了一个人,那么就只能到候客区中坐下等待银行办理

    但是假如银行中开着的窗口满了,而候客区也满了,但是还在进人

    1586246904431

    这个时候另外三个候选窗口就要开启了

    但是假如全部的窗口都满了,候客区也满了,又进来一个人

    1586246985164

    那么这个人要么走,要么等着,这就是一个策略,这就是拒绝策略

    综上,银行全部的窗口就是总线程,一直开着的窗口就是核心线程,候客区就是阻塞队列,全部的位置满了之后对再进来的人的处理策略就是拒绝策略

    1586247129576

    处理到了最后,所有人,或者只有剩下的两个主线程的还没有都处理完了,等待了一段时间之后,还没有更多的人来处理,那么剩下的3,4,5这三个银行端口就关闭,等待下一次开启

    这个部分就叫做超时等待


    四种拒绝策略

    名称 拒绝策略
    AbortPolicy 抛出异常
    CallerRunsPolicy 回去执行,比如main线程到了新线程,那么就回到main线程执行
    DiscardOldestPolicy 尝试去和最早的线程竞争,竞争失败任务则不执行
    DiscardPolicy 不会抛出异常,但是任务不执行

    不使用Executors创建线程池的方法

    import java.util.concurrent.*;
    
    public class PoolDemo {
        public static void main(String[] args) {
            ExecutorService threadPool
                    = new ThreadPoolExecutor(
                            2,  //核心线程池
                    5,  //最大线程池
                    3,  //超时等待时间
                    TimeUnit.SECONDS,   //时间单位
                    new LinkedBlockingDeque<>(3),   //阻塞队列
                    Executors.defaultThreadFactory(),   //默认的创建工厂,一般不改变
                    new ThreadPoolExecutor.AbortPolicy()    //拒绝策略,抛出异常
            );
        }
    }
    

    线程<=2:只使用核心线程

    2<线程<=5:使用核心线程+阻塞队列,2个线程运行,3个线程等待

    5<线程<=8:使用全部线程+阻塞队列,5个线程运行,3个线程等待,最大承载

    线程>8:使用全部线程+阻塞队列+抛出异常,5个线程运行,3个线程等待,其余线程执行拒绝策略


    小结和扩展

    调优:线程池的最大大小如何去设置

    最大线程到底该如何定义

    • CPU密集型
    • IO密集型

    CPU密集型的写法:

    我们一开始的时候说到的并发和并行中,说到了cpu的核数,假如是12核的cpu,就意味着并发量为12

    12条线程同时执行

    那么定义为CPU密集型的时候,这个时候我们将最大线程定义为12,就可以确保电脑的利用率达到最好

    我们可以使用:Runtime.getRuntime().availableProcessors()获取CPU的核数

    IO密集型的写法:

    一个程序有15个大型任务,IO十分占用资源,那么我们至少留15个线程执行任务,在这种情况下只要比它大就可以了

    一般情况下设置为IO任务的两倍。这里是30


    四大函数式接口

    函数式接口

    四大函数式接口(基本):java.util.function

    Function,Predicate,Consumer,Supplier

    1586251694226

    其余的都是组合式接口

    @FunctionalInterface:函数式接口

    只有一个方法的接口,比如Runnable,比如foreach()的参数就是一个消费者类型的函数式接口

    public void forEach(Consumer<? super E> action) {}
    
    @FunctionalInterface
    public interface Consumer<T> {}
    

    函数式接口在java中超级多,在新版本的底层大量应用


    • Function:函数型接口
    @FunctionalInterface
    public interface Function<T, R> {
        R apply(T t);
    
        default <V> Function<V, R> compose(Function<? super V, ? extends T> before) {
            Objects.requireNonNull(before);
            return (V v) -> apply(before.apply(v));
        }
    
        default <V> Function<T, V> andThen(Function<? super R, ? extends V> after) {
            Objects.requireNonNull(after);
            return (T t) -> after.apply(apply(t));
        }
    
        static <T> Function<T, T> identity() {
            return t -> t;
        }
    }
    

    使用:

    Function<String, String> function = new Function<String, String>() {
        @Override
        public String apply(String s) {
            return s;
        }
    };
    //简化后:Function<String, String> function = str-> {return str;};
    

    函数型接口:有一个输入参数,有一个输出(注意函数型接口不是函数式接口)

    • Predicate:断定型接口
    @FunctionalInterface
    public interface Predicate<T> {
    
        boolean test(T t);
    
        default Predicate<T> and(Predicate<? super T> other) {
            Objects.requireNonNull(other);
            return (t) -> test(t) && other.test(t);
        }
    
        default Predicate<T> negate() {
            return (t) -> !test(t);
        }
    
        default Predicate<T> or(Predicate<? super T> other) {
            Objects.requireNonNull(other);
            return (t) -> test(t) || other.test(t);
        }
    
        static <T> Predicate<T> isEqual(Object targetRef) {
            return (null == targetRef)
                    ? Objects::isNull
                    : object -> targetRef.equals(object);
        }
    }
    

    使用:

    Predicate<String> predicate = new Predicate<String>() {
        @Override
        public boolean test(String s) {
            return s.isEmpty();
        }
    };
    //简化后:Predicate<String> predicate =s -> {return s.isEmpty();};
    

    断定型接口,有一个输入参数,返回值只能是布尔值

    • Consumer:消费型接口
    @FunctionalInterface
    public interface Consumer<T> {
    
        void accept(T t);
    
        default Consumer<T> andThen(Consumer<? super T> after) {
            Objects.requireNonNull(after);
            return (T t) -> { accept(t); after.accept(t); };
        }
    }
    

    使用:

    Consumer<String> consumer = new Consumer<String>() {
        @Override
        public void accept(String s) {
            System.out.println(s);      
        }
    };
    //Consumer<String> consumer = s-> System.out.println(s);
    

    消费型接口:只有输入,没有返回值

    • Supplier:供给型接口
    @FunctionalInterface
    public interface Supplier<T> {
    
        T get();
    }
    

    使用:

    Supplier<String> supplier = new Supplier<String>() {
        @Override
        public String get() {
            return "str";
        }
    };
    //Supplier<String> supplier =()->{return "str";};	
    

    供给型接口:只有输出,没有输入


    Stream流计算

    什么是Stream流计算

    存储+计算

    java.util.Stream

    我们的存储使用集合,MySQL等等

    计算现在交给流来做

    @Data
    @AllArgsConstructor
    @NoArgsConstructor
    public class User {
        private int id;
        private String name;
        private int age;
    }
    
    /*
    * 题目要求:一分钟之内完成此题,而且只能用一行代码实现!
    * 现在有5个用户,筛选:
    * 1. ID必须为偶数
    * 2. 年龄必须大于23岁
    * 3. 用户名字转为大写
    * 4. 用户名字母倒着排序
    * 5. 只输出一个用户
    * */
    public class Test {
        public static void main(String[] args) {
            User u1 = new User(1, "a", 21);
            User u2 = new User(2, "b", 22);
            User u3 = new User(3, "c", 23);
            User u4 = new User(4, "d", 24);
            User u5 = new User(5, "e", 25);
    
            List<User> list = Arrays.asList(u1, u2, u3, u4, u5);
        }
    }
    

    最后我们输出的应当是D

    使用流进行计算

    查看jdk:java.util.stream

    • stream():将集合转换为流
    • filter():过滤,传入断定型接口,假如为true则保留,为false过滤掉
    • map():转换,传入函数型接口,可以自由地对参数进行转换
    • sorted():排序,默认为正序,可传入函数式接口使用compare()进行倒序排序
    • limit():分页
    • foreach():可以传入消费型函数式接口,一般用于遍历
    /*
    * 题目要求:一分钟之内完成此题,而且只能用一行代码实现!
    * 现在有5个用户,筛选:
    * 1. ID必须为偶数
    * 2. 年龄必须大于23岁
    * 3. 用户名字转为大写
    * 4. 用户名字母倒着排序
    * 5. 只输出一个用户
    * */
    public class Test {
        public static void main(String[] args) {
            User u1 = new User(1, "a", 21);
            User u2 = new User(2, "b", 22);
            User u3 = new User(3, "c", 23);
            User u4 = new User(4, "d", 24);
            User u5 = new User(5, "e", 25);
    
            List<User> list = Arrays.asList(u1, u2, u3, u4, u5);
            list.stream()
                    .filter(u->{return u.getId()%2==0;})    //选取偶数
                    .filter(u->{return u.getAge()>23;})     //年龄必须大于23岁
                    .map(u->{return u.getName().toUpperCase();})    //用户名转大写
                    .sorted((uu1,uu2)->{return uu2.compareTo(uu1);})    //倒序排序
                    .forEach(System.out::println);
        }
    }
    

    ForkJoin

    什么是ForkJoin

    中文名字叫做分支合并

    ForkJoin在JDK7的时候出现,并行执行任务,提高效率

    1586311850816


    什么时候使用ForkJoin

    大数据量的时候


    ForkJoin特点:工作窃取

    A,B维护着双端队列

    假如A线程有4个任务,B线程也有4个任务

    A线程还没执行完人物,但是B线程已经执行完任务了

    这个时候B线程会从另一头把A线程的人物拿过来执行,不让B线程去等待

    这就叫工作窃取

    1586312095300


    ForkJoin的操作

    java.util.concurrent

    ForkJoin分为两步操作:Fork,Join

    1. ForkJoinPool执行

    2. 计算任务forkjointask执行:forkjoinPool.execute(ForkJoinTask task)

      execute:同步提交

      submit:异步提交

      ForkJoinTask

      • RecursiveAction:递归事件,没有返回值
      • RecursiveTask:递归任务,有返回值

      1586312635301

    3. 计算类继承ForkJoinTask


    • ForkJoinTask
    import java.util.concurrent.RecursiveTask;
    
    public class ForkJoinDemo extends RecursiveTask<Long> {
    
        private Long start; //开始
        private Long end;   //结束
    
        private Long temp = 10000L;  //临界值,高于临界值使用forkjoin,小于临界值用普通方法
    
        public ForkJoinDemo(Long start, Long end) {
            this.start = start;
            this.end = end;
        }
    
        //计算
        @Override
        protected Long compute() {
    
            if ((end-start)<temp){
                Long sum = 0L;
                for (Long i = start; i <= end; i++) {
                    sum+=i;
                }
                return sum;
            }else {//forkjoin
                long middle = (start + end) / 2;    //中间值,使用中间值将这个任务分为两个小任务
    
                ForkJoinDemo task1 = new ForkJoinDemo(start, middle);   //前半段的任务
                task1.fork();//把任务压入队列
    
                ForkJoinDemo task2 = new ForkJoinDemo(middle+1, end);   //后半段的任务,注意这里是middle+1
                task2.fork();//把任务压入队列
    
                return task1.join() + task2.join(); //获得结果
    
            }
        }
    }
    
    • Test
    import java.util.concurrent.ExecutionException;
    import java.util.concurrent.ForkJoinPool;
    import java.util.concurrent.ForkJoinTask;
    import java.util.stream.LongStream;
    
    public class Test {
        public static void main(String[] args) throws ExecutionException, InterruptedException {
    //        test1();//sum=500000000500000000时间:8296
    //        test2();//sum=500000000500000000,时间:6227,这里效率还可以更高,他可以把临界值调整一下,我们这里使用的是1000L,但是如果修改之后还可以更快
            test3();//sum=500000000500000000,时间:543,使用stream流还是牛逼
        }
    
        //拿3000块钱的程序员
        public static void test1(){
            long start = System.currentTimeMillis();
    
            Long sum = 0L;
            for (Long i = 1L; i <= 10_0000_0000L; i++) {
                sum+=i;
            }
            long end = System.currentTimeMillis();
            System.out.println("sum="+sum+",时间:"+(end-start));
        }
    
        //拿6000块钱的程序员:forkjoin
        public static void test2() throws ExecutionException, InterruptedException {
            long start = System.currentTimeMillis();
    
            ForkJoinPool forkJoinPool = new ForkJoinPool();
            ForkJoinTask<Long> task = new ForkJoinDemo(1L, 10_0000_0000L);
    
    //        forkJoinPool.execute(task);//执行任务,没有返回值
            ForkJoinTask<Long> submit = forkJoinPool.submit(task);//提交任务,有返回值
    
            Long sum = submit.get();//需要阻塞等待
    
            long end = System.currentTimeMillis();
            System.out.println("sum="+sum+",时间:"+(end-start));
        }
    
    
    
        //拿9000块钱的程序员:stream
        public static void test3() throws ExecutionException, InterruptedException {
            long start = System.currentTimeMillis();
    
            //range()和rangeClose()的选择区间:range左右不包含,rangeClose左开右闭
            long sum = LongStream.rangeClosed(0L, 10_0000_0000L).parallel().reduce(0, Long::sum);
    
            long end = System.currentTimeMillis();
            System.out.println("sum="+sum+",时间:"+(end-start));
        }
    }
    

    从上面那个案例中可以看出,真要计算还是要是用stream流,但是forkjoin也是要会用

    而且我们也知道了流还有LongStream,那么其他的DoubleStream,IntStream等等,没有区别


    异步回调

    Future:设计的初衷就是对未来的某个事件进行建模

    java.util.concurrent

    异步回调说白了就三个:

    • 异步执行
    • 成功回调
    • 失败回调

    Future的实现类:CompletableFuture

    import java.util.concurrent.CompletableFuture;
    import java.util.concurrent.ExecutionException;
    import java.util.concurrent.TimeUnit;
    
    //异步调用
    public class Demo01 {
        public static void main(String[] args) throws ExecutionException, InterruptedException {
    
    
    //        resultNull();
            resultNotNull();
    
        }
    
        public static void resultNull() throws ExecutionException, InterruptedException {
            //发起一个请求
    
            //没有返回值的异步回调,注意这里的Void
            CompletableFuture<Void> completedFuture = CompletableFuture.runAsync(()->{
                try {
                    TimeUnit.SECONDS.sleep(2);//这里模拟结果执行
                } catch (InterruptedException e) {
                    e.printStackTrace();
                }
                System.out.println(Thread.currentThread().getName());
            });
            completedFuture.get();//他要等待completedFuture的结果执行完毕才会得到结果,所以获取执行结果会阻塞
            /*
                上面的这个例子可以理解一下,我们可以把任务放到completedFuture里面去执行
                然后等到我们需要结果的时候只需要执行completedFuture.get();就可以获得相应的结果
             */
        }
    
    
        public static void resultNotNull() throws ExecutionException, InterruptedException {
            //有返回值的异步回调
            CompletableFuture<Integer> completableFuture = CompletableFuture.supplyAsync(()->{
                return 1024;
            });
    
    
            Integer result = completableFuture
                            .whenComplete((t, u) -> {  //编译成功
                                System.out.println("t=>" + t);//正常的返回结果,这里是1024
                                System.out.println("u=>" + u);//错误信息
                            })
                            .exceptionally((e) -> {    //编译失败
                                System.out.println(e.getMessage());
                                e.printStackTrace();
                                return 233;
                            }).get();//.get()得到结果
    
            System.out.println(result);
        }
    }
    

    JMM

    请你谈谈 Volatile的理解

    Volatile是JAVA虚拟机提供轻量级的同步机制

    1、保证可见性

    2、不保证原子性

    3、禁止指令重排

    可见性--》JMM

    什么是JMM

    JMM:Java内存模型,这个是不存在的东西,是一种概念,约定

    关于JMM的同步约定

    1、线程解锁前,必须把共享变量立刻刷新到主存

    我们知道线程的操作是:将主内存的共享变量复制一份到线程自己的工作内存中

    那么线程在解锁之前必须立刻把自己工作内存中的值刷新到主存中

    2、线程加锁前:必须读取主存中的最新值到工作内存中

    3、加锁和解锁必须是同一把锁

    线程中分为工作内存和主内存

    线程工作时会将内存中的数据刷到工作内存中,工作完成之后会将数据再次刷回主内存中

    1586328751482


    内存交互操作有8种,虚拟机实现必须保证每一个操作都是原子的,不可在分的

    对于double和long类型的变量来说,load、store、read和write操作在某些平台上允许例外

    • lock (锁定):作用于主内存的变量,把一个变量标识为线程独占状态
    • unlock (解锁):作用于主内存的变量,它把一个处于锁定状态的变量释放出来,释放后的变量才可以被其他线程锁定
    • read (读取):作用于主内存变量,它把一个变量的值从主内存传输到线程的工作内存中,以便随后的load动作使用
    • load (载入):作用于工作内存的变量,它把read操作从主存中变量放入工作内存中
    • use (使用):作用于工作内存中的变量,它把工作内存中的变量传输给执行引擎,每当虚拟机遇到一个需要使用到变量的值,就会使用到这个指令
    • assign (赋值):作用于工作内存中的变量,它把一个从执行引擎中接受到的值放入工作内存的变量副本中
    • store (存储):作用于主内存中的变量,它把一个从工作内存中一个变量的值传送到主内存中,以便后续的write使用
    • write  (写入):作用于主内存中的变量,它把store操作从工作内存中得到的变量的值放入主内存的变量中

    JMM对这八种指令的使用,制定了如下规则:

    • 不允许read和load、store和write操作之一单独出现。即使用了read必须load,使用了store必须write
    • 不允许线程丢弃他最近的assign操作,即工作变量的数据改变了之后,必须告知主存
    • 不允许一个线程将没有assign的数据从工作内存同步回主内存
    • 一个新的变量必须在主内存中诞生,不允许工作内存直接使用一个未被初始化的变量。就是对s变量实施use、store操作之前,必须经过assign和load操作
    • 一个变量同一时间只有一个线程能对其进行lock。多次lock后,必须执行相同次数的unlock才能解锁
    • 如果对一个变量进行lock操作,会清空所有工作内存中此变量的值,在执行引擎使用这个变量前,必须重新load或assign操作初始化变量的值
    • 如果一个变量没有被lock,就不能对其进行unlock操作。也不能unlock一个被其他线程锁住的变量
    • 对一个变量进行unlock操作之前,必须把此变量同步回主内存

    Volatile

    问题

    那么现在有一个问题:

    线程B修改了值,但是线程A不能及时可见

    1586328905990

    import java.util.concurrent.TimeUnit;
    
    public class JMMDemo {
    
        private static boolean flag = true;
    
        public static void main(String[] args) {
    
            new Thread(()->{
                while (flag){
                }
            }).start();
    
            try {
                TimeUnit.SECONDS.sleep(2);
            } catch (InterruptedException e) {
                e.printStackTrace();
            }
    
            flag = false;
            System.out.println(flag);
        }
    }
    

    如果按照正常的逻辑,这里是应该是当flag = true的时候一直进入while,但是将flag = false

    修改之后结束循环

    但是事实却是当flag输出为False的时候程序还没有结束


    Volatile的三大特点

    保证可见性

    对上面的代码进行改造,假如volatile关键字

    import java.util.concurrent.TimeUnit;
    
    public class volatileDemo {
    
        private volatile static boolean flag = true;
    
        public static void main(String[] args) {
    
            new Thread(()->{
                while (flag){
                }
            }).start();
    
            try {
                TimeUnit.SECONDS.sleep(2);
            } catch (InterruptedException e) {
                e.printStackTrace();
            }
    
            flag = false;
            System.out.println(flag);
        }
    }
    

    当控制台打印出:flag的时候,程序就已经停止

    Volatile不需要进行阻塞即可将结果展示给所有的线程,所以不用线程阻塞

    不保证原子性

    原子性:操作不可分割,要么一起完成,要么不完成

    //不保证原子性
    public class volatileDemo2 {
    
        private volatile static int num = 0;
    
        public static void add(){
            num++;
        }
    
        public static void main(String[] args) {
            for (int i = 1; i <= 100; i++) {
                new Thread(()->{
                    for (int i1 = 0; i1 < 100; i1++) {
                        add();
                    }
                }).start();
            }
    
    
            while (Thread.activeCount()>2){//假如除了main线程和gc线程之外还有其他的线程
                Thread.yield();//那么就让main线程去礼让其他的线程,让他们跑完
            }
    
            System.out.println(num);
        }
    }
    //这次是9907
    

    我们可以看到了,volatil3不能保证原子操作

    我们通过:javap -c volatileDemo2.class来将class文件反编译,查询到:

    1586331275766

    1. 获得这个值
    2. 执行+1
    3. 写会这个值

    那么不加lock或者synchronized,如何保证原子性操作?

    volatile不可以保证原子操作,那么可以使用lock锁或者synchronized保证原子操作,但是我们要说一个更厉害的:

    java.util.concurrent.atomic 中,都是原子操作,其中有一个AtomicInteger

    那么我们对这个程序进行更改

    import java.util.concurrent.atomic.AtomicInteger;
    
    //不保证原子性
    public class volatileDemo2 {
    
    //    private volatile static int num = 0;
        private volatile static AtomicInteger num = new AtomicInteger();
    
        public static void add(){
            num.getAndIncrement();//执行+1操作
        }
    
        public static void main(String[] args) {
            for (int i = 1; i <= 100; i++) {
                new Thread(()->{
                    for (int i1 = 0; i1 < 100; i1++) {
                        add();
                    }
                }).start();
            }
    
            while (Thread.activeCount()>2){//假如除了main线程和gc线程之外还有其他的线程
                Thread.yield();//那么就让main线程去礼让其他的线程,让他们跑完
            }
    
            System.out.println(num);
        }
    }
    

    不论怎么测试,这个就变成了10000,不再变化,说明这才是保证的原子操作

    那么为什么atomic这么厉害可以保证原子操作?

    因为这些类的底层都和操作系统挂钩,在内存中修改值

    禁止指令重排

    指令重排:计算机并不是按照你写的程序进行执行的

    从源代码到执行之间的可能性:编译器优化的重排,指令并行可能会重排,内存系统可能会重排

    案例一:

    比如:我写了一个

    int x = 1;	//第1步
    int y = 2;	//第2步
    x = x + 5;	//第3步
    y = x * x;	//第4步
    

    我们期望的是 1234,但是在计算机中可能会被重新排列为2134,1324

    但是不会为4123

    因为不管是2134还是1324都不会对数据产生影响,但是4123会对数据产生影响

    处理器在考虑指令重排的时候会考虑数据之间的相互依赖

    案例二:

    但是在多线程下看一个可能造成影响的结果:有 a b x y,这四个默认值都是0

    线程A 线程B
    x = a y = b
    b = 1 a = 2

    正常的结果是:x = 0,y =0

    但是指令重排之后可能会造成线程A中和线程B中的顺序进行颠倒

    线程A 线程B
    b = 1 a = 2
    x = a y = b

    重排之后的诡异结果:x = 2,y = 1

    平时的时候是不会出现这种问题的,但是在理论上这个问题是存在的,所以即使这个问题没有出现我们也要预防可能发生的结果

    volatile可以避免指令重排的依仗:

    在计算机中存在着一个叫做内存屏障的东西

    内存屏障的作用:可以保证特定的执行顺序

    可以保证某些变量的内存可见性(volatile就是利用这些特性实现了可见性)

    volatile就是在他之前和之后都加上了内存屏障

    1586332016523


    单例模式

    饿汉式单例

    //饿汉式单例
    public class Hungry {
        
        
        private byte[] data1 = new byte[1024*1024];
        private byte[] data2 = new byte[1024*1024];
        private byte[] data3 = new byte[1024*1024];
        private byte[] data4 = new byte[1024*1024];
    
        private Hungry() {
        }
    
        //一开始就创建了,可能会浪费空间
        private static final Hungry HUNGRY = new Hungry();
        
        public static Hungry getInstance(){
            return HUNGRY;
        }
    }
    

    单例模式首先要进行构造器私有


    懒汉式单例

    //DCL懒汉式单例
    public class Lazy {
    
        public Lazy() {
        }
    
        private static Lazy lazy;
    
        public static Lazy getInstance(){
            if (lazy!=null){
                lazy = new Lazy();
            }
            return lazy;
        }
    }
    

    饿汉式单例虽然浪费资源,但是在多线程的情况下貌似是没有问题的,但是懒汉式单例在多线程下可能就出现了问题

    //DCL懒汉式单例
    public class LazyMan {
    
        private LazyMan() {
            System.out.println(Thread.currentThread().getName()+"   ok");
        }
    
        private static LazyMan lazyMan;
    
        public static LazyMan getInstance(){
            if (lazyMan ==null){
                lazyMan = new LazyMan();
            }
            return lazyMan;
        }
    
        public static void main(String[] args) {
            for (int i = 0; i < 10; i++) {
                new Thread(()->{
                    LazyMan.getInstance();
                }).start();
            }
        }
    }
    //Thread-2   ok
    //Thread-1   ok
    //Thread-0   ok
    

    很明显,多线程下单例是有问题的,构造方法执行了三次,他们拿到的并不是一个,单例模式失败了


    我们可以使用锁来解决多线程的一个夺取问题

        public static LazyMan getInstance(){
            if (lazyMan ==null){
                synchronized (LazyMan.class){
                    if (lazyMan==null){
                        lazyMan = new LazyMan();
                    }
                }
            }
            return lazyMan;
        }
    

    使用双重检测锁模式来检测DCL懒汉式单例,我们再次运行,发现没有问题了,从始至终都只有一个Thread-0 ok,说明构造方法只执行了一次,单例模式执行Ok

    但是这样其实并不保险,因为lazyMan = new LazyMan()它不是一个原子性操作,至少要执行三步:

    1. 分配内存空间
    2. 执行构造方法初始化对象
    3. 把对象指向空间

    这个时候也有可能出现指令重排的现象,比如改为132

    对于多线程模式下者就完蛋了,因为可能A线程执行了13,然后B线程来了,发现lazyMan!=null,这个时候直接return lazyMan,但是事实上还没有完成构造,这就完蛋了

    所以为了防止指令重排,必须加上volatile

    private LazyMan() {
        System.out.println(Thread.currentThread().getName()+"   ok");
    }
    
    private static volatile LazyMan lazyMan;
    
    public static LazyMan getInstance(){
        if (lazyMan ==null){
            synchronized (LazyMan.class){
                if (lazyMan==null){
                    lazyMan = new LazyMan();
                }
            }
        }
        return lazyMan;
    }
    

    这才是最终的双重检测锁模式


    静态内部类

    静态内部类实现

    //静态内部类实现
    public class Holder {
        private Holder(){}
    
        public static Holder getInstance(){
            return InnerClass.HOLDER;
        }
    
        public static class InnerClass{
            private static final Holder HOLDER = new Holder();
        }
    }
    

    静态内部类看起来虽然不错,但是其实并没有什么卵用


    利用反射破解单例模式

    单例模式其实都是不安全的,比如我们现在要破解DCL懒汉式单例

    import java.lang.reflect.Constructor;
    
    //懒汉式单例
    public class LazyMan {
    
        private LazyMan() {}
    
        private volatile static LazyMan lazyMan;
    
        public static LazyMan getInstance(){
            if (lazyMan ==null){
                synchronized (LazyMan.class){
                    if (lazyMan==null){
                        lazyMan = new LazyMan();
                    }
                }
            }
            return lazyMan;
        }
    
        public static void main(String[] args) throws Exception {
            LazyMan lazyMan1 = LazyMan.getInstance();//普通方法获得
    
            //利用反射破坏单例获得
            Constructor<LazyMan> declaredConstructor = LazyMan.class.getDeclaredConstructor(null);
            declaredConstructor.setAccessible(true);
            LazyMan lazyMan2 = declaredConstructor.newInstance();
    
            System.out.println(lazyMan1);//com.bean.single.LazyMan@74a14482
            System.out.println(lazyMan2);//com.bean.single.LazyMan@1540e19d
    
        }
    }
    

    现在看的很清楚了,他们两个不一样,单例被破坏了

    解决单例的破坏问题:加锁

    //懒汉式单例
    public class LazyMan {
    
        private LazyMan() {
            synchronized (LazyMan.class){
                if (lazyMan!=null){
                    throw new RuntimeException("不要使用反射破坏");
                }
            }
        }
    
        private volatile static LazyMan lazyMan;
    
        public static LazyMan getInstance(){
            if (lazyMan ==null){
                synchronized (LazyMan.class){
                    if (lazyMan==null){
                        lazyMan = new LazyMan();
                    }
                }
            }
            return lazyMan;
        }
    
        public static void main(String[] args) throws Exception {
            LazyMan lazyMan1 = LazyMan.getInstance();//普通方法获得
    
            //利用反射破坏单例获得
            Constructor<LazyMan> declaredConstructor = LazyMan.class.getDeclaredConstructor(null);
            declaredConstructor.setAccessible(true);
            LazyMan lazyMan2 = declaredConstructor.newInstance();
    
            System.out.println(lazyMan1);
            System.out.println(lazyMan2);
    
        }
    }
    

    我们直接在类模板上加了一个锁,普通情况下已经有了lazyMan的时候不会执行构造方法,但是现在还是执行了

    说明有人在用反射破坏

    执行之后发现抛出了异常,没有问题java.lang.RuntimeException: 不要使用反射破坏

    现在成为了三重检测

    虽然这样检测,其实还是可以破坏的

    LazyMan lazyMan1 = LazyMan.getInstance();
    

    我们之前看到的第一个对象是用这种普通方法去创建的,所以会执行构造方法

    但是现在我不用这个方法,我直接不创建这个对象,直接使用方法,那单例模式就又被破坏了

        public static void main(String[] args) throws Exception {
    //        LazyMan lazyMan1 = LazyMan.getInstance();//普通方法获得
    
            //利用反射破坏单例获得
            Constructor<LazyMan> declaredConstructor = LazyMan.class.getDeclaredConstructor(null);
            declaredConstructor.setAccessible(true);
    
            LazyMan lazyMan1 = declaredConstructor.newInstance();//现在直接不创建对象,直接使用这个方法,那单例完蛋了
            LazyMan lazyMan2 = declaredConstructor.newInstance();
    
            System.out.println(lazyMan1);//com.bean.single.LazyMan@74a14482
            System.out.println(lazyMan2);//com.bean.single.LazyMan@1540e19d
    
        }
    

    即便是这样,还是有办法来解决的:

    使用红绿灯办法,设置一个flag变量,在不知道这个变量的情况下,反射还是不能破坏

    //懒汉式单例
    public class LazyMan {
    
        private static boolean flag = false;
    
        private LazyMan() {
            synchronized (LazyMan.class){
                if (flag==false){
                    flag = true;
                }else {
                    throw new RuntimeException("不要使用反射破坏");
                }
            }
        }
    
        private volatile static LazyMan lazyMan;
    
        public static LazyMan getInstance(){
            if (lazyMan ==null){
                synchronized (LazyMan.class){
                    if (lazyMan==null){
                        lazyMan = new LazyMan();
                    }
                }
            }
            return lazyMan;
        }
    
        public static void main(String[] args) throws Exception {
    //        LazyMan lazyMan1 = LazyMan.getInstance();//普通方法获得
    
            //利用反射破坏单例获得
            Constructor<LazyMan> declaredConstructor = LazyMan.class.getDeclaredConstructor(null);
            declaredConstructor.setAccessible(true);
    
            LazyMan lazyMan1 = declaredConstructor.newInstance();
            LazyMan lazyMan2 = declaredConstructor.newInstance();
    
            //java.lang.RuntimeException: 不要使用反射破坏
            System.out.println(lazyMan1);
            System.out.println(lazyMan2);
    
        }
    }
    

    不管是不是要创建对象,构造方法总是要走的,只要一走构造方法,flag就会变为true,然后就可以防止反射被破坏

    但是这样依旧不安全,我们可以使用反编译的方式查看这个变量。

        public static void main(String[] args) throws Exception {
    
            Constructor<LazyMan> declaredConstructor = LazyMan.class.getDeclaredConstructor(null);
            declaredConstructor.setAccessible(true);
    
            LazyMan lazyMan1 = declaredConstructor.newInstance();
    
    
            Field flag = LazyMan.class.getDeclaredField("flag");
            flag.setAccessible(true);
            flag.set(lazyMan1,false);
    
            LazyMan lazyMan2 = declaredConstructor.newInstance();
    
    
            System.out.println(lazyMan1);
            System.out.println(lazyMan2);
    
        }
    

    单例又被破坏了

    这样还是可以防止的:我们可以使用枚举防止单例被破坏,反射是不能破坏枚举的

    public enum  EnumSingle {
        INSTANCE;
        
        public EnumSingle getInstance(){
            return INSTANCE;
        }
    }
    

    我们通过查看它的class文件可以发现它是一个无参构造

    public enum EnumSingle {
    INSTANCE;
    
    private EnumSingle() {
    }
    
    public EnumSingle getInstance() {
    return INSTANCE;
    }
    }
    
    class Test{
        public static void main(String[] args) throws Exception {
            EnumSingle instance = EnumSingle.INSTANCE;
    
            Constructor<EnumSingle> declaredConstructor = EnumSingle.class.getDeclaredConstructor(null);
            declaredConstructor.setAccessible(true);
            EnumSingle enumSingle = declaredConstructor.newInstance();
        }
    }
    

    java.lang.NoSuchMethodException: com.bean.single.EnumSingle.<init>()

    报错了,他说没有这个构造方法

    问题来了,明明有这个构造方法,却说没有,这个说明class文件欺骗了我们,通过反编译javap -c xxx.class来查看

    1586397750388

    发现还是空参方法,说明这个也骗了我们

    那么使用专业的工具jad.exe,反编译成java文件,查看出来:

    1586397834877

    构造方法变了,不是空参的构造方法

    那就好办了,继续破解

    class Test{
        public static void main(String[] args) throws Exception {
            EnumSingle instance = EnumSingle.INSTANCE;
    
            Constructor<EnumSingle> declaredConstructor = EnumSingle.class.getDeclaredConstructor(String.class,int.class);
            declaredConstructor.setAccessible(true);
            EnumSingle enumSingle = declaredConstructor.newInstance();
        }
    }
    

    执行,发现错误变了: Cannot reflectively create enum objects

    反射不能破坏枚举对象

    到此为止,交锋结束了

    枚举确实好使

    CAS

    什么是CAS

    CAS:比较当前内存中的值和主内存中的值,如果这个值是期望的,那么则执行操作,如果不是则一直循环

    import java.util.concurrent.atomic.AtomicInteger;
    
    //CAS:compare and set-->比较并交换
    //CAS是CPU的并发原语
    public class CASDemo {
    
        public static void main(String[] args) {
            //之前的AtomicInteger其实就是使用了CAS
            AtomicInteger atomicInteger = new AtomicInteger(2020);
    
            //期望,更新
            //public final boolean compareAndSet(int expect,int update)
            //如果我期望的值达到了,那么就更新,否则就不更新
            System.out.println(atomicInteger.compareAndSet(2020, 2021));
            System.out.println(atomicInteger.get());//2021
    
            //再次修改就会修改失败,因为只有2020才能够修改为2021,但是上面已经修改为了2021
            System.out.println(atomicInteger.compareAndSet(2020, 2021));
        }
    }
    

    Unsafe类

    之前我们使用过AtomicInteger的+1操作,现在来看一下底层

        public final int getAndIncrement() {
            return unsafe.getAndAddInt(this, valueOffset, 1);
        }
        
    
        private static final Unsafe unsafe = Unsafe.getUnsafe();
        private static final long valueOffset;
    
        static {
            try {
                valueOffset = unsafe.objectFieldOffset	//获得内存地址偏移值
                    (AtomicInteger.class.getDeclaredField("value"));
            } catch (Exception ex) { throw new Error(ex); }
        }
    
        private volatile int value;	//避免指令重排
    

    unsafe类:

    • Java不可以操作内存
    • Java可以调用c++
    • c++可以操作内存

    所以这个类是Java的后门,可以通过这个类来操作内存

    1586399188659

    getAndAddInt:当前对象,当前地址偏移值,要增加的值

    所以var5就是获取当前对象的地址偏移值,也就是获取内存地址中的值

    compareAndSwapInt(var1, var2, var5, var5 + var4)

    如果当前的地址偏移值还是我期望的那个地址偏移值,那么就让他+v4

    如果var1+var2还是期望的那个地址偏移值var5,那么就让var5+var4

    所以这是一个内存操作,效率非常高

    atomicInteger.getAndIncrement()所以这是一个标准的自旋锁


    CAS的优缺点

    优点:自带原子性

    缺点:

    1. 循环会耗时,但是比java强多了
    2. 一次性只能保证一个共享变量的原子性
    3. ABA问题

    CAS:ABA问题(狸猫换太子)

    现在有一个资源A = 1,两条线程

    线程1期望A=1,要通过CAS修改为2

    线程2也期望A=1,要通过CAS修改为3

    线程3期望A=3,要通过CAS修改为1

    那么现在:线程2因为操作比较快,将A修改为了3,然后又被线程3修改回了1

    这个过程,线程1是不知情的,它拿到了资源A,那么这个时候A = 1

    这个线程1以为A还是之前那个A,但是其实已经被动过手脚了

    这就是ABA问题

    测试

    import java.util.concurrent.atomic.AtomicInteger;
    
    //CAS:compare and set-->比较并交换
    //CAS是CPU的并发原语
    public class CASDemo {
    
        public static void main(String[] args) {
    
            AtomicInteger atomicInteger = new AtomicInteger(2020);
            
    
            /*==================捣乱的线程==================*/
            System.out.println(atomicInteger.compareAndSet(2020, 2021));
            System.out.println(atomicInteger.get());
            System.out.println(atomicInteger.compareAndSet(2021, 2020));
            System.out.println(atomicInteger.get());
    
            /*==================期望的线程==================*/
            System.out.println(atomicInteger.compareAndSet(2020, 6666));
            System.out.println(atomicInteger.get());
    
    
        }
    }
    

    对于我们写的SQL,只要写上一把乐观锁,就可以解决这个问题

    原子引用解决ABA

    带版本号的原子操作

    java.util.concurrent.atomic.AtomicReference:原子引用

    //CAS:compare and set-->比较并交换
    //CAS是CPU的并发原语
    public class CASDemo {
    
        public static void main(String[] args) {
    
    //        AtomicInteger atomicInteger = new AtomicInteger(2020);
    
            //替换AtomicInteger,使用带有版本号的AtomicStampedReference,有两个参数:期望值,版本号
            /*
                注意,这里的Integer,Integer使用了对象缓存机制,默认范围是-128~127,推荐使用静态工厂方法valueOf获取对象实例,而不是new
                因为valueOf使用缓存,new一定会创建新的对象分配新的内存空间
                因为一开始的默认值是2020,超出了Integer的范围,所以出现了错误
    
                而且所有相同类型的包装类之间的比较要全部使用equals,这是因为在-128~127之间是从缓存中产生的,在这个区间中会复用已有对象,可以使用==
                但是超过了这个区间就会在堆上产生,并不会复用已有的对象,那么指向的并不是一个,所以会出现问题
    
               这是一个大坑
    
                所以泛型如果是一个包装类,就要注意对象的引用问题
             */
            AtomicStampedReference<Integer> atomicStampedReference = new AtomicStampedReference<>(1,1);
    
            new Thread(()->{
    
                System.out.println("A1  ==>"+atomicStampedReference.getStamp());//获取版本号
                try {
                    TimeUnit.SECONDS.sleep(1);
                } catch (InterruptedException e) {
                    e.printStackTrace();
                }
                //期望的值为1,修改的值为2。修改值并执行版本号+1
                System.out.println(atomicStampedReference.compareAndSet(1, 2, atomicStampedReference.getStamp(), atomicStampedReference.getStamp() + 1));
    
                System.out.println("A2  ==>"+atomicStampedReference.getStamp());//获取版本号
    
                //再把值改回去
                System.out.println(atomicStampedReference.compareAndSet(2, 1, atomicStampedReference.getStamp(), atomicStampedReference.getStamp() + 1));
    
                System.out.println("A3  ==>"+atomicStampedReference.getStamp());//获取版本号
            },"A").start();
    
            new Thread(()->{
                System.out.println("B1  ==>"+atomicStampedReference.getStamp());//获取版本号
                try {
                    TimeUnit.SECONDS.sleep(2);
                } catch (InterruptedException e) {
                    e.printStackTrace();
                }
    
                System.out.println(atomicStampedReference.compareAndSet(1, 6, atomicStampedReference.getStamp(), atomicStampedReference.getStamp() + 1));
    
                System.out.println("B2  ==>"+atomicStampedReference.getStamp());
            },"B").start();
    
    
        }
    
    
    }
    

    公平锁,非公平锁

    公平锁:非常公平,一个队列不能插队

    非公平锁:可以插队

    默认都是非公平

    1586507458158

    在前面的Lock锁就已经讲过了

    可重入锁

    所有的锁都是可重入锁,也叫递归锁

    1586507546942

    //可重入锁
    public class Demo01 {
        public static void main(String[] args) {
    
            Phone phone = new Phone();
    
            new Thread(()->{
                phone.sms();
            },"A").start();
    
            new Thread(()->{
                phone.sms();
            },"B").start();
        }
    }
    
    class Phone{
        public synchronized void sms(){
            System.out.println(Thread.currentThread().getName()+"sms");
            call();//这里也有锁
        }
    
        public synchronized void call(){
            System.out.println(Thread.currentThread().getName()+"call");
        }
    }
    /*
        Asms
        Acall
        Bsms
        Bcall
    */
    

    在上面我们可以知道,无论执行多少次,A都会先执行完,然后释放锁

    这里A获得的是两个锁:sms方法和call方法的锁

    这就是可重入锁,A获取了sms的锁之后自动获得call的锁

    使用Lock锁也是一样的结果,本质上是一样的

    import java.util.concurrent.locks.Lock;
    import java.util.concurrent.locks.ReentrantLock;
    
    //可重入锁
    public class Demo02 {
        public static void main(String[] args) {
    
            Phone phone = new Phone();
    
            new Thread(()->{
                phone.sms();
            },"A").start();
    
            new Thread(()->{
                phone.sms();
            },"B").start();
        }
    }
    
    class Phone2{
        Lock lock = new ReentrantLock();
        public void sms(){
            lock.lock();//加锁
            try {
                System.out.println(Thread.currentThread().getName()+"sms");
                call();//这里也有锁
            } catch (Exception e) {
                e.printStackTrace();
            } finally {
                lock.unlock();//解锁
            }
        }
    
        public synchronized void call(){
            lock.lock();    //加锁,注意这里的锁和上面的锁不是同一把锁
            try {
                System.out.println(Thread.currentThread().getName()+"call");
            } catch (Exception e) {
                e.printStackTrace();
            } finally {
                lock.unlock();//解锁,注意这里的锁和上面的锁不是同一把锁
            }
        }
    }
    

    自旋锁

    spinlock,自旋锁,这种锁会不断循环,直到成功

    1586508209025

    //自旋锁
    public class SpinlockDemo {
        AtomicReference<Thread> atomicReference = new AtomicReference<>();
    
        //加锁
        public void myLock(){
            Thread thread = Thread.currentThread();//获取当前的线程
    
            System.out.println(Thread.currentThread().getName()+" ==> mylock");
    
            //自旋锁,使用CAS,我们的期望值为null,然后设为thread
            //这个CAS操作返回肯定是false,所以我们对他进行取反,让它一直循环
            while (!atomicReference.compareAndSet(null,thread)){
    
            }
        }
    
        //解锁
        public void myUnLock(){
            Thread thread = Thread.currentThread();//获取当前的线程
    
            System.out.println(Thread.currentThread().getName()+"==>myUnLock");
    
            //加锁自旋,解锁就不需要了
            //我们期望值为thread当前线程,把它置为空
            //以达到lock锁while循环解锁的条件
            atomicReference.compareAndSet(thread,null);
        }
    }
    
    import java.util.concurrent.TimeUnit;
    
    
    public class TestSpinLock {
        public static void main(String[] args) {
            //第层使用了自旋锁,使用CAS操作
            SpinlockDemo lock = new SpinlockDemo();
    
            new Thread(()->{
                lock.myLock();
                try {
                    TimeUnit.SECONDS.sleep(5);//休息一下
                } catch (InterruptedException e) {
                    e.printStackTrace();
                } finally {
                    lock.myUnLock();
                }
    
            },"T1").start();
    
            //我们休息一秒钟,保证t1可以先获取到锁
            try {
                TimeUnit.SECONDS.sleep(1);
            } catch (InterruptedException e) {
                e.printStackTrace();
            }
    
            new Thread(()->{
                lock.myLock();
                try {
                    TimeUnit.SECONDS.sleep(1);//t2也休息一下
                } catch (InterruptedException e) {
                    e.printStackTrace();
                } finally {
                    lock.myUnLock();
                }
            },"T2").start();
    
        }
    }
    /*
        T1 ==> mylock
        T2 ==> mylock
        T1==>myUnLock
        T2==>myUnLock
     */
    

    这样答案很明显了

    只有当T1释放了锁之后,T2才有资格释放锁

    情况是这样的:

    t1首先执行了mylock的输出,然后使用了CAS

    然后t2执行了mylock的输出,但是因为CAS正在修改t1,所以t2一直在等待

    然后t1执行了unlock

    t2使用了CAS,然后执行了unlock

    如果不信,可以吧lock输出放到while之后,然后再执行一遍看看

    死锁

    什么是死锁

    死锁就是两个线程互相抢对方的锁

    1586510224359

    怎么排除死锁

    解决问题

    1. 使用 jps -l定位进程号

      1586510509670

    2. 使用jstack 进程号找到死锁问题

      1586510587004

    面试或者工作中,出现问题可以查看:日志,堆栈信息

  • 相关阅读:
    Git SSH Key 生成步骤
    IOS音频1:之采用四种方式播放音频文件(一)AudioToolbox AVFoundation OpenAL AUDIO QUEUE
    IOS网络篇1之截取本地URL请求(NSURLProtocol)
    IOS 视频直播/智能家居(一行行敲代码,从零开始)lesson:1整体架构
    iOS应用支持IPV6,就那点事儿
    App store最新审核标准公布
    iOS应用内付费(IAP)开发步骤列表
    iOS应用内支付(IAP)的那些坑
    IOS 第三方支付的使用:支付宝
    亲们,委托你们是否已忘记
  • 原文地址:https://www.cnblogs.com/howling/p/14230603.html
Copyright © 2011-2022 走看看