JUC并发编程-B站【狂神说Java笔记】

狂神视频地址:https://www.bilibili.com/video/BV1B7411L7tE

准备工作

  1. 新建一个Maven项目,引入一个lombok依赖.
 <dependencies>
        <dependency>
            <groupId>org.projectlombok</groupId>
            <artifactId>lombok</artifactId>
            <version>1.18.8</version>
        </dependency>
    </dependencies>
  1. 项目配置
    JDK8
    在这里插入图片描述
    在这里插入图片描述
    在这里插入图片描述
  2. 下载JDK帮助文档

1. 什么是JUC

Java.util.concurrent
在这里插入图片描述

2. 线程和进程

进程:一个程序,QQ.exe Music.exe 程序的集合;
一个进程往往可以包含多个线程,至少包含一个!
Java默认有几个线程? 2 个 :mian、GC。

线程:开了一个进程 Typora,写字,自动保存(线程负责的)。

2.1 Java 真的可以开启线程吗?

java是开不了线程的

new Thread().start();
public synchronized void start() {
        /**
         * This method is not invoked for the main method thread or "system"
         * group threads created/set up by the VM. Any new functionality added
         * to this method in the future may have to also be added to the VM.
         *
         * A zero status value corresponds to state "NEW".
         */
        if (threadStatus != 0)
            throw new IllegalThreadStateException();

        /* Notify the group that this thread is about to be started
         * so that it can be added to the group's list of threads
         * and the group's unstarted count can be decremented. */
        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 */
            }
        }
    }
	//调用本地方法,底层的C++,JAVA无法直接操作硬件
    private native void start0();

首先。start()是一个synchronized方法,同步方法,安全,这个方法会把当前线程加入一个线程组,调用了start0()方法,这个start0()是用native修饰的,也就是本地方法。所以最后是调用了本地方法,JAVA是没有权限开启线程的。start()调用了本地的C++方法,因为java是运行在虚拟机之上的,无法直接操作硬件。

2.2 并发、并行

并发编程:并发,并行
并发:多个线程操作同一个资源

  • 并发:cpu只有一个核:多线程操作同一个资源(cpu通过线程间的快速交替,模拟出来多条线程,看似并行,实际串行)
  • 并行:cpu有多个核,多个线程可以同时执行,可以通过线程池完成
public class Demo01 {
    public static void main(String[] args) {
        //获取CPU核数
        //CPU 密集型,IO密集型
        System.out.println(Runtime.getRuntime().availableProcessors());
    }
}

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

2.3 线程状态

public enum State {
        //新生
        NEW,

        //运行
        RUNNABLE,

       //阻塞
        BLOCKED,

        //等待(死死的等)
        WAITING,

        //超时等待(到期就不等了)
        TIMED_WAITING,

       //终止
        TERMINATED;
    }

2.4 wait/sleep区别

  1. 来自不同的类
    wait()来自Object类,sleep()来自Thread类
  2. 关于锁的释放
    wait()会释放锁,sleep()不会释放锁,可以理解为抱着锁睡觉
  3. 使用范围不同
    wait()只能在同步代码块中使用,sleep()可以在任何地方使用
  4. 是否需要捕获异常
    wait()不需要捕获异常,sleep()必须要捕获异常

3. LOCL锁(重点)

3.1 传统的synchronized锁

在公司真正的多线程开发中,线程就是一个单独的资源类,没有任何附属的操作(类中只有属性和方法),为了降低耦合性,不会用类去实现接口,因为实现类接口就不是OOP编程了,而且实现了接口的话耦合性变高,如果让类实现了Runnable,这个类就只是一个线程类了

如下代码,只是把Ticket作为了资源类,并没有让它实现Runnable接口

/**
 * 真正的多线程开发,公司中的开发,降低耦合性
 * 线程就是一个单独的资源类,没有认合附属操作
 * 1. 属性,方法
*/
//基本的卖票例子
public class SaleTicket01 {
    public static void main(String[] args) {
        //多线程操作
        //并发:多个线程操作同一个资源类,把资源类丢入线程
        Ticket ticket = new Ticket();

        // @FunctionalInterface 函数式接口,JDK1.8 Lambda表达式
        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编程
class Ticket {
    //属性,方法

    //票数
    private int num = 30;

    //卖票方式
    // synchronized 本质:队列,锁
    public synchronized void sale() {
        if (num > 0) {
            System.out.println(Thread.currentThread().getName()+"卖出了第"+(num--)+"张票,剩余:"+num);
        }
    }
}

3.2 Lock锁

在这里插入图片描述
在这里插入图片描述
在这里插入图片描述
公平锁:十分公平,先来后到,排队
非公平锁:不公平,可以插队
(默认是非公平锁,是为了公平,比如一个线程要3s,另一个线程要3h,难道一定要让3h的锁先来就先执行吗)

public class SaleTicket02 {
    public static void main(String[] args) {
        //并发:多个线程操作同一个资源类,把资源类丢入线程
        Ticket2 ticket = new Ticket2();
        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三部曲
// 1. new ReentrantLock()
// 2. lock.lock(); //加锁
// 3. finally => lock.unlock(); //解锁
class Ticket2 {
    //属性,方法
    private int num = 30;

    Lock lock = new ReentrantLock();

    public void sale() {
        lock.lock(); //加锁

        try {
            // 业务代码
            if (num > 0) {
                System.out.println(Thread.currentThread().getName()+"卖出了第"+(num--)+"张票,剩余:"+num);
            }
        } catch (Exception e) {
            e.printStackTrace();
        } finally {
            lock.unlock(); //解锁
        }
    }
}

3.3 Synchronized和Lock锁的区别

  1. Synchronized:内置的Java关键字,Lock:是一个java类
  2. Synchronized:无法判断获取锁的状态,Lock:可以判断是否获取了锁
  3. Synchronized:会自动释放锁,Lock:必须要手动释放锁,如果不释放锁,会死锁
  4. Synchronized:线程1(获得锁,阻塞),线程2(等待,傻傻的等),Lock:Lock锁不一定会等待,Lock有一个方法Lock.tryLock();会去尝试获取锁
  5. Synchronized:可重入锁,不可以在中断,非公平, Lock:可重入,可以判断锁,可以设置公平或者非公平
  6. Synchronized:适合锁少量的代码同步问题,Lock:适合锁大量的同步代码

4. 生产者消费者问题

4.1 synchronized版

/**
 * 线程之间的通信问题:生产者消费者问题
 * 线程交替执行 A B 操作同一个变量 num=0
 * 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();
    }
}

//判断等待,业务,通知
class Data{ //数字,资源类
    private int num = 0;

    // +1
    public synchronized void increment() throws InterruptedException {
        if (num != 0) {
            //等待
            this.wait();
        }
        num++;
        System.out.println(Thread.currentThread().getName()+"=>"+num);
        //通知其他线程,我+1完了
        this.notifyAll();
    }

    // -1
    public synchronized void decrement() throws InterruptedException {
        if (num == 0) {
            //等待
            this.wait();
        }
        num--;
        System.out.println(Thread.currentThread().getName()+"=>"+num);
        //通知其他线程,我-1完了
        this.notifyAll();
    }
}

synchronized版本存在的问题
如果增加两个线程,即两个线程加,两个线程减,得到如下结果,并不能1,0交替,而且出现了2,3

new Thread(()->{
            for (int i = 0; i < 10; i++) {
                try {
                    data.increment();
                } catch (InterruptedException e) {
                    e.printStackTrace();
                }
            }
        },"C").start();
        new Thread(()->{
            for (int i = 0; i < 10; i++) {
                try {
                    data.decrement();
                } catch (InterruptedException e) {
                    e.printStackTrace();
                }
            }
        },"D").start();

在这里插入图片描述
问题原因分析
看jdk1.8的官方文档,找到Object类的wait()方法

导致当前线程等待,直到另一个线程调用此对象的notify()方法或notifyAll()方法,或指定的时间已过。
线程也可以唤醒,而不会被通知,中断或超时,即所谓的虚假唤醒 。 虽然这在实践中很少会发生,但应用程序必须通过测试应该使线程被唤醒的条件来防范,并且如果条件不满足则继续等待。 换句话说,等待应该总是出现在循环中,就像这样:

         while (<condition does not hold>)
             obj.wait(timeout);
         ... // Perform action appropriate to condition
     }  ```

结论
就是用if判断的话,唤醒后线程会从wait之后的代码开始运行,不会重新判断if条件,直接继续运行if代码块之后的代码,而如果使用while的话,也会从wait之后的代码运行,但是唤醒后会重新判断循环条件,如果不成立再执行while代码块之后的代码块,成立的话继续wait。

解决方法
把if判断改成while判断等待,因为if判断进if之后不会停,用while判断的话,变量一旦被修改,另外一个线程拿到锁之后,就会等待,防止虚假唤醒。

public synchronized void increment() throws InterruptedException {
        while (num != 0) {
            //等待
            this.wait();
        }
        num++;
        System.out.println(Thread.currentThread().getName()+"=>"+num);
        //通知其他线程,我+1完了
        this.notifyAll();
    }

在这里插入图片描述

4.2 JUC版(lock锁)

在这里插入图片描述

public class B {
    public static void main(String[] args) {
        Data2 data = new Data2();
        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();
        new Thread(()->{
            for (int i = 0; i < 10; i++) {
                try {
                    data.increment();
                } catch (InterruptedException e) {
                    e.printStackTrace();
                }
            }
        },"C").start();
        new Thread(()->{
            for (int i = 0; i < 10; i++) {
                try {
                    data.decrement();
                } catch (InterruptedException e) {
                    e.printStackTrace();
                }
            }
        },"D").start();
    }
}

class Data2{
    private int num = 0;

    Lock lock = new ReentrantLock();
    Condition condition = lock.newCondition();

    // +1
    public void increment() throws InterruptedException {
        lock.lock();
        try {
            while (num != 0) {
                //等待
                condition.await();
            }
            num++;
            System.out.println(Thread.currentThread().getName()+"=>"+num);
            //通知其他线程,我+1完了
            condition.signalAll();
        } catch (Exception e) {
            e.printStackTrace();
        } finally {
            lock.unlock();
        }
    }

    // -1
    public void decrement() throws InterruptedException {
        lock.lock();
        try {
            while (num == 0) {
                //等待
                condition.await();
            }
            num--;
            System.out.println(Thread.currentThread().getName()+"=>"+num);
            //通知其他线程,我-1完了
            condition.signalAll();
        } catch (Exception e) {
            e.printStackTrace();
        } finally {
            lock.unlock();
        }
    }
}

任何一个新的技术,绝对不是仅仅只是覆盖了原来的技术,优势和补充!

Condition 精准的通知和唤醒线程

//A执行完调用B,B执行完调用C,C执行完调用A
public class C {
    public static void main(String[] args) {
        Data3 data = new Data3();
        new Thread(()->{ for (int i = 0; i < 10; i++) data.printA(); },"A").start();
        new Thread(()->{ for (int i = 0; i < 10; i++) data.printB(); },"B").start();
        new Thread(()->{ for (int i = 0; i < 10; i++) data.printC(); },"C").start();
    }
}

class Data3{
    private Lock lock = new ReentrantLock();
    private Condition condition1 = lock.newCondition();
    private Condition condition2 = lock.newCondition();
    private Condition condition3 = lock.newCondition();
    private int num = 1; //1A 2B 3C

    public void printA() {
        lock.lock();
        try {
            //业务:判断-执行-通知
            while (num != 1){
                //等待
                condition1.await();
            }
            System.out.println(Thread.currentThread().getName()+"=>AAAAA");
            //唤醒,唤醒指定的人,B
            num = 2;
            condition2.signal();
        } catch (Exception e) {
            e.printStackTrace();
        } finally {
            lock.unlock();
        }
    }

    public void printB() {
        lock.lock();
        try {
            //业务:判断-执行-通知
            while (num != 2){
                //等待
                condition2.await();
            }
            System.out.println(Thread.currentThread().getName()+"=>BBBBB");
            //唤醒,唤醒指定的人,B
            num = 3;
            condition3.signal();
        } catch (Exception e) {
            e.printStackTrace();
        } finally {
            lock.unlock();
        }
    }

    public void printC() {
        lock.lock();
        try {
            //业务:判断-执行-通知
            while (num != 3){
                //等待
                condition3.await();
            }
            System.out.println(Thread.currentThread().getName()+"=>CCCCC");
            //唤醒,唤醒指定的人,B
            num = 1;
            condition1.signal();
        } catch (Exception e) {
            e.printStackTrace();
        } finally {
            lock.unlock();
        }
    }
}

在这里插入图片描述

5. 八锁

锁是什么,如何判断锁的是谁?
8锁,就是关于锁的8个问题

  • 问题1:标准情况下,先发短信,还是先打电话?
/*
 问题1:标准情况下,先打印发短信,还是先打印打电话?结果是:先打印发短信,后打印打电话
 原因:不能回答先调用A线程,这是错误的,不是先调用先执行,这是锁的问题,因为被Synchronized修饰的
 方法,锁的对象是方法的调用者,所以调用两个方法的对象都是phone,但是现在phone只有一个,也就是说着两个方
 法现在用的是同一把锁,谁先拿到,谁就先执行
*/
public class Test1 {
    public static void main(String[] args) {
        phone phone = new phone();
        new Thread(()->{ phone.sendSms(); },"A").start();
        try {
            TimeUnit.SECONDS.sleep(1);
        } catch (Exception e) {
            e.printStackTrace();
        }
        new Thread(()->{ phone.call(); },"B").start();
    }
}

class phone{
    //synchronized 锁的对象是方法的调用者
    public synchronized void sendSms() {
        System.out.println("发短信");
    }
    public synchronized  void call() {
        System.out.println("打电话");
    }
}

结果:先打印发短信,后打印打电话

  • 问题2:给发短信方法加延时4s,程序的执行情况
public synchronized void sendSms() {
        try {
            TimeUnit.SECONDS.sleep(4);
        } catch (InterruptedException e) {
            e.printStackTrace();
        }
        System.out.println("发短信");
    }

结果:经过4s之后打印发短信,然后立马打印打电话。
如果把主函数中延时改为5s,那么程序运行情况是经过4s打印发送短信,然后经过1s打印打电话(主程序的延时是同时进行的)

public class Test1 {
    public static void main(String[] args) {
        phone phone = new phone();
        new Thread(()->{ phone.sendSms(); },"A").start();
        try {
            TimeUnit.SECONDS.sleep(5);
        } catch (Exception e) {
            e.printStackTrace();
        }
        new Thread(()->{ phone.call(); },"B").start();
    }
}
  • 问题3:当调用普通方法,而不是synchronized方法时,程序的执行情况
public class Test1 {
    public static void main(String[] args) {
        phone phone = new phone();
        new Thread(()->{ phone.sendSms(); },"A").start();
        try {
            TimeUnit.SECONDS.sleep(1);
        } catch (Exception e) {
            e.printStackTrace();
        }
        new Thread(()->{ phone.sayHello(); },"B").start();
    }
}

class phone{
    //synchronized 锁的对象是方法的调用者
    public synchronized void sendSms() {
        try {
            TimeUnit.SECONDS.sleep(4);
        } catch (InterruptedException e) {
            e.printStackTrace();
        }
        System.out.println("发短信");
    }
    public synchronized  void call() {
        System.out.println("打电话");
    }
    public  void sayHello()
    {
        System.out.println("hello");
    }
}

结果:1秒先打印Hello,4秒后打印发短信
先执行普通方法,因为普通方法没有锁,不受锁的影响

  • 问题4:两个对象分别调用synchronized方法时,程序的执行情况
public static void main(String[] args) {
		//两个对象,两个调用者,两把锁
        phone phone1 = new phone();
        phone phone2 = new phone();

        new Thread(()->{ phone1.sendSms(); },"A").start();
        try {
            TimeUnit.SECONDS.sleep(1);
        } catch (Exception e) {
            e.printStackTrace();
        }
        new Thread(()->{ phone2.call(); },"B").start();
    }

结果:先打电话,后发短信。
两个对象,两个调用者,两把锁,因为锁不一样,所以耗时短的先输出。

  • 问题5:增加两个静态的同步方法,只有一个对象,程序的执行情况
public class Test1 {
    public static void main(String[] args) {
        phone phone = new phone();

        new Thread(()->{ phone.sendSms(); },"A").start();
        try {
            TimeUnit.SECONDS.sleep(1);
        } catch (Exception e) {
            e.printStackTrace();
        }
        new Thread(()->{ phone.call(); },"B").start();
    }
}

class phone{
    //static 静态方法,类一加载就有了,锁的是Class
    public static synchronized void sendSms() {
        try {
            TimeUnit.SECONDS.sleep(4);
        } catch (InterruptedException e) {
            e.printStackTrace();
        }
        System.out.println("发短信");
    }
    public static synchronized  void call() {
        System.out.println("打电话");
    }
}

结果:先发短信,后打电话。

  • 问题6:两个静态的同步方法,两个对象,程序的执行情况
public class Test1 {
    public static void main(String[] args) {
        phone phone1 = new phone();
        phone phone2 = new phone();

        new Thread(()->{ phone1.sendSms(); },"A").start();
        try {
            TimeUnit.SECONDS.sleep(1);
        } catch (Exception e) {
            e.printStackTrace();
        }
        new Thread(()->{ phone2.call(); },"B").start();
    }
}

结果:先发短信,后打电话。
两个对象的Class模板只有一个,static锁的是Class

  • 问题7:一个静态同步方法,一个普通同步方法,只有一个对象,程序的执行情况
public class Test1 {
    public static void main(String[] args) {
        phone phone = new phone();

        new Thread(()->{ phone.sendSms(); },"A").start();
        try {
            TimeUnit.SECONDS.sleep(1);
        } catch (Exception e) {
            e.printStackTrace();
        }
        new Thread(()->{ phone.call(); },"B").start();
    }
}

class phone{
    //静态同步方法,锁的是Class模板
    public static synchronized void sendSms() {
        try {
            TimeUnit.SECONDS.sleep(4);
        } catch (InterruptedException e) {
            e.printStackTrace();
        }
        System.out.println("发短信");
    }

    //普通同步方法,锁的是调用者,不需要去等待锁
    public synchronized void call() {
        System.out.println("打电话");
    }
}

结果:先打电话,后发短信。
不是同一个锁,锁调用者的不需要等待,锁Class的要等待

  • 问题8:一个静态同步方法,一个普通同步方法,两个对象,程序的执行情况
public static void main(String[] args) {
        phone phone1 = new phone();
        phone phone2 = new phone();

        new Thread(()->{ phone1.sendSms(); },"A").start();
        try {
            TimeUnit.SECONDS.sleep(1);
        } catch (Exception e) {
            e.printStackTrace();
        }
        new Thread(()->{ phone2.call(); },"B").start();
    }

结果:先打电话,后发短信。
两个锁,还是锁的对象不一样

小结
普通同步方法:(new this) 锁的是调用者,是一个具体的对象
静态同步方法:(static Class) 锁的是Class模板,是唯一的

6. 集合类不安全

6.1 List不安全

CopyOnWriteArrayList

  • Arrarlist测试(单线程)
public class ListTest01 {
    public static void main(String[] args) {
        List<String> list = Arrays.asList("1", "2", "3");
        list.forEach(System.out::println);
    }
}
  • 多线程下ArrayList还安全吗?
    现在我们创建10个线程来向List添加元素
public class ListTest01 {
    public static void main(String[] args) {
        ArrayList<String> list = new ArrayList<>();
        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();
            //UUID(Universally Unique Identifier):通用唯一识别码,是一种软件建构的标准.UUID是指在一台机器上生成的数字,它保证对在同一时空中的所有机器都是唯一的
            //String.valueOf(i),给10个线程分别取名字
        }
    }
}

在这里插入图片描述

  • 并发下 ArrayList 不安全的解决方案

    • List list = new Vector<>(); //vector默认是安全的(是同步方法)
    • List list = Collections.synchronizedList(new ArrayList<>); //工具类
    • List list = new CopyOnWriteArrayList<>();//CopyOnWrite,写入时复制,COW,计算机程序设计领域的一种优化策略
      多个线程调用的时候,list,读取的时候 固定的,写入(覆盖)
      在写入的时候避免覆盖,造成数据问题!
      读写分离(写入的时候复制一个数组出来,写入完之后再插入进去,保证线程安全)
  • CopyOnWriteArrayList 比 Vector 好在哪里?
    Vector的add方法有Synchronized修饰(看源码),有Synchronized修饰的方法,效率都比较低
    CopyOnWriteArrayList—— add源码
    在这里插入图片描述

6.2 Set不安全

public class SetTest {
    public static void main(String[] args) {
        // Set<String> set = new HashSet<>(); //ConcurrentModificationException
        // Set<String> set = Collections.synchronizedSet(new HashSet<>());
        Set<String> set = new CopyOnWriteArraySet<>();

        for (int i = 1; i <= 30; i++) {
            new Thread(()->{
                set.add(UUID.randomUUID().toString().substring(0,5));
                System.out.println(set);
            },String.valueOf(i)).start();
        }
    }
}

HashSet的底层是什么?

public HashSet() {
        map = new HashMap<>();
    }
   
//add set本质就是map,key是无法重复的
public boolean add(E e) {
        return map.put(e, PRESENT)==null;
    }

private static final Object PRESENT = new Object(); //PRESENT 不变的值

6.2 Map不安全

HashMap<Object, Object> map = new HashMap<>();

问题1:Map是这样用的吗?
答:工作中不这样用

问题2:map默认等价于什么
其中16是初始容量,0.75是加载因子

HashMap<Object, Object> map1 = new HashMap<>(16,0.75);

在这里插入图片描述

public class MapTest {
    public static void main(String[] args) {
        // HashMap<Object, Object> map = new HashMap<>(); //ConcurrentModificationException
        // HashMap<Object, Object> map = (HashMap<Object, Object>) Collections.synchronizedMap(new HashMap<>());
        Map<Object, Object> map = new ConcurrentHashMap<>();
        for (int i = 0; i < 30; i++) {
            new Thread(()->{
                map.put(Thread.currentThread().getName(),UUID.randomUUID().toString().substring(0,5));
                System.out.println(map);
            },String.valueOf(i)).start();
        }
    }
}

7. Callable

官方文档:Callable接口类似于Runnable ,因为它们都是为其实例可能由另一个线程执行的类设计的。 然而, Runnable不返回结果,也不能抛出被检查的异常。

  1. 可以有返回值
  2. 可以抛出异常
  3. 方法不同,run() / call()

根据底层源码

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

Thread 只能接受Runnable类型的参数,不能接受Callable类型的参数
但是,Runnable有一个实现类:Class FutureTask
在这里插入图片描述
FutureTask可以接受Callable类型的参数

public class CallableTest {
    public static void main(String[] args) throws ExecutionException, InterruptedException {
        //new Thread(new Runnable()).start();
        //new Thread(new FutureTask<V>()).start();
        //new Thread(new FutureTask<V>(Callable)).start();
        new Thread().start(); //怎么启动callable

        MyThread myThread = new MyThread();
        FutureTask futureTask = new FutureTask<>(myThread); //适配类

        new Thread(futureTask,"A").start();

        Object o = futureTask.get(); //获取Callable的返回结果,get方法可能会产生阻塞,一般放到最后,或者使用异步通信
        System.out.println(o);
    }
}

class MyThread implements Callable<String>{

    @Override
    public String call() throws Exception {
        System.out.println("call()");
        return "aaa";
    }
}

问题:如果new两个线程会打印几个call()?
答案:一个
分析:结果会被缓存,效率高

8. 常用的辅助类(必会)

8.1 CountDownLatch(减法计数器)

在这里插入图片描述

//计数器
public class CountDownLatchDemo {
    public static void main(String[] args) throws InterruptedException {
        //总数是6,必须要在执行任务的时候,再使用
        CountDownLatch countDownLatch = new CountDownLatch(6);
        for (int i = 1; i <= 6; i++) {
            new Thread(()->{
                System.out.println(Thread.currentThread().getName()+" go out");
                countDownLatch.countDown(); //数量-1
            },String.valueOf(i)).start();
        }

        countDownLatch.await(); //等待计数器归零,再向下执行
        System.out.println("Close door");
    }
}

原理
countDownLatch.countDown(); // 数量-1
countDownLatch.await(); // 等待计数器归零,然后再向下执行
每次有线程调用 countDown() 数量-1,假设计数器变为0,countDownLatch.await() 就会被唤醒,继续执行!
如果 countDown()没有减到0,后面的程序是不会执行的

8.2 CylicBarrier(加法计数器)

在这里插入图片描述

public class CylicBarrierDemo {
    public static void main(String[] args) throws InterruptedException {
        //集齐7颗龙珠召唤神龙
        //召唤龙珠的线程
        CyclicBarrier cyclicBarrier = new CyclicBarrier(7, () -> {
            System.out.println("召唤神龙成功");
        });
        for (int i = 1; i <= 7; i++) {
            final int temp = i;
            new Thread(()->{
                //lambda表达式获取不到i
                System.out.println(Thread.currentThread().getName()+"收集了"+temp+"颗龙珠");
                try {
                    cyclicBarrier.await(); //等待
                } catch (InterruptedException e) {
                    e.printStackTrace();
                } catch (BrokenBarrierException e) {
                    e.printStackTrace();
                }
            }).start();
        }
    }
}

8.3 Semaphore(信号量)

在这里插入图片描述

//模拟停车,假设现在有6辆车,但是只有3个停车位
//在有限的情况下使其有秩序,限流的时候可以使用
public class SemaphoreDemo {
    public static void main(String[] args) throws InterruptedException {
        //线程数量:停车位
        Semaphore semaphore = new Semaphore(3);
        for (int i = 1; i <= 6; i++) {
            new Thread(()->{
                // acquire() 得到
                try {
                    semaphore.acquire();
                    System.out.println(Thread.currentThread().getName()+"抢到车位");
                    TimeUnit.SECONDS.sleep(2);
                    System.out.println(Thread.currentThread().getName()+"离开车位");
                } catch (InterruptedException e) {
                    e.printStackTrace();
                } finally {
                    semaphore.release(); // release() 释放
                }
            },String.valueOf(i)).start();
        }
    }
}

原理:
semaphore.acquire():获得,假设如果已经满了,等待,等待被释放为止!
semaphore.release():释放,会将当前的信号量释放 + 1,然后唤醒等待的线程!
作用: 多个共享资源互斥的使用!并发限流,控制最大的线程数!

9. 读写锁ReadWriteLock

读的时候可以被多线程同时读,写的时候只能有一个线程去写。

独占锁(写锁):一次只能被一个线程占有
共享锁(读锁):可以被多个线程同时占有
读-读:可以共存
读-写:不能共存
写-写:不能共存

public class ReadWriteLockTest {
    public static void main(String[] args) {
        //MyCache myCache = new MyCache();
        MyCacheLock myCache = new MyCacheLock();

        //写入
        for (int i = 1; i <= 5; i++) {
            final int temp = i;
            new Thread(()->{
                myCache.put(temp+"",temp+"");
            },String.valueOf(i)).start();
        }

        //读取
        for (int i = 1; i <= 5; i++) {
            final int temp = i;
            new Thread(()->{
                myCache.get(temp+"");
            },String.valueOf(i)).start();
        }
    }
}

//加锁的
class MyCacheLock{
    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()+"写入成功");
        } 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()+"读取成功");
        } catch (Exception e) {
            e.printStackTrace();
        } finally {
            readWriteLock.readLock().unlock();
        }
    }
}

//自定义缓存
class MyCache{
    private volatile Map<String,Object> map = new HashMap<>();

    //存 写
    public void put(String key, Object value){
        System.out.println(Thread.currentThread().getName()+"写入"+key);
        map.put(key,value);
        System.out.println(Thread.currentThread().getName()+"写入成功");
    }

    //读 取
    public void get(String key){
        System.out.println(Thread.currentThread().getName()+"读取"+key);
        Object o = map.get(key);
        System.out.println(Thread.currentThread().getName()+"读取成功");
    }
}

10. 阻塞队列

写入:如果队列满了,就必须阻塞等待
读取:如果队列是空的,必须阻塞等待生产
在这里插入图片描述
在这里插入图片描述

BlockingQueue
在这里插入图片描述

什么情况会使用阻塞队列?
多线程(A调用B,必须等B先执行,B没有执行完,A就会挂起或者等待)
线程池(出了弹性大小之外,一般会用一个队列去维护里面的大小)

学会使用队列
添加,移除

四组API
在这里插入图片描述

  1. 抛出异常
public class BqTest {
    public static void main(String[] args) {
        test1();
    }

    // 1. 抛出异常
    public static void test1(){
        //队列大小
        ArrayBlockingQueue arrayBlockingQueue = new ArrayBlockingQueue<>(3);
        System.out.println(arrayBlockingQueue.add("a"));
        System.out.println(arrayBlockingQueue.add("b"));
        System.out.println(arrayBlockingQueue.add("c"));

        //java.lang.IllegalStateException: Queue full 抛出异常
        //System.out.println(arrayBlockingQueue.add("d"));

        System.out.println(arrayBlockingQueue.remove());
        System.out.println(arrayBlockingQueue.remove());
        System.out.println(arrayBlockingQueue.remove());

        //java.util.NoSuchElementException
        //System.out.println(arrayBlockingQueue.remove());
    }
}
  1. 不会抛出异常
// 2. 有返回值,不抛出异常
    public static void test2(){
        ArrayBlockingQueue arrayBlockingQueue = new ArrayBlockingQueue<>(3);
        System.out.println(arrayBlockingQueue.offer("a"));
        System.out.println(arrayBlockingQueue.offer("b"));
        System.out.println(arrayBlockingQueue.offer("c"));

        System.out.println(arrayBlockingQueue.offer("d")); //false 不抛出异常

        System.out.println(arrayBlockingQueue.poll());
        System.out.println(arrayBlockingQueue.poll());
        System.out.println(arrayBlockingQueue.poll());
        System.out.println(arrayBlockingQueue.poll()); //null 不抛出异常
    }
  1. 阻塞等待
// 3. 等待,阻塞(一直阻塞)
    public static void test3() throws InterruptedException {
        ArrayBlockingQueue arrayBlockingQueue = new ArrayBlockingQueue<>(3);
        arrayBlockingQueue.put("a");
        arrayBlockingQueue.put("b");
        arrayBlockingQueue.put("c");
        //arrayBlockingQueue.put("d"); //队列没有位置,一直阻塞

        System.out.println(arrayBlockingQueue.take());
        System.out.println(arrayBlockingQueue.take());
        System.out.println(arrayBlockingQueue.take());
        //System.out.println(arrayBlockingQueue.take()); //没有这个元素,一直阻塞
    }
  1. 超时等待
// 4. 等待,阻塞(等待超时)
    public static void test4() throws InterruptedException {
        ArrayBlockingQueue arrayBlockingQueue = new ArrayBlockingQueue<>(3);
        System.out.println(arrayBlockingQueue.offer("a"));
        System.out.println(arrayBlockingQueue.offer("b"));
        System.out.println(arrayBlockingQueue.offer("c"));
        System.out.println(arrayBlockingQueue.offer("d", 2, TimeUnit.SECONDS)); //等待超过2秒就退出

        System.out.println(arrayBlockingQueue.poll());
        System.out.println(arrayBlockingQueue.poll());
        System.out.println(arrayBlockingQueue.poll());
        System.out.println(arrayBlockingQueue.poll(2, TimeUnit.SECONDS)); //等待超过2秒就退出
    }
  • 同步队列
    • 没有容量,进去一个元素,必须等待取出来之后,才能再往里面放一个元素。
    • 和其他的BlockingQueue 不一样, SynchronousQueue 不存储元素
    • put了一个元素,必须从里面先take取出来,否则不能在put进去值!
public class SynchronousQueueDemo {
    public static void main(String[] args) {
        BlockingQueue<String> synchronousQueue = new SynchronousQueue<>();//同步队列

        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();
            }
        },"T1").start();

        new Thread(()->{
            try {
                TimeUnit.SECONDS.sleep(3);
                System.out.println(Thread.currentThread().getName()+" take "+synchronousQueue.take());
                TimeUnit.SECONDS.sleep(3);
                System.out.println(Thread.currentThread().getName()+" take "+synchronousQueue.take());
                TimeUnit.SECONDS.sleep(3);
                System.out.println(Thread.currentThread().getName()+" take "+synchronousQueue.take());
            } catch (InterruptedException e) {
                e.printStackTrace();
            }
        },"T2").start();
    }
}

11. 线程池

  • 池化技术
    • 程序运行的本质:占用系统资源,为了优化资源的使用,引入了池化技术
    • 比如线程池、连接池。内存池、对象池
    • 池化技术:事先准备好一些资源,有人要用,就来拿,用完就归还。

线程池的好处

  1. 降低资源消耗
  2. 提高响应速度
  3. 方便管理线程

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

线程池重点核心:三大方法, 7大参数, 4种拒绝策略

11. 1 三大方法

//Executors 工具类 3大方法
public class Demo01 {
    public static void main(String[] args) {
        //ExecutorService threadPool = Executors.newSingleThreadExecutor();// 单个线程
        //ExecutorService threadPool = Executors.newFixedThreadPool(5);// 创建一个固定的线程池的大小
        ExecutorService threadPool = Executors.newCachedThreadPool(); // 可伸缩的

        try {
            for (int i = 0; i < 100; i++) {
                // 使用线程池创建线程
                threadPool.execute(()->{
                    System.out.println(Thread.currentThread().getName()+" ok");
                });
            }
        } catch (Exception e) {
            e.printStackTrace();
        } finally {
            // 线程池用完,程序结束,线程池要关闭
            threadPool.shutdown();
        }
    }
}

11.2 7大参数

源码分析

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;
    }

在这里插入图片描述

11.3 4种拒绝策略

在这里插入图片描述
四种拒绝策略:
在这里插入图片描述

  1. new ThreadPoolExecutor.AbortPolicy() //银行满了,但是还有人进来,不处理这个人的,抛出异常
  2. new ThreadPoolExecutor.CallerRunsPolicy() //哪来的去哪里 (main线程执行)
  3. new ThreadPoolExecutor.DiscardPolicy() //队列满了,丢掉任务,不会抛出异常
  4. new ThreadPoolExecutor.DiscardOldestPolicy() //队列满了,尝试去和最早的竞争,不会抛出异常
public class Demo01 {
    public static void main(String[] args) {
        //自定义线程池
        ExecutorService threadPool = new ThreadPoolExecutor(
                2,
                5,
                3,
                TimeUnit.SECONDS,
                new LinkedBlockingDeque<>(3),
                Executors.defaultThreadFactory(),
                new ThreadPoolExecutor.DiscardOldestPolicy() //队列满了,尝试去和最早的竞争,不会抛出异常
        );

        try {
            // 最大承载 8个:Deque + max
            // 超过:RejectedExecutionException
            for (int i = 0; i < 10; i++) {
                threadPool.execute(()->{
                    System.out.println(Thread.currentThread().getName()+" ok");
                });
            }
        } catch (Exception e) {
            e.printStackTrace();
        } finally {
            // 线程池用完,程序结束,线程池要关闭
            threadPool.shutdown();
        }
    }
}
  • 最大线程到底该如何定义
  1. CPU密集型:几核,就是几,可以保持CPU的效率最高!
    你的电脑是几个核心的(用程序获取),你的最大线程数就设置成几,可以保持CPU的效率最高)
    在这里插入图片描述
  2. IO密集型:最大线程数 > 判断你程序中十分耗IO的线程
    例如:你的程序里面有15个任务很占用IO资源,就用15个线程去执行,所以最大
    线程数量大于这个15就好了,一般是大型IO任务数量的2倍

12. 四大函数式接口

新时代的程序员:lambda表达式、链式编程、函数式接口、Stream流式计算

函数式接口:只有一个方法的接口

@FunctionalInterface
public interface Runnable {
    public abstract void run();
}

超级多的FunctionalInterface
简化编程模型,在新版本的框架底层大量应用
foreach(消费者类型的函数式接口)
在这里插入图片描述

12.1 Function 函数型接口

在这里插入图片描述
有一个输入参数,有一个输出
只要是函数式接口,就可以用lambda表达式简化

public class FunctionDemo {
    public static void main(String[] args) {
        /*Function function = new Function<String,String>() {
            //工具类:输出输入的值
            @Override
            public String apply(String str) {
                return str;
            };
        };*/
        Function function = (str)->{return str;};
        System.out.println(function.apply("aaa"));
    }
}

12.2 Predicate 断定型接口

在这里插入图片描述
有一个输入参数,返回值只能是布尔值

public class PredicateDemo {
    public static void main(String[] args) {
        //判断字符串是否为空
        /*Predicate<String> predicate = new Predicate<String>() {
            @Override
            public boolean test(String str) {
                return str.isEmpty();
            }
        };*/
        Predicate<String> predicate = (str)->{return str.isEmpty();};
        System.out.println(predicate.test(""));
    }
}

12.3 Supplier 供给型接口

在这里插入图片描述

public class SuppierDemo {
    public static void main(String[] args) {
        //打印字符串
        /*Supplier<String> supplier = new Supplier<String>() {
            @Override
            public String get() {
                return "aaa";
            }
        };*/

        Supplier<String> supplier = ()->{return "aaa";};
        System.out.println(supplier.get());
    }
}

12.4 Consumer 消费型接口

在这里插入图片描述

public class ConsummerDemo {
    public static void main(String[] args) {
        //打印字符串
       /* Consumer<String> consumer = new Consumer<String>() {
            @Override
            public void accept(String s) {
                System.out.println(s);
            }
        };*/
        Consumer<String> consumer = (s)->{System.out.println(s);};
        consumer.accept("sss");
    }
}

13. Stream 流式计算

  • 什么是Stream流式计算
    • 大数据:存储+计算
    • 集合、Mysql本来就是存储数据的,计算应该交给流来操作。
/**
 * 题目要求: 用一行代码实现
 * 1. Id 必须是偶数
 * 2.年龄必须大于23
 * 3. 用户名转为大写
 * 4. 用户名倒序
 * 5. 只能输出一个用户
 **/

public class StreamDemo {
    public static void main(String[] args) {
        User u1 = new User(1, "a", 23);
        User u2 = new User(2, "b", 23);
        User u3 = new User(3, "c", 23);
        User u4 = new User(6, "d", 24);
        User u5 = new User(4, "e", 25);

        List<User> list = Arrays.asList(u1, u2, u3, u4, u5);
        // lambda、链式编程、函数式接口、流式计算
        list.stream()
                .filter(user -> {return user.getId()%2 == 0;})
                .filter(user -> {return user.getAge() > 23;})
                .map(user -> {return user.getName().toUpperCase();})
                .sorted((user1, user2) -> {return user2.compareTo(user1);})
                .limit(1)
                .forEach(System.out::println);
    }
}

14. ForkJoin

ForkJoin 在JDK1.7,并行执行任务!提高效率~。在大数据量速率会更快!

大数据中:Map Reduce 核心思想->把大任务拆分为小任务
在这里插入图片描述

  • ForkJoin 特点: 工作窃取!
    这里面维护的是双端队列
    实现原理是:双端队列!从上面和下面都可以去拿到任务进行执行!
    在这里插入图片描述
  • 如何使用ForkJoin?
    • 通过ForkJoinPool来执行
    • 计算任务 execute(ForkJoinTask<?> task)
    • 计算类要去继承ForkJoinTask;

在这里插入图片描述

public class ForkJoinDemo extends RecursiveTask<Long> {
    private long star;
    private long end;
    /** 临界值 */
    private long temp = 1000000L;

    public ForkJoinDemo(long star, long end) {
        this.star = star;
        this.end = end;
    }

    /**
     * 计算方法
     * @return
     */
    @Override
    protected Long compute() {
        if ((end - star) < temp) {
            Long sum = 0L;
            for (Long i = star; i < end; i++) {
                sum += i;
            }
            return sum;
        }else {
            // 使用ForkJoin 分而治之 计算
            //1 . 计算平均值
            long middle = (star + end) / 2;
            ForkJoinDemo forkJoinDemo1 = new ForkJoinDemo(star, middle);
            // 拆分任务,把线程压入线程队列
            forkJoinDemo1.fork();
            ForkJoinDemo forkJoinDemo2 = new ForkJoinDemo(middle, end);
            forkJoinDemo2.fork();

            long taskSum = forkJoinDemo1.join() + forkJoinDemo2.join();
            return taskSum;
        }
    }
}

测试

public class ForkJoinTest {
    private static final long SUM = 20_0000_0000;

    public static void main(String[] args) throws ExecutionException, InterruptedException {
        test1();
        test2();
        test3();
    }

    /**
     * 使用普通方法
     */
    public static void test1() {
        long star = System.currentTimeMillis();
        long sum = 0L;
        for (long i = 1; i < SUM ; i++) {
            sum += i;
        }
        long end = System.currentTimeMillis();
        System.out.println(sum);
        System.out.println("时间:" + (end - star));
        System.out.println("----------------------");
    }
    
    /**
     * 使用ForkJoin 方法
     */
    public static void test2() throws ExecutionException, InterruptedException {
        long star = System.currentTimeMillis();

        ForkJoinPool forkJoinPool = new ForkJoinPool();
        ForkJoinTask<Long> task = new ForkJoinDemo(0L, SUM);
        ForkJoinTask<Long> submit = forkJoinPool.submit(task); //提交任务
        Long along = submit.get();

        System.out.println(along);
        long end = System.currentTimeMillis();
        System.out.println("时间:" + (end - star));
        System.out.println("-----------");
    }
    
    /**
     * 使用 Stream并行流计算
     */
    public static void test3() {
        long star = System.currentTimeMillis();

        long sum = LongStream.rangeClosed(0L, SUM).parallel().reduce(0, Long::sum);
        System.out.println(sum);
        long end = System.currentTimeMillis();
        System.out.println("时间:" + (end - star));
        System.out.println("-----------");
    }
}

15. 异步回调

Future 设计的初衷:对将来的某个事件结果进行建模!

其实就是前端 --> 发送ajax异步请求给后端
但是我们平时都使用CompletableFuture

  • 没有返回值的runAsync异步回调
public class FutureDemo {
    public static void main(String[] args) throws ExecutionException, InterruptedException {
        // 发起一个请求
        CompletableFuture completableFuture = CompletableFuture.runAsync(()->{
            //发起一个异步任务
            try {
                TimeUnit.SECONDS.sleep(2);
            } catch (InterruptedException e) {
                e.printStackTrace();
            }
            System.out.println(Thread.currentThread().getName()+"runAsync");
        });

        System.out.println("111");
        completableFuture.get(); // 获取阻塞执行结果
    }
}
  • 有返回值的runAsync异步回调
//ajax 成功和失败的回调
//返回的是错误信息
public class FutureDemo2 {
    public static void main(String[] args) throws ExecutionException, InterruptedException {
        CompletableFuture<Integer> completableFuture = CompletableFuture.supplyAsync(()->{
            System.out.println(Thread.currentThread().getName()+"supplyAsync");
            int i = 10/0;
            return 1024;
        });

        System.out.println(completableFuture.whenComplete((t,u)->{
            //success 回调
            System.out.println("t=>"+t); //正常的返回结果
            System.out.println("u=>"+u); //抛出异常的 错误信息
        }).exceptionally((e)->{
            //error回调
            System.out.println(e.getMessage());
            return 233;
        }).get());
    }
}

16. JMM

  • 对Volatile 的理解
    Volatile 是 Java 虚拟机提供 轻量级 的同步机制
    1、保证可见性
    2、不保证原子性
    3、禁止指令重排

  • 什么是JMM?
    JMM:JAVA内存模型,不存在的东西,是一个概念,也是一个约定!

  • 关于JMM的一些同步的约定:

    • 线程解锁前,必须把共享变量立刻刷回主存;
    • 线程加锁前,必须读取主存中的最新值到工作内存中;
    • 加锁和解锁是同一把锁;

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

  • 8种操作:
    在这里插入图片描述
  • 内存操作有8种,虚拟机实现必须保证每一个操作都是原子性的,不可再分的
    • Read(读取):作用于主内存变量,它把一个变量的值从主内存传输到线程的工作内存中,以便随后的load动作使用;
    • load(载入):作用于工作内存的变量,它把read操作从主存中变量放入工作内存中;
    • Use(使用):作用于工作内存中的变量,它把工作内存中的变量传输给执行引擎,每当虚拟机遇到一个需要使用到变量的值,就会使用到这个指令;
    • assign(赋值):作用于工作内存中的变量,它把一个从执行引擎中接受到的值放入工作内存的变量副本中;
    • store(存储):作用于主内存中的变量,它把一个从工作内存中一个变量的值传送到主内存中,以便后续的write使用;
    • write(写入):作用于主内存中的变量,它把store操作从工作内存中得到的变量的值放入主内存的变量中;
    • lock(锁定):作用于主内存的变量,把一个变量标识为线程独占状态;
    • unlock(解锁):作用于主内存的变量,它把一个处于锁定状态的变量释放出来,释放后的变量才可以被其他线程锁定;

在这里插入图片描述

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

17. volatile

17.1 保证可见性

public class JMMDemo {
    private static volatile int num = 0;

    public static void main(String[] args) {
        new Thread(()->{
            while (num == 0){

            }
        }).start();

        try {
            TimeUnit.SECONDS.sleep(2);
        } catch (InterruptedException e) {
            e.printStackTrace();
        }

        num = 1;
        System.out.println(num);
    }
}

17.2 不保证原子性

原子性:不可分割;
线程A在执行任务的时候,不能被打扰的,也不能被分割的,要么同时成功,要么同时失败。

public class JMMDemo {

    // volatile 不保证原子性
    private static volatile int num = 0;

    public static void add(){
        num++; //不是一个原子性操作
    }

    //num 理论上结果应该是20000

    public static void main(String[] args) {
        for (int i = 1; i <= 20; i++) {
            new Thread(()->{
                for (int j = 1; j <= 1000; j++) {
                    add();
                }
            }).start();
        }

        while (Thread.activeCount() > 2) { //main gc
            Thread.yield();
        }

        System.out.println(Thread.currentThread().getName()+ " " + num);
    }
}

如果不加lock和synchronized ,怎么样保证原子性?
在这里插入图片描述
使用原子类 解决原子性问题

public class JMMDemo {

    // 原子类的 Integer
    private static volatile AtomicInteger num = new AtomicInteger();

    public static void add(){
        num.getAndIncrement(); //AtomicInteger的+1方法,底层CAS
    }

    //num 理论上结果应该是20000

    public static void main(String[] args) {
        for (int i = 1; i <= 20; i++) {
            new Thread(()->{
                for (int j = 1; j <= 1000; j++) {
                    add();
                }
            }).start();
        }

        while (Thread.activeCount() > 2) { //main gc
            Thread.yield();
        }

        System.out.println(Thread.currentThread().getName()+ " " + num);
    }
}

原子类为什么这么高级?
这些类的底层都直接和操作系统挂钩!是在内存中修改值。
Unsafe类是一个很特殊的存在;

17.3 禁止指令重排

  • 什么是指令重排?
    • 我们写的程序,计算机并不是按照我们自己写的那样去执行的
    • 源代码–>编译器优化重排–>指令并行也可能会重排–>内存系统也会重排–>执行
    • 处理器在进行指令重排的时候,会考虑数据之间的依赖性!

处理器在进行指令重排的时候,会考虑数据之间的依赖性!

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

//我们期望的执行顺序是 1_2_3_4  可能执行的顺序会变成2134 1324
//可不可能是 4123? 不可能的
1234567

可能造成的影响结果:前提:a b x y这四个值 默认都是0

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

正常的结果: x = 0; y =0;

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

可能在线程A中会出现,先执行b=1,然后再执行x=a;
在B线程中可能会出现,先执行a=2,然后执行y=b;
那么就有可能结果如下:x=2; y=1.

  • volatile可以避免指令重排:
    • volatile中会加一道内存的屏障,这个内存屏障可以保证在这个屏障中的指令顺序。
    • 内存屏障:CPU指令。作用:
      1、保证特定的操作的执行顺序;
      2、可以保证某些变量的内存可见性(利用这些特性,就可以保证volatile实现的可见性)
      在这里插入图片描述

总结

  1. volatile可以保证可见性;
  2. 不能保证原子性
  3. 由于内存屏障,可以保证避免指令重排的现象产生

在哪里用这个内存屏障用得最多呢?单例模式

18. 单例模式

  • 饿汉式
// 饿汉式
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 final static Hungry HUNGRY = new Hungry();

    public static Hungry getInstance(){
        return HUNGRY;
    }
}
  • 静态内部类
// 静态内部类
public class Holder {
    private Holder(){

    }

    public static class InnerClass {
        private static final Holder HOLDER = new Holder();
    }

    public static Holder getInstance() {
        return InnerClass.HOLDER;
    }
}
  • 懒汉式
// 懒汉式
public class LazyMan {
    private static boolean flag = false;

    private LazyMan() {
        //System.out.println(Thread.currentThread().getName() + "ok");
        synchronized (LazyMan.class) {
            if (flag == false) {
                flag = true;
            }else {
                throw new RuntimeException("不要试图通过反射破坏异常");
            }
            /*if (lazyMan != null) {
                throw new RuntimeException("不要试图通过反射破坏异常");
            }*/
        }
    }

    private volatile static LazyMan lazyMan;

    /*public static LazyMan getInstance(){
        if (lazyMan == null) {
            lazyMan = new LazyMan();
        }
        return lazyMan;
    } //单线程可以*/

    // 解决:双重检测锁模式的 懒汉式单例 DCL懒汉式
    public static LazyMan getInstance() {
        if (lazyMan == null) {
            synchronized (LazyMan.class) {
                if (lazyMan == null) {
                    lazyMan = new LazyMan(); //不是一个原子性操作
                    /**
                    * 1. 分配内存空间
                    * 2. 执行构造方法,初始化对象
                    * 3. 把这个对象指向这个空间
                     * 可能发生指令重排   上面加volatile
                    */
                }
            }
        }
        return lazyMan;
    }

    //多线程并发  有问题
    /*public static void main(String[] args) {
        for (int i = 0; i < 10; i++) {
            new Thread(()->{
                lazyMan.getInstance();
            }).start();
        }
    }*/

    // 反射破解
    public static void main(String[] args) throws Exception {
        //LazyMan instance = LazyMan.getInstance();

        Field flag = LazyMan.class.getDeclaredField("flag");
        flag.setAccessible(true); //破坏私有权限
        Constructor<LazyMan> declaredConstructor = LazyMan.class.getDeclaredConstructor(null);
        declaredConstructor.setAccessible(true);
        LazyMan instance = declaredConstructor.newInstance(); //还是被破解,用标志位加密
        flag.set(instance, false);
        LazyMan instance1 = declaredConstructor.newInstance();

        System.out.println(instance);
        System.out.println(instance1);
    }
}

单例不安全, 因为反射

  • 枚举
public enum EnumSingle {
    INSTANCE;

    public EnumSingle getInstance(){
        return INSTANCE;
    }
}

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

在这里插入图片描述
枚举类型的最终反编译源码:

public final class EnumSingle extends Enum
{

    public static EnumSingle[] values()
    {
        return (EnumSingle[])$VALUES.clone();
    }

    public static EnumSingle valueOf(String name)
    {
        return (EnumSingle)Enum.valueOf(com/ogj/single/EnumSingle, name);
    }

    private EnumSingle(String s, int i)
    {
        super(s, i);
    }

    public EnumSingle getInstance()
    {
        return INSTANCE;
    }

    public static final EnumSingle INSTANCE;
    private static final EnumSingle $VALUES[];

    static 
    {
        INSTANCE = new EnumSingle("INSTANCE", 0);
        $VALUES = (new EnumSingle[] {
            INSTANCE
        });
    }
}

19. 深入理解CAS

public class CASDemo {

    // CAS: compareAndSet 比较并交换
    public static void main(String[] args) {
        AtomicInteger atomicInteger = new AtomicInteger(2020);

        // 期望, 更新
        // public final boolean compareAndSet(int expect, int update)
        // 如果我期望的值达到了,那么就更新,否则,不更新。CAS 是CPU的并发原语
        System.out.println(atomicInteger.compareAndSet(2020, 2021));
        System.out.println(atomicInteger.get());
        atomicInteger.getAndIncrement(); //相当于++

        System.out.println(atomicInteger.compareAndSet(2020, 2021));
        System.out.println(atomicInteger.get());
    }
}

在这里插入图片描述
Unsafe 类
在这里插入图片描述
在这里插入图片描述

  • 总结

    • CAS:比较当前工作内存中的值 和 主内存中的值,如果这个值是期望的,那么则执行操作!如果不是就一直循环,使用的是自旋锁。
    • 缺点:
      • 循环会耗时;
      • 一次性只能保证一个共享变量的原子性;
      • 它会存在ABA问题
  • CAS:ABA问题?(狸猫换太子)
    在这里插入图片描述

 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());
    }

20. 原子引用

解决ABA问题,对应的思想:就是使用了乐观锁~

带版本号的 原子操作!
在这里插入图片描述

public class CASDemo {

    // 注意:AtomicStampedReference,如果泛型是一个包装类,注意对象的引用问题
    static AtomicStampedReference<Integer> atomicInteger = new AtomicStampedReference<>(1, 1);

    public static void main(String[] args) {
        //AtomicInteger atomicInteger = new AtomicInteger(2020);

        new Thread(()->{
            int stamp = atomicInteger.getStamp();//获得版本号
            System.out.println("a1=>"+stamp);

            try {
                TimeUnit.SECONDS.sleep(1);
            } catch (InterruptedException e) {
                e.printStackTrace();
            }

            atomicInteger.compareAndSet(1, 2, atomicInteger.getStamp(), atomicInteger.getStamp() + 1);
            System.out.println("a2=>"+atomicInteger.getStamp());

            atomicInteger.compareAndSet(2, 1, atomicInteger.getStamp(), atomicInteger.getStamp() + 1);
            System.out.println("a3=>"+atomicInteger.getStamp());

        },"a").start();

        new Thread(()->{
            int stamp = atomicInteger.getStamp();//获得版本号
            System.out.println("b1=>"+stamp);
            try {
                TimeUnit.SECONDS.sleep(1);
            } catch (InterruptedException e) {
                e.printStackTrace();
            }

            System.out.println(atomicInteger.compareAndSet(1, 6, stamp, stamp + 1));
            System.out.println("b2=>"+atomicInteger.getStamp());

        },"b").start();
    }
}

21. 各种锁的理解

21.1 公平锁,非公平锁

  • 公平锁:非常公平,不能插队,必须先来后到
public ReentrantLock() {
    sync = new NonfairSync();
}
  • 非公平锁(默认):非常不公平,允许插队,可以改变顺序
public ReentrantLock(boolean fair) {
    sync = fair ? new FairSync() : new NonfairSync();
}

21.2 可重入锁

在这里插入图片描述

  • Synchonized 锁
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");
    }
}
  • Lock 锁
//lock
public class Demo02 {

    public static void main(String[] args) {
        Phone2 phone = new Phone2();
        new Thread(()->{
            phone.sms();
        },"A").start();
        new Thread(()->{
            phone.sms();
        },"B").start();
    }

}
class Phone2{

    Lock lock=new ReentrantLock();

    public void sms(){
        lock.lock(); //细节:这个是两把锁,两个钥匙
        //lock锁必须配对,否则就会死锁在里面
        try {
            System.out.println(Thread.currentThread().getName()+"=> sms");
            call();//这里也有一把锁
        } catch (Exception e) {
            e.printStackTrace();
        }finally {
            lock.unlock();
        }
    }
    public void call(){
        lock.lock();
        try {
            System.out.println(Thread.currentThread().getName() + "=> call");
        }catch (Exception e){
            e.printStackTrace();
        }
        finally {
            lock.unlock();
        }
    }
}

lock锁必须配对,相当于lock和 unlock 必须数量相同;
在外面加的锁,也可以在里面解锁;在里面加的锁,在外面也可以解锁;

12.3 自旋锁

  • spinlock
public final int getAndAddInt(Object var1, long var2, int var4) {
    int var5;
    do {
        var5 = this.getIntVolatile(var1, var2);
    } while(!this.compareAndSwapInt(var1, var2, var5, var5 + var4));
    return var5;
}
  • 自我设计自旋锁
//自旋锁
public class SpinLock {

    // int 0
    // Thread null
    AtomicReference<Thread> atomicReference = new AtomicReference<>();

    // 加锁
    public void myLock(){
        Thread thread = Thread.currentThread();
        System.out.println(Thread.currentThread().getName() + "===> myLock");

        // 自旋锁
        while (!atomicReference.compareAndSet(null, thread)){

        }
    }

    // 解锁
    // 加锁
    public void myUnlock() {
        Thread thread = Thread.currentThread();
        System.out.println(Thread.currentThread().getName() + "===> myUnlock");
        atomicReference.compareAndSet(thread, null);
    }
}

测试:

public class TestSpinLock {
    public static void main(String[] args) throws InterruptedException {
        /*ReentrantLock reentrantLock = new ReentrantLock();
        reentrantLock.lock();
        reentrantLock.unlock();*/

        // 底层使用自旋锁CAS
        SpinLock spinLock = new SpinLock();

        new Thread(()->{
            spinLock.myLock();
            try {
                TimeUnit.SECONDS.sleep(3);
            } catch (Exception e) {
                e.printStackTrace();
            } finally {
                spinLock.myUnlock();
            }
        },"t1").start();

        TimeUnit.SECONDS.sleep(1);

        new Thread(()->{
            spinLock.myLock();
            try {
                TimeUnit.SECONDS.sleep(3);
            } catch (Exception e) {
                e.printStackTrace();
            } finally {
                spinLock.myUnlock();
            }
        },"t2").start();
    }
}

t2进程必须等待t1进程Unlock后,才能Unlock,在这之前进行自旋等待。。。。

21.4 死锁

在这里插入图片描述

public class DeadLock {
    public static void main(String[] args) {
        String lockA = "lockA";
        String lockB = "lockB";
        new Thread(new MyThread(lockA,lockB),"t1").start();
        new Thread(new MyThread(lockB,lockA),"t2").start();
    }
}

class MyThread implements Runnable{

    private String lockA;
    private String lockB;

    public MyThread(String lockA, String lockB) {
        this.lockA = lockA;
        this.lockB = lockB;
    }

    @Override
    public void run() {
        synchronized (lockA) {
            System.out.println(Thread.currentThread().getName()+"lock: "+lockA+"=>get"+lockB);
            try {
                TimeUnit.SECONDS.sleep(2);
            } catch (InterruptedException e) {
                e.printStackTrace();
            }
            synchronized (lockB) {
                System.out.println(Thread.currentThread().getName()+"lock: "+lockB+"=>get"+lockA);
            }
        }

    }
}

死锁排查:

  1. 使用jps定位进程号
    jdk的bin目录下: 有一个jps
    命令:jps -l
    在这里插入图片描述
    使用jstack 进程进程号,找到死锁信息
    在这里插入图片描述
    一般情况信息在最后:
    在这里插入图片描述
  • 4
    点赞
  • 19
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值