java 高并发编程(JUC)[狂神笔记+整理]


1.juc 编程

在这里插入图片描述

源码+官方文档  面试高频

java.util.concurrent  工具包
2.进程和线程

进程和线程

进程:一个程序 xx.exe
一个进程可以包含多个线程,至少包含一个

java 默认有 2个线程; main 、GC

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

对于java 而言:Thread、Runnable、Callable

java真的 可以开启线程吗? 不能

线程本质是竞争cpu使用权
java 没有 调用硬件的权限,本地方法 native 底层是c++ 可以

并发编程:并发、并行

并发(多线程操作同一个资源)

cpu一核,模拟出来多条线程, 快速交替

并行(多个人一起行走)

cpu多核,多线程可以同时执行,线程池
获取电脑核数 代码: 
	Runtime.getRuntime().availableProcessors()
    
或查看 此电脑--右键--设备管理器--cpu ,数个数
 并发编程的本质,充分利用CPU资源

线程有几种状态

Thread 有枚举类 State

Thread.State 
     /* @since   1.5
     * @see #getState
     */
    public enum State {
        //  新建
        NEW,
        //  运行
        RUNNABLE,
        //  阻塞
        BLOCKED,
        //  等待,死死地等
        WAITING,
        //  超时等待(过期不候)
        TIMED_WAITING,
          // 死亡
        TERMINATED                                 
    }

wait/sleep的区别

  1. 来自不同的类

    wait --> object

     sleep-->Thread
    
  2. 关于锁的释放

    wait会释放锁、sleep睡觉了,抱着锁睡觉,不会释放

  3. 使用范围是不同的

    wait 必须在同步代码块中,必须要 notify

    sleep 可以再任何地方睡觉

3.Lock锁(重点)

传统 synchronized

// 基本的卖票例子
public class saleTickets {

    public static void main (String[] args) {
        Ticket tk = new Ticket(); // 创建共享资源类
        // 多线程操作同一资源,把资源类丢入线程
        new Thread(()->{ for (int i=0;i<50;i++) tk.sale(); },"A窗口").start();
        new Thread(()->{ for (int i=0;i<50;i++) tk.sale(); },"B窗口").start();
        new Thread(()->{ for (int i=0;i<50;i++) tk.sale(); },"C窗口").start();
    }
}

//  资源OOP 编程,不直接 implement  Rannable
class Ticket{
    private int  number =30;
    // synchronized 本质; 锁、队列
    // (synchronized 一个线程使用,别的线程会阻塞,直到线程使用完后,别的再进行竞争cpu使用权)
    public synchronized void sale() {
        if (number >0){
            System.out.println(Thread.currentThread().getName()+"==》正在卖出第 "+number--+" 张票,还剩"+number+"张票");
            try {
                Thread.sleep(500);
            } catch (InterruptedException e) {
                e.printStackTrace();
            }
        }
    }
}

lock 接口

包 java.util.concurrent.locks --> interface lock ---->实现类:

ReentrantLock(可重入锁,普通锁)
ReentrantReadWriteLock.ReadLock (读锁)
ReentrantReadWriteLock.WriteLock (写锁)

ReentrantLock

公平锁十分公平:可以先来后到 ,不管来的早的占多长时间 (3s 3h)

非公平锁 :十分不公平,可以插队 (默认是非公平锁)

以下是构造方法:
在这里插入图片描述

public class saleTickets2 {

    public static void main (String[] args) {
        Ticket2 tk2 = new Ticket2(); // 创建共享资源类
        // 多线程操作同一资源,把资源类丢入线程
        new Thread(()->{ for (int i=0;i<50;i++) tk2.sale(); },"A窗口").start();
        new Thread(()->{ for (int i=0;i<50;i++) tk2.sale(); },"B窗口").start();
        new Thread(()->{ for (int i=0;i<50;i++) tk2.sale(); },"C窗口").start();

    }
}

//  资源OOP 编程,不直接 implement  Rannable
class Ticket2{
    private int  number =30;
     // Lock三部曲
    // 1、 new ReentrantLock();
    // 2、 lock.lock(); // 加锁 ,获取cpu使用权
    // 3、 finally=> lock.unlock(); // 解锁,释放cpu使用权(必须写在finally中)
    Lock lock = new ReentrantLock();
    public  void sale() {
        lock.lock();
        try {
            if (number >0){
                System.out.println(Thread.currentThread().getName()+"==》正在卖出第 "+number--+" 张票,还剩"+number+"张票");
            }
            // 经过的线程休眠500ms,拉长占用时间,
            // 让别的线程在本线程同步代码执行完后,有更多机会拿到 cpu 使用权
            Thread.sleep(500);
        } catch (Exception e) {
            e.printStackTrace();
        } finally {
            lock.unlock();
        }
    }
}

synchronized 和 lock 区别(自动档与手动档的区别)

  1. Synchronized 内置的Java关键字, Lock 是一个Java类
  2. Synchronized 无法判断获取锁的状态,Lock 可以判断是否获取到了锁
  3. Synchronized 会自动释放锁,lock 必须要手动释放锁!如果不释放锁,死锁
  • Synchronized 线程 1(获得锁,阻塞)、线程2(等待,傻傻的等);
  • Lock锁就不一定会等待下去(lock.tryLock(); 尝试获取锁的方法);
  1. Synchronized 可重入锁,不可以中断的,非公平;

    Lock ,可重入锁,可以 判断锁,非公平(可以自己设置);

  2. Synchronized 适合锁少量的代码同步问题,Lock 适合锁大量的同步代码!

锁是什么,如何判断锁的是谁!(八锁问题)

4.生产者和消费者模式问题

面试必问:单例模式、排序算法、生产者和消费者、死锁

生产者和消费者问题 Synchronized 版

public class ProandCus {
    // A,B线程 交替执行  + 1  、-1 操作
    // A+1    B-1
    public static void main(String[] args) {
        Data data = new Data();
        new Thread(()->{ for(int i=0;i<10;i++) data.increment(); },"生产者A").start();
        new Thread(()->{ for(int i=0;i<10;i++) data.decrement(); },"消费者B").start();
    }
}

class Data {  // 创建资源类
   private  int num =0;
    //  加 1 操作
    public synchronized  void increment()  {
        if(num !=0){ // 是 0 才行,否则等待
            try {
                this.wait(); // 等待
            } catch (InterruptedException e) {
                e.printStackTrace();
            }
        }
         num++;
        System.out.println(Thread.currentThread().getName()+"===>"+num);
        this.notifyAll(); // 唤醒其他线程 + 1完毕
    }
   // 减去 1操作
    public synchronized  void decrement()  {
        if(num == 0){ // 是 1 才行,否则等待
            try {
                this.wait();// 等待
            } catch (InterruptedException e) {
                e.printStackTrace();
            }
        }
        num--;
        System.out.println(Thread.currentThread().getName()+"===>"+num);
        this.notifyAll();// 唤醒其他线程 -1完毕
    }
}

如果有 A B C D 四个线程?

问题存在,A B C D 4 个线程! 虚假唤醒 java.lang.object --> wait
在这里插入图片描述if 改为 while 判断

  • if 判断只判断一次,有可能多个线程同时进去、等待应该出现在循环中。
public class ProandCus {

    public static void main(String[] args) {
        Data data = new Data();
        new Thread(()->{ for(int i=0;i<10;i++) data.increment(); },"生产者A").start();
        new Thread(()->{ for(int i=0;i<10;i++) data.decrement(); },"消费者B").start();
        new Thread(()->{ for(int i=0;i<10;i++) data.increment(); },"生产者C").start();
        new Thread(()->{ for(int i=0;i<10;i++) data.decrement(); },"消费者D").start();
    }
}

class Data {  // 创建资源类

   private  int num =0;
    //  加 1 操作
    public synchronized  void increment()  {
        while(num !=0){ // 是 0 才行,否则等待
            try {
                this.wait();// 等待
            } catch (InterruptedException e) {
                e.printStackTrace();
            }
        }
         num++;
        System.out.println(Thread.currentThread().getName()+"===>"+num);
        this.notifyAll(); // 唤醒其他线程 + 1完毕
    }
   // 减去 1操作
    public synchronized  void decrement()  {
        while(num == 0){ // 是 1 才行,否则等待
            try {
                this.wait();// 等待
            } catch (InterruptedException e) {
                e.printStackTrace();
            }
        }
        num--;
        System.out.println(Thread.currentThread().getName()+"===>"+num);
        this.notifyAll();// 唤醒其他线程 -1完毕
    }
}

JUC版的生产者和消费者问题
在这里插入图片描述
在这里插入图片描述

public class ProandCus2 {

    public static void main(String[] args) {
        Data2 data2 = new Data2();
        new Thread(()->{ for(int i=0;i<10;i++) data2.increment(); },"生产者A").start();
        new Thread(()->{ for(int i=0;i<10;i++) data2.decrement(); },"消费者B").start();
        new Thread(()->{ for(int i=0;i<10;i++) data2.increment(); },"生产者C").start();
        new Thread(()->{ for(int i=0;i<10;i++) data2.decrement(); },"消费者D").start();
    }
}

class Data2 {  // 创建资源类

   private  int num =0;
   Lock lock = new ReentrantLock();
   Condition condition = lock.newCondition();

    //  加 1 操作
    public   void increment()  {
        lock.lock(); // 上锁

        try {
            while (num !=0){ // 是 0 才行,否则等待
                    condition.await();// 等待
            }
            num++;
            System.out.println(Thread.currentThread().getName()+"===>"+num);
            condition.signalAll(); // 唤醒其他线程 + 1完毕
        } catch (Exception e) {
            e.printStackTrace();
        } finally {
            lock.unlock(); //解锁
        }
    }
   // 减去 1操作
    public   void decrement()  {
        lock.lock(); // 上锁

        try {
            while (num ==0){ // 不是 0 才行,否则等待
                condition.await();// 等待
            }
            num--;
            System.out.println(Thread.currentThread().getName()+"===>"+num);
            condition.signalAll(); // 唤醒其他线程 -1完毕
        } catch (Exception e) {
            e.printStackTrace();
        } finally {
            lock.unlock(); //解锁
        }
    }
}

任何一个新的技术,绝对不是仅仅覆盖原来的就技术,一定有优势和补充。

condition 精准的通知和唤醒

public class ProandCus3 {
   // 1-A  2-B  3-C
    public static void main(String[] args) {
        Data3 data3 = new Data3();
        new Thread(()->{ for(int i=0;i<10;i++) data3.print1(); },"A").start();
        new Thread(()->{ for(int i=0;i<10;i++) data3.print2(); },"B").start();
        new Thread(()->{ for(int i=0;i<10;i++) data3.print3(); },"C").start();
    }
}

class Data3 {
   Lock lock = new ReentrantLock();
   // 创建三个监听器
   Condition condition1 = lock.newCondition();
   Condition condition2 = lock.newCondition();
   Condition condition3 = lock.newCondition();

    private  int num =1;

    public   void print1()  {
        lock.lock(); // 上锁
        try {
            while (num !=1){ // 是 1 才行,否则等待
                    condition1.await();// 等待
            }
            num = 2;
            System.out.println(Thread.currentThread().getName()+"===>"+num);
            condition2.signal(); // 唤醒2线程
        } catch (Exception e) {
            e.printStackTrace();
        } finally {
            lock.unlock(); //解锁
        }
    }
    public  void print2()  {
      lock.lock();
        try {
            while (num != 2){ // 是 2 才行,否则等待
               condition2.await(); // 等待
            }
            num = 3;
            System.out.println(Thread.currentThread().getName()+"===>"+num);
            condition3.signal();// 唤醒3线程
        } catch (Exception e) {
            e.printStackTrace();
        } finally {
           lock.unlock();
        }
    }
    public  void print3()  {
        lock.lock();
        try {
            while (num != 3){ // 是 3 才行,否则等待
                condition3.await(); // 等待
            }
            num = 1;
            System.out.println(Thread.currentThread().getName()+"===>"+num);
            condition1.signal();// 唤醒1线程
        } catch (Exception e) {
            e.printStackTrace();
        } finally {
            lock.unlock();
        }
    }
}
5. 八锁现象

判断锁的是谁! 对象、class

import java.util.concurrent.TimeUnit;
/**
* 8锁,就是关于锁的8个问题
* 1、标准情况下,两个线程先打印 发短信还是 打电话? 1/发短信 2/打电话
* 1、sendSms延迟4秒,两个线程先打印 发短信还是 打电话? 1/发短信 2/打电话
*/
public class Test01 {
    public static void main(String[] args) {
        Phone phone = new Phone();
        //锁的存在
        new Thread(()->{ phone.sendSms();},"A").start();

        try {
            TimeUnit.SECONDS.sleep(1);  // JUC的休眠方式
        } catch (InterruptedException e) {e.printStackTrace();
        }

        new Thread(()->{phone.doCall();},"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 doCall(){
        System.out.println("打电话");
    }
}

======================================================
import java.util.concurrent.TimeUnit;
/**
* 3、 两个对象,两个同步方法, 发短信还是 打电话? // 打电话
*/
public class Test02 {
    public static void main(String[] args) {
      // 两个对象,两个调用者,两把锁!
        Phone2 phone1 = new Phone2();
        Phone2 phone2 = new Phone2();
      //锁的存在
        new Thread(()->{ phone1.sendSms();},"A").start();

        try {
            TimeUnit.SECONDS.sleep(1);
        } catch (InterruptedException e) {
            e.printStackTrace();
        }
        new Thread(()->{phone2.doCall();},"B").start();
    }
}
class Phone2 {
    // synchronized 锁的对象是方法的调用者!
    public synchronized void sendSms() {
        try {
            TimeUnit.SECONDS.sleep(4);
        } catch (InterruptedException e) {
            e.printStackTrace();
        }
        System.out.println("发短信");
    }

    public synchronized void doCall() {
        System.out.println("打电话");
    }
}
=======================================================
import java.util.concurrent.TimeUnit;
/**
* 4、 增加了一个普通方法后!先执行发短信还是Hello? 普通方法
*/
public static void main(String[] args) {
     // 两个对象,两个调用者,两把锁!
    Phone3 phone = new Phone3();
    //锁的存在
    new Thread(()->{ phone.sendSms(); },"A").start();

    try {
        TimeUnit.SECONDS.sleep(1);
    } catch (InterruptedException e) {
        e.printStackTrace();
    }
    new Thread(()->{ phone.hello(); },"B").start();
    }
}
class Phone3{
    // synchronized 锁的对象是方法的调用者!
    public synchronized void sendSms(){
        try {
            TimeUnit.SECONDS.sleep(4);
        } catch (InterruptedException e) {
            e.printStackTrace();
        }
        System.out.println("发短信");
    }
    public synchronized void doCall(){
        System.out.println("打电话");
    }
    // 这里没有锁!不是同步方法,不受锁的影响
    public void hello(){
        System.out.println("hello");
    }
    }
=====================================================
import java.util.concurrent.TimeUnit;
/**
* 5、增加两个静态的同步方法,只有一个对象,先打印? 发短信->打电话
* 6、两个对象!增加两个静态的同步方法, 先打印 发短信->打电话
*/
public class Test04 {
    public static void main(String[] args) {
        // 两个对象的Class类模板只有一个,static,锁的是Class
        Phone4 phone1 = new Phone4();
        Phone4 phone2 = new Phone4();
        //锁的存在
        new Thread(() -> { phone1.sendSms(); }, "A").start();

        try {
            TimeUnit.SECONDS.sleep(1);
        } catch (InterruptedException e) {
            e.printStackTrace();
        }
        new Thread(() -> { phone2.doCall(); }, "B").start();
    }
}
// Phone4唯一的一个 Class 对象
class Phone4{
// synchronized 锁的对象是方法的调用者!
// static 静态方法
// 类一加载就有了!锁的是Class
    public static synchronized void sendSms(){
        try {
            TimeUnit.SECONDS.sleep(4);
        } catch (InterruptedException e) {
            e.printStackTrace();
        }
        System.out.println("发短信");
    }
    public static synchronized void doCall(){
        System.out.println("打电话");
    }
}
===========================================
import java.util.concurrent.TimeUnit;
/**
* 7、1个静态的同步方法,1个普通的同步方法 ,一个对象,先打印 发短信?打电话?
* 8、1个静态的同步方法,1个普通的同步方法 ,两个对象,先打印 发短信?打电话?
*/
public class Test05 {
    public static void main(String[] args) {
        // 两个对象的Class类模板只有一个,static,锁的是Class
        Phone5 phone1 = new Phone5();
        Phone5 phone2 = new Phone5();
        //锁的存在
        new Thread(()->{ phone1.sendSms(); },"A").start();
        try {
            TimeUnit.SECONDS.sleep(1);
        } catch (InterruptedException e) {
            e.printStackTrace();
        }
        new Thread(()->{ phone2.doCall(); },"B").start();
    }
}
   // Phone5唯一的一个 Class 对象
class Phone5{
    // 静态的同步方法 锁的是 Class 类模板
    public static synchronized void sendSms(){
        try {
            TimeUnit.SECONDS.sleep(4);
        } catch (InterruptedException e) {
            e.printStackTrace();
        }
        System.out.println("发短信");
    }
    // 普通的同步方法 锁的调用者
    public synchronized void doCall(){
        System.out.println("打电话");
    }
}

小结

synchronzied 锁指向被调用者~(跟同步方法加不加 static 有很大关系)

new 出来的对象,调用同步方法 --> 锁对象是:this 具体的一个new出来的对象

new 出来的对象,调用加static的 方法 --> 锁对象是: class 唯一的一个模板

6.集合类不安全

list 集合

    public static void main(String[] args) {
        List<String> list = new ArrayList<>();
        for (int i = 0; i <30 ; i++) {
            new Thread(()->{
                list.add(UUID.randomUUID().toString().substring(0,5));
                System.out.println(list);
            },String.valueOf(i)).start();
        }
    }

报错:java.util.ConcurrentModificationException并发修改异常。

并发争取修改导致,一个线程正在写,一个线程过来争抢,导致线程写的过程被其他线程打断,导致数据不一致。
在这里插入图片描述

  • 解决方案
    public static void main(String[] args) {
        //1.
        // List<String> list = new Vector<>();
        //2.
        // List<String> list = Collections.synchronizedList(new ArrayList<>());
        //3.
        List<String> list = new CopyOnWriteArrayList<>();

        for (int i = 0; i <30 ; i++) {
            new Thread(()->{
                list.add(UUID.randomUUID().toString().substring(0,5));
                System.out.println(list);
            },String.valueOf(i)).start();
        }
    }
第一种:使用List arrayList = new Vector<>();它的底层使用了synchronized加锁,但是并发速度下降

在这里插入图片描述

第二种:使用List arrayList = Collections.synchronizedList(new ArrayList()); 使用 Collections工具类,线程同步方法。

第三种:JUC方式,使用List arrayList = new CopyOnWriteArrayList<>();写时复制,效率最高

在这里插入图片描述

这样写时复制实现了读写分离,我们不需要在读的时候加锁(之前读需要加锁是因为读写不能同时进行,但一旦给读加了锁,那么读也不能同时进行,就降低了并发效率)。

set集合

public static void main(String[] args) {
    // 解决方案
    //1.变成线程安全的
    // Set<String> hashSet = Collections.synchronizedSet(new HashSet<>());
    //2. JUC方式
     Set<String> hashSet = new CopyOnWriteArraySet<>(); 
    //(它的底层还是 new CopyOnWriteArrayList<>();)
    for (int i = 0; i <30 ; i++) {
        new Thread(()->{
            hashSet.add(UUID.randomUUID().toString().substring(0,5));
            System.out.println(hashSet);
        },String.valueOf(i)).start();
    }
}
  • 解决方案

    第一种:Set hashSet = Collections.synchronizedSet(new HashSet<>());

    第二种:Set hashSet = new CopyOnWriteArraySet();(它的底层还是 new CopyOnWriteArrayList<>();)

hashSet 底层是什么? HashSet的底层就是HashMap

public HashSet() {
    map = new HashMap<>();
}
====================
    // add  set本质就是 map key 是无法重复的
    //使用HashSet的add方法,依然是调用HashMap的底层put方法
    public boolean add(E e) {
    return map.put(e, PRESENT)==null;
}
====================
    private static final Object PRESENT = new Object();// 是个固定值

map 集合

基本回顾: 1.map 在工作中不用 hashMap
2.new HashMap<>(); 默认等价于什么? new HashMap<>(16,0.75);

在这里插入图片描述

public static void main(String[] args) {
        //Map<String,String> hashMap = new HashMap<>();
        // 解决方案
        //1.变成线程安全的
        //Map<String,String> hashMap = Collections.synchronizedMap(new HashMap<>());
        //2.JUC方式
        Map<String,String> hashMap = new ConcurrentHashMap<>();
        for (int i = 0; i <30 ; i++) {
            new Thread(()->{
  hashMap.put(Thread.currentThread().getName(),UUID.randomUUID().toString().substring(0,5));
                System.out.println(hashMap);
            },String.valueOf(i)).start();
        }
    }
  • 解决方案:

    第一种:Map<String,String> hashMap = Collections.synchronizedMap(new HashMap<>());

    第二种:Map<String,String> hashMap = new ConcurrentHashMap<>(); //无论读取还是写入,都会进行加锁,当并发级别特别高,线程之间在任何操作上都会进行等待,效率低。

  • ConcurrentHashMap是面试中常问的问题

采用分段锁技术,其中Segment继承于ReentrantLock。不会像HashTable(线程安全) 那样不管是put还是get操作都需要做同步处理,理论上ConcurrentHashMap支持CurrentLevel(Segment数组数量)的线程并发。每当一个线程占用锁访问一个Segment时,不会影响其他的Segment。

7.Callable(实现多线程的第三种方式)

Callable与 Runnable

java.util.concurrent.Callabel, Callable是一个接口,类似于Runnable,两者都是为那些实例可能被另一个线程执行的类设计的。

在这里插入图片描述

java.lang.Runnable, 是一个接口。其实例由线程执行。该类必须定义一个无参数的方法称为run。Runnable不会返回结果,也不会抛出异常。run方法的异常只能在内部消化不能上抛。

在这里插入图片描述

结论:

  1. Callable可以有返回值
  2. Callable可以抛出异常
  3. 方法不同call()/run()
  4. Callable与Future结合,实现利用Future来跟踪异步计算的结果

Callable 如何启动

new Thread().start 中参数只能是 Runable,不能是callable;callable通过 Runable 使用Thread,通过适配类 futrueTask。

①futrueTask 是Runable 的实现类

②futrueTask 可以调用callable

在这里插入图片描述


在这里插入图片描述

public class CallableTest {
    //new Thread(new Runnable()).start();
    //new Thread(new FutureTask<V>()).start();
    //new Thread(new FutureTask<V>( Callable )).start();
    //new Thread().start();
    // 怎么启动Callable?
    public static void main(String[] args) throws Exception {
        Mythread mythread = new Mythread();
        FutureTask futureTask = new FutureTask(mythread);//适配类
        new Thread(futureTask,"线程A").start();
        //call ()只打印一次,结果会被缓存,提高效率
        new Thread(futureTask,"线程B").start();
        //方法中使用耗时操作,get这个方法可能会产生阻塞,把它放到最后 或 使用异步通信
        Integer result = (Integer) futureTask.get();
        System.out.println(result);
    }
}
class Mythread implements Callable<Integer> {
    @Override
    public Integer call() throws Exception {
        // 耗时操作...
        System.out.println("这是callable的 call()方法");
        return 1024;
    }
}

小结:

1.Callable 有缓存

2.结果可能需要等待,会阻塞。

异步计算:

用不同的线程去做不同的事,最后再进行数据汇总并返回结果。是分布式计算的一种实现方式。

8.常用辅助类(高并发必会)
8.1 .CountDownLatch 减法计数器

java.util.CountDownLatch 倒计时锁存器,是一个同步辅助类,在完成一组正在其他线程中执行的操作之前,它允许一个或多个线程一直等待。

public class CountDownLatchTest {
    // 减法计数器
    public static void main(String[] args) throws InterruptedException {
        //总数是6,必须要执行任务的时候,再使用
        CountDownLatch countDownLatch = new CountDownLatch(6);
        for (int i = 1; i <7 ; i++) {
            new Thread(()->{
                System.out.println(Thread.currentThread().getName()+"出去");
                countDownLatch.countDown(); // 数量-1
            },String.valueOf(i)).start();
        }
        countDownLatch.await(); // 等待计数器归零,再往下执行代码

        System.out.println("关门");
    }
}

总结

countDownLatch.countDown(); // 数量-1

countDownLatch.await();// 等待计数器归零,再往下执行代码。

每次有线程调用 countDown() -1,假设计数器变为0,countDownLatch.await();就会被唤醒继续执行。

8.2.CyclicBarrier 加法计数器 (栅栏,循环屏障)

是一个同步辅助类,允许一组线程相互等待直到所有线程到达一个公共的屏障点。在程序中有固定数量的线程,这些线程有时候必须等待彼此,这种情况下,使用CyclicBarrier。能阻塞一组线程直到某个事件的发生。它与闭锁的区别关键在于,所有的线程必须同时到达栅栏位置,才能继续执行。闭锁用于等待事件,而栅栏用于等待其他线程。

例子:长途汽车站提供长途客运服务。当等待坐车的乘客到达20人时,汽车站就会发出一辆长途汽车,让这20个乘客上车走人。等到下次等待的乘客又到达20人是,汽车站就会又发出一辆长途汽车。

public class CyclicBarrierTest {
    public static void main(String[] args) {
        // 出生葫芦娃的线程
        CyclicBarrier cyclicBarrier = new CyclicBarrier(8,()->{
            System.out.println("八个葫芦娃破壳出生!");
        });
        for (int i = 1; i < 9; i++) {
            //lambda 不能操作到 i,要通过 final 变量
            final int temp =i;
            new Thread(()->{
                System.out.println(Thread.currentThread().getName()+"葫芦藤正在结出第"+temp+"个葫芦~");
                try {
                    cyclicBarrier.await(); // 等待
                } catch (InterruptedException e) {
                    e.printStackTrace();
                } catch (BrokenBarrierException e) {
                    e.printStackTrace();
                }
            }).start();
        }
    }
}
8.3 Semaphore 信号量;

也是一个线程同步的辅助类,一个计数信号量可以维护当前访问自身的线程个数,并提供了同步机制。semaphore可以控制同时访问资源的线程个数,而不是访问某些资源。例如,实现一个文件允许的并发访问数。

主要有两个目的:

  • 用于多个共享资源的互斥使用
  • 用于并发线程数的控制 (限流!)

Semaphore可以达到复用,有减,有加,一直使用。

6车-3个停车位,占住车位的开走了,才能进新的车

public class SemaphoreTest {
    public static void main(String[] args) {
        // 线程数量-->停车位!限流!
        Semaphore semaphore = new Semaphore(3);
        for (int i = 1; i < 7; i++) {
            new Thread(()->{
                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(); // 释放
                }
            },String.valueOf(i)).start();
        }
    }
}

总结:

semaphore.acquire();获得 ,会将当前的信号量释放 -1,假设如果已经满了,等待,等到被释放为止。

semaphore.release();释放,会将当前的信号量释放 +1 ,然后唤醒等待的线程。

9.读写锁 ReadWriteLock

在这里插入图片描述

public class ReadWriteLockTest {
    /**
     *独占锁(写锁) 一次只能被一个线程占有
     *共享锁(读锁) 多个线程可以同时占有
     *ReadWriteLock
     * 读--读  可以共存
     * 写--读  不能共存
     * 写--写  不能共存
     */
    public static void main(String[] args) {
        MyCacheLock myCache = new MyCacheLock();
        // 写入
        for (int i = 1; i <=30 ; i++) {
           final int temp =i;
            new Thread(()->{
                myCache.writeMethod(temp+"",temp);
            },String.valueOf(i)).start();
        }
        // 读取
        for (int i = 1; i <=30 ; i++) {
            final int temp =i;
            new Thread(()->{
                myCache.readMethod(temp+"");
            },String.valueOf(i)).start();
        }
    }

}
//  普通缓存类
class MyCache{

    private volatile Map<String,Object> map = new HashMap();

    public void writeMethod(String key,Object value){
        System.out.println(Thread.currentThread().getName()+"正在写入...");
        map.put(key,value);
        System.out.println(Thread.currentThread().getName()+"写入完毕");
    }

    public void readMethod(String key){
        System.out.println(Thread.currentThread().getName()+"正在读取...");
        map.get(key);
        System.out.println(Thread.currentThread().getName()+"读取完毕");
    }
}
//  读写锁缓存类
class MyCacheLock{

    private volatile Map<String,Object> map = new HashMap();
    // 读写锁:更加细粒度的控制
    private ReadWriteLock readWriteLock = new ReentrantReadWriteLock();
    // 写入时,只希望同时只有一个线程
    public void writeMethod(String key,Object value){
        readWriteLock.writeLock().lock();// 上写锁
        try {
            System.out.println(Thread.currentThread().getName()+"正在写入...");
            map.put(key,value);
            System.out.println(Thread.currentThread().getName()+"写入完毕");
        } catch (Exception e) {
            e.printStackTrace();
        } finally {
            readWriteLock.writeLock().unlock(); // 释放写锁
        }

    }
    // 读时,希望所有线程都可以读。
    public void readMethod(String key){
        readWriteLock.readLock().lock(); // 上读锁
        try {
            System.out.println(Thread.currentThread().getName()+"正在读取...");
            map.get(key);
            System.out.println(Thread.currentThread().getName()+"读取完毕");
        } catch (Exception e) {
            e.printStackTrace();
        } finally {
            readWriteLock.readLock().unlock();// 释放读锁
        }
    }
}

多线程同时读一个资源类没有问题,所以为了满足并发量,读取共享资源应该可以同时进行。但是,如果有一个线程想去写共享资源,就不应该有其他线程对该资源进行读或写

10.BlockingQueue 阻塞队列

在这里插入图片描述

是一个支持两个操作的队列:不得不阻塞

  • :在队列为空时,必须阻塞等待生产。
  • :当队列满时,就必须阻塞等待。

常用于生产者和消费者的场景,生产者—>往队列里添加元素,消费者—>从队列里拿取元素。

在这里插入图片描述

阻塞队列的用处:

在多线程领域:所谓阻塞,在某些情况下会挂起线程(即阻塞),一旦条件满足,被挂起的线程又会自动被唤起。我们不用担心什么时候需要阻塞线程,什么时候需要唤醒线程,因为这一切BlockingQueue都做了。

(参考:https://zhuanlan.zhihu.com/p/268678714)

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

  • ArrayBlockingQueue:由数组结构组成的有界阻塞队列
  • LinkedBlockingQueue:由链表结构组成的有界阻塞队列
  • SynchronousQueue:不存储元素的阻塞队列,即单个元素队列
  • PriorityBlockingQueue:支持优先级排序的无界阻塞队列
  • DelayQueue:使用优先级队列实现的延迟无界阻塞队列
  • LinkedTransferQueue:由列表组成的无界阻塞队列
  • LinkedBlockingDeque:由链表组成的双向阻塞队列

BlockingQueue 是Collection的一个子类

什么情况下我们会使用阻塞队列?多线程并发处理、线程池

学会使用队列:添加、移除

四组API:

方式抛出异常有返回值,不抛出异常阻塞等待(死等)超时等待(不死等)
添加add(e)offer(e)put(e)offer(e,time,timeUnit)
移除remove()或remove(e)poll()take()poll(time,timeUnit)
检测队首元素elementpeek-不可用-不可用
public class BlockingQueueTest {
    public static void main(String[] args) {
        Test1();
    }
    /**
     * 抛出异常:
     */
    public static void  Test1(){
        ArrayBlockingQueue blockingQueue = new ArrayBlockingQueue(3);// 容量为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.add("D"));// java.lang.IllegalStateException: Queue full
        System.out.println(blockingQueue.element());// 队首 A
        System.out.println(blockingQueue.remove());// remove方法移除队首元素
        System.out.println(blockingQueue.remove());
        System.out.println(blockingQueue.remove());
// System.out.println(blockingQueue.remove());// 没有了java.util.NoSuchElementException
        System.out.println(blockingQueue.remove("E")); // 删除不存在 元素false
    }
}

抛出异常总结:

  • add(e):当队列容量已满,再添加元素就会抛出IllegalStateException:Queue full异常
  • remove():当队列没有元素,再移除元素,则抛出NoSuchElementException;
    如果删除一个队列中不存在的元素,不会抛出异常,返回false
  • element():如果队列为空,则抛出NoSuchElementException,若不为空,则返回第一个元素。
    /**
     * 有返回值,没有异常
     */
    public static void  Test2(){
        ArrayBlockingQueue blockingQueue = new ArrayBlockingQueue(3);// 容量为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.offer("D"));// 超出队列 false

        System.out.println(blockingQueue.peek());// 队首 A

        System.out.println(blockingQueue.poll());// poll方法移除队首元素
        System.out.println(blockingQueue.poll());
        System.out.println(blockingQueue.poll());
        System.out.println(blockingQueue.poll());// 队列里没有了 null,不抛异常

    }

有返回值,不抛出异常总结:

  • offer(e):如果可以的话,将元素添加到队列,返回true;
    如果队列已满,则不添加,返回false.不阻塞执行线程
  • poll():从队列中返回第一个元素,如果没有返回null
  • peek():从队列中返回第一个元素,如果没有返回null
    /**
     * 等待阻塞
     */
    public static void  Test3() throws InterruptedException {
        ArrayBlockingQueue blockingQueue = new ArrayBlockingQueue(3);// 容量为3
        blockingQueue.put("A");
        blockingQueue.put("B");
        blockingQueue.put("C");
// blockingQueue.put("D");//  超了容量 一直等待

        System.out.println(blockingQueue.peek());// 队首 A

        System.out.println(blockingQueue.take());// take方法移除队首元素
        System.out.println(blockingQueue.take());
        System.out.println(blockingQueue.take());
        System.out.println(blockingQueue.take());// 阻塞,一直等待
    }
}

等待阻塞总结:

  • put(e):把元素添加到阻塞队列中,如果阻塞队列没有空间,则调用此方法的线程被阻断直到队列有空间再继续
  • take():如果取到则返回,如果没有取到,则一直阻塞,直到队列不为空
    /**
     * 超时等待
     */
    public static void  Test4() throws InterruptedException {
        ArrayBlockingQueue blockingQueue = new ArrayBlockingQueue(3);// 容量为3
        System.out.println(blockingQueue.offer("A"));// true
        System.out.println(blockingQueue.offer("B"));// true
        System.out.println(blockingQueue.offer("C"));// true

        // 等待2退出
        System.out.println(blockingQueue.offer("D", 2, TimeUnit.SECONDS)); //   false

        System.out.println(blockingQueue.peek());// 队首 A

        System.out.println(blockingQueue.poll());// poll方法移除队首元素
        System.out.println(blockingQueue.poll());
        System.out.println(blockingQueue.poll());
        System.out.println(blockingQueue.poll(2,TimeUnit.SECONDS));// 阻塞只等待2s
    }
}

超时等待总结:

  • offer(e,time,unit):将指定的元素插入到此队列中,等待指定的等待时间。
  • poll(time):检索并删除此队列的头,等待指定的等待时间。
SynchronousQueue 同步队列

没有容量,进去一个元素,必须等待取出来之后,才能再往里放另外的元素。

方式: put 、take

public class SynchronousQueueTest {

    public static void main(String[] args) {

        SynchronousQueue synchronousQueue = new SynchronousQueue();// 没有容量

        new Thread(()->{
            try {
                synchronousQueue.put("A");
                System.out.println(Thread.currentThread().getName()+"存放了  A");
                synchronousQueue.put("B");
                System.out.println(Thread.currentThread().getName()+"存放了  B");
                synchronousQueue.put("C");
                System.out.println(Thread.currentThread().getName()+"存放了  C");

            } catch (InterruptedException e) {
                e.printStackTrace();
            }
        },"线程一").start();


        new Thread(()->{
            try {
                TimeUnit.SECONDS.sleep(2);
                System.out.println(Thread.currentThread().getName()+"取出 "+ 		synchronousQueue.take());
                TimeUnit.SECONDS.sleep(2);
                System.out.println(Thread.currentThread().getName()+"取出 "+ synchronousQueue.take());
                TimeUnit.SECONDS.sleep(2);
                System.out.println(Thread.currentThread().getName()+"取出 "+ synchronousQueue.take());

            } catch (InterruptedException e) {
                e.printStackTrace();
            }
        },"线程二").start();
        
    }
}

总结:

没有容量,一个生产线程,当它生产产品(put的时候),如果当前没有人想要消费产品(即当前没有线程执行take),此生产线程必须阻塞。等待一个消费线程调用take操作,take操作会唤醒该生产线程,同时消费线程会获取生产线程的产品(即数据传递)。这样的一个过程称为一次配对过程(也可以先take,后put 原理相同)。

11.线程池(重点)

线程池:三大方法、7大参数、4种拒绝策略

程序运行的本质:占用系统的资源! 优化资源的使用!=>池化技术

池化技术:通过预先创建好多个线程,放在池中,这样可以在需要使用线程的时候直接获取,用完之后放回池中,避免多次重复创建、销毁带来的开销。(创建、销毁,十分浪费资源)

线程池、连接池、内存池、对象池…

线程池的好处:

1、 降低资源的消耗

2、 提高响应的速度

3、 方便管理。

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


阿里巴巴开发手册

在这里插入图片描述

三大方法

Executors.newSingleThreadExecutor();

Executors.newFixedThreadPool(5);

newCachedThreadPool();

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

        // 最大承载:Deque + max
        for (int i = 1; i <=5 ; i++) {
            threadpool.execute(()->{
                try {
                    System.out.println(Thread.currentThread().getName()+"---执行");
                } catch (Exception e) {
                    e.printStackTrace();
                } finally {
                    // 线程池用完,程序结束,关闭线程池
                    threadpool.shutdown();
                }
            });
        }
    }
}

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

// @Native public static final int   MAX_VALUE = 0x7fffffff;  略为 21 亿
=======================================================================================
    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;
    }

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

  • new ThreadPoolExecutor.AbortPolicy() // 银行满了,还有人进来,不处理这个人的,抛出异常。
    超出最大承载,就会抛出异常:队列容量大小+maxPoolSize。

  • new ThreadPoolExecutor.CallerRunsPolicy() // 哪来的去哪里!–》 main线程

  • new ThreadPoolExecutor.DiscardPolicy() //队列满了,丢掉任务,不会抛出异常!

  • new ThreadPoolExecutor.DiscardOldestPolicy() //队列满了,尝试去和最早的竞争,也不会抛出异常!

    银行排队例子

在这里插入图片描述

自定义线程池
public class MyPool {

    public static void main(String[] args) {
        // 最大承载:max + Deque  5 + 3
        ExecutorService threadpool = new ThreadPoolExecutor(2,
                5,
                2,
                TimeUnit.SECONDS,
                new LinkedBlockingQueue<>(3),
                Executors.defaultThreadFactory(),// 一般默认不改
                new ThreadPoolExecutor.DiscardOldestPolicy()
                );
        try {
            for (int i = 1; i <=9 ; i++) {
                threadpool.execute(()->{
                    System.out.println(Thread.currentThread().getName()+"执行");
                });
            }
        } catch (Exception e) {
            e.printStackTrace();
        } finally {
            threadpool.shutdown();
        }
    }
}
如何设置线程池的大小(CPU密集型和IO密集型)

CPU密集型和IO密集型:

1、CPU密集型:电脑的核数是几核就选择几;选择maximunPoolSize的大小

       // 获取cpu 的核数
        int max = Runtime.getRuntime().availableProcessors();
        ExecutorService service =new ThreadPoolExecutor(
                2,
                max, // cpu 的核数
                3,
                TimeUnit.SECONDS,
                new LinkedBlockingDeque<>(3),
                Executors.defaultThreadFactory(),
                new ThreadPoolExecutor.AbortPolicy()
        );

2、I/O密集型:

I/O密集型就是判断我们程序中十分耗I/O的线程数量,设置要大于十分消耗I/O线程数量;是最大I/O数的一倍到两倍之间。

12.四大函数式接口(重点)

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

函数式接口,只有一个方法的接口
在这里插入图片描述
在这里插入图片描述

Function函数式接口

有一个输入参数,有一个输出
在这里插入图片描述

只要是函数型接口,都可以用 lambda表达式简化

public class funcTest {
    public static void main(String[] args) {
//        Function<String,String> function = new Function<String,String>() {
//            @Override
//            public String apply(String str) {
//                return o+"函数式";
//            }
//        };
        Function<String,String> function=(str)->{return str+"函数式";};
        System.out.println(function.apply("传入"));

    }
}
Predicate 断定型接口

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

public class PredicateTest{
    public static void main(String[] args) {
//        Predicate<String> p = new Predicate<String>() {
//            @Override
//            public boolean test(String str) {
//                return str.isEmpty();
//            }
//        };

        Predicate<String> p= (str)->{
            return str.isEmpty();
        };
        System.out.println(p.test("传入字符串"));
    }
}

Consumer 消费型接口

Consumer 消费型接口: 只有输入,没有返回值
在这里插入图片描述

public class ConsumerTest {
    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 = (str)->{System.out.println(str+"传进来了");};

        consumer.accept("big");
    }
}

Supplier 供给型接口

Supplier 供给型接口 没有参数,只有返回值
在这里插入图片描述

public class SupplierTest {
    public static void main(String[] args) {
//        Supplier<String> supplier = new Supplier() {
//            @Override
//            public Object get() {
//                return "输出字符串!";
//            }
//        };
        Supplier<String> supplier =()->{return "输出字符串!";};
        System.out.println(supplier.get());
    }
}
13.strem流式计算(并行流)

大数据:存储 + 计算

存储:集合、MySQL 本质就是存储东西的;

计算:都应该交给来操作!

大数据scala、框架底层也用得到。

@Data
@AllArgsConstructor
@NoArgsConstructor
public class User {
    private int id;
    private String name;
    private int age;
}
=============================================
public class StreamTest {
/**
 * 题目要求:一分钟内完成此题,只能用一行代码实现!
 * 现在有5个用户!筛选:
 * 1、ID 必须是偶数
 * 2、年龄必须大于23岁
 * 3、用户名转为大写字母
 * 4、用户名字母倒着排序
 * 5、只输出一个用户!
 */
	public static void main(String[] args) {

        User user1 = new User(1,"a",21);
        User user2 = new User(2,"b",22);
        User user3 = new User(3,"c",23);
        User user4 = new User(4,"d",24);
        User user5 = new User(5,"e",25);
        User user6 = new User(6,"f",26);
        User user7 = new User(7,"g",27);
        User user8 = new User(8,"h",28);

        List<User> arrayList = Arrays.asList(user1,user2,user3,user4,user5,user6,user7,user8);

        // 计算交给Stream流
        // lambda表达式、链式编程、函数式接口、Stream流式计算
        arrayList.stream().filter((user)->{return user.getId()%2 ==0;})
                          .filter((user)->{return user.getAge()>23;})
                          .map((user)->{return user.getName().toUpperCase();})
                          .sorted((u1,u2)->{return u2.compareTo(u1);})
                          .limit(1)
                          .forEach((user)->{ System.out.println(user);});

        // “函数式接口 变量名 = 类实例::方法名” 的方式对该方法进行引用
        // forEach((user)->{System.out.println(user);});  可以写为 forEach( System.out::println;)
    }
}    
14.forkjoin

什么是forkjoin

一个线程拆分成多个子任务。

Forkjoin 在JDK1.7 ,并行执行任务!提高效率。在大数据量时使用。

大数据中:MapReduce 核心思想->把大任务拆分为小任务!

在这里插入图片描述

forkjoin特点:工作窃取

实现原理:双端队列!从上面和下面都可以拿到任务去执行!
在这里插入图片描述

如何使用forkjoin

1.通过forkjoinpool来执行

2.计算任务 execute(ForkJoinTask<?> task)

3.计算类要去继承RecursiveTask;重写 compute() 方法

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

public class FrokjoinData extends RecursiveTask<Long> {

     private Long start;
     private Long end;

     //  临界值,小于临界值,采用普通计算;大于临界值采用 Frokjoin
    private Long temp =10000L;

    public FrokjoinData(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;
            FrokjoinData frokjoinData1 = new FrokjoinData(start,middle);
            frokjoinData1.fork();// 拆分任务,把任务押入线程。
            FrokjoinData frokjoinData2 = new FrokjoinData(middle,end);
            frokjoinData2.fork();// 拆分任务,把任务押入线程。

            // 汇总线程结果
            Long sumTask =frokjoinData1.join() + frokjoinData2.join();
            return sumTask;
        }
    }
}

测试类:

public class ForkjoinTest {
    private static final  Long num = 20_0000_0000L;

    public static void main(String[] args) throws ExecutionException, InterruptedException {
        test1();
        test2();
        test3();
    }
     // 使用普通方法
    public static  void  test1(){
        Long s = System.currentTimeMillis();
        Long sum =0L;
        for (int i = 0; i < num; i++) {
            sum +=i;
        }
        System.out.println(sum);
        Long e = System.currentTimeMillis();
        System.out.println("3000块程序员方法速度:"+(e-s)+"ms");
    }
    // 使用ForkJoin 方法
    public static  void  test2() throws ExecutionException, InterruptedException {
        Long s = System.currentTimeMillis();
        ForkJoinPool forkJoinPool = new ForkJoinPool();
        FrokjoinData data = new FrokjoinData(0L,num);
        ForkJoinTask<Long> submit = forkJoinPool.submit(data);
        Long sum = submit.get();

        System.out.println(sum);

        Long e = System.currentTimeMillis();
        System.out.println("6000块程序员方法速度:"+(e-s)+"ms");
    }
      // 使用 Stream 流计算
    public static  void  test3()  {
        Long s = System.currentTimeMillis();
        // 使用一个并行流去计算整个计算,提高效率。
        Long sum = LongStream.range(0L,num).parallel().reduce(0,Long::sum);
        System.out.println(sum);
        Long e = System.currentTimeMillis();
        System.out.println("9000块程序员方法速度:"+(e-s)+"ms");
    }
}

在这里插入图片描述

15.异步调用CompletableFuture

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

类比: 前端 --> 发送ajax异步请求给后端

平时都使用CompletableFuture

  • java.util.concurrent.CompletableFuture
public class CompletableFutureTest {

    public static void main(String[] args) throws ExecutionException, InterruptedException {
        test1();
        test2();
    }
    /**
     * 异步调用: CompletableFuture
     * // 异步执行
     * // 成功回调
     * // 失败回调
     */
    // 1.没有返回值的 runAsync 异步回调
    public static  void test1() throws ExecutionException, InterruptedException {
        //发起一个请求 void
        CompletableFuture<Void> completableFuture = CompletableFuture.runAsync(()->{
            try {
                TimeUnit.SECONDS.sleep(2);
            } catch (InterruptedException e) {
                e.printStackTrace();
            }
            System.out.println(Thread.currentThread().getName()+" runAsync方法执行->void 返回值");
        });
        System.out.println("主线程执行...");
        completableFuture.get(); // 获取阻塞执行结果
    }

    /**
     * 异步调用: CompletableFuture
     * // 异步执行
     * // 成功回调
     * // 失败回调
     */
    // 2.没有返回值的 supplyAsync 异步回调
    public static  void test2() throws ExecutionException, InterruptedException {
        CompletableFuture<Integer> completableFuture = CompletableFuture.supplyAsync(()->{
            System.out.println(Thread.currentThread().getName()+" supplyAsync方法->Integer 返回值");
            int i =10/0;
            return 1024;
        });

        Integer res=completableFuture.whenComplete((t, u)->{
            System.out.println("正常的返回结果->"+t);
            System.out.println("错误的返回结果->"+u);
        }).exceptionally((e)->{
            System.out.println(e.getMessage());
            return 404; // 返回错误结果
        }).get();

        //  正常的话接收正常值,如果发生了异常,get可以获取到exceptionally返回的值;
        System.out.println(res);
    }
}

whenComplete: 有两个参数,一个是t 一个是u

T:是代表的 正常返回的结果

U:是代表的 抛出异常的错误信息

如果发生了异常,get可以获取到exceptionally返回的值;

16.JMM
请你谈谈你对 Volatile 的理解

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

1、保证可见性

2、不保证原子性

3、禁止指令重排

什么是JMM

JMM : Java内存模型,不存在的东西,概念!约定!

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

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

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

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

线程的内存分为 工作内存主内存

8种操作:

读read ->加载load-> 使用use->赋值(assign) -> 写write->存储store 以及 加锁lock、解锁unlock
在这里插入图片描述

问题:线程B修改了值,但是线程A不能及时可见(线程B修改了flag=false,线程A还没有写出)

在这里插入图片描述

内存交互操作有8种,虚拟机实现必须保证每一个操作都是原子的,不可在分的(对于double和long类型的变量来说,load、store、read和write操作在某些平台上允许例外)

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

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

  • 不允许read和load、store和write操作之一单独出现,必须成对使用。即使用了read必须load,使用了store必须write

  • 不允许线程丢弃他最近的assign操作,即工作变量的数据改变了之后,必须告知主存

  • 不允许一个线程将没有assign的数据从工作内存同步回主内存一个新的变量必须在主内存中诞生,不允许工作内存直接使用一个未被初始化的变量。就是对变量实施use、store操作之前,必须经过assign和load操作

  • 一个变量同一时间只有一个线程能对其进行lock。多次lock后,必须执行相同次数的unlock才能解锁

  • 如果对一个变量进行lock操作,会清空所有工作内存中此变量的值,在执行引擎使用这个变量前,必须重新load或assign操作初始化变量的值

  • 如果一个变量没有被lock,就不能对其进行unlock操作。也不能unlock一个被其他线程锁住的变量对一个变量进行unlock操作之前,必须把此变量同步回主内存

    问题: 程序不知道主内存的值已经被修改过了

public class Test1 {
    private static int num =0;
    public static void main(String[] args) {
        new Thread(()->{ // 线程1 对主内存的变化不知道
            while (num==0){

            }
        }).start();

        try {
            TimeUnit.SECONDS.sleep(1); // 休眠事件间隔
        } catch (InterruptedException e) {
            e.printStackTrace();
        }
        num=1;
        System.out.println("主线程的num = "+num);
    }
}


在这里插入图片描述

主线程 num=1 ,线程1不知道还在循环,如何保证可见性 volatile

在这里插入图片描述

17.volatile
volatile 保证可见性
public class Test1 {
    // 如果不加volatile 程序会死循环
    // 加了volatile是可以保证可见性的
    private volatile static int num = 0;
   //private static int num =0;
    public static void main(String[] args) {
        new Thread(()->{
            while (num==0){

            }
        }).start();

        try {
            TimeUnit.SECONDS.sleep(1); // 休眠事件间隔
        } catch (InterruptedException e) {
            e.printStackTrace();
        }
        num=1;
        System.out.println("主线程的num = "+num);
    }
}

/*
* 主线程的num = 1
* Process finished with exit code 0
*/


volatile不保证原子性

原子性:不可分割;

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

public class Test2 {

    private static volatile int num =0;

    private static void add(){
        num++;  // 不是一个原子性操作,查看 字节码文件就能看出来(cmd  javap -c xxx.class)
    }
    public static void main(String[] args) {
      // 正常的结果应该是 30_000000
        for (int i = 0; i <30 ; i++) {
            new Thread(()->{
                for (int j = 0; j <1000000 ; j++) {
                    add();
                }
            }).start();
        }
        while (Thread.activeCount()>2){  // main  gc
            Thread.yield();
        }
        System.out.println(Thread.currentThread().getName()+"的 num="+num);
    }
}

// 结果每次不一样  有  num=11951181

在这里插入图片描述

如何保证原子性操作,加锁 : 使用 synchronized 或者 JUC 的 lock 给 add 方法加锁;

如果不用synchronized或者 JUC 的 lock加锁的方式呢?

使用原子类,解决原子性问题,比锁高效很多倍!

在这里插入图片描述

public class Test2 {

    private static volatile AtomicInteger num = new AtomicInteger(); // 原子类型 数据

    private synchronized static void add(){
        num.incrementAndGet(); //底层是CAS保证的原子性
    }
    public static void main(String[] args) {
      // 正常的结果应该是 30_000000
        for (int i = 0; i <30 ; i++) {
            new Thread(()->{
                for (int j = 0; j <1000000 ; j++) {
                    add();
                }
            }).start();
        }
        while (Thread.activeCount()>2){  // main  gc
            Thread.yield();
        }
        System.out.println(Thread.currentThread().getName()+"的 num="+num);
    }
}

这些类的底层都直接和操作系统挂钩!是在内存中修改值。

Unsafe类是一个很特殊的存在;(Unsafe方法都是本地方法)

原子类为什么这么高级?

3、禁止指令重排

什么是指令重排?

我们写的程序,计算机并不是按照我们自己写的那样去执行的。

源代码--》编译器优化重排--》指令并行也可能重排--》内存系统也会重排--》执行

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

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

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

可能造成的影响结果:前提: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实现的可见性)

在这里插入图片描述

(前有开路,后有断后的,谁能加塞)

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

18.玩转单例模式
1.饿汉式单例:
// 饿汉式单例-- 提前加载好对象可能会浪费内存
public class HungryMan {
    

    private HungryMan(){ // 1.私有化构造器

    }

    public static final HungryMan HUNGRY_MAN = new HungryMan();//2.直接创建好独有静态对象

    public static  HungryMan getInstance(){ // 3.抛出对外方法
        return  HUNGRY_MAN;
    } 
}

2.懒汉式单例:
// 懒汉式单例-- 用到时再加载对象
public class LazyMan {

    private LazyMan() {
    }

    private static  LazyMan lazyMan;

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

懒汉式在多线程下会出问题,考虑 双重检测锁模式 DCL懒汉式:

// 懒汉式单例-- 用到时再加载对象
public class LazyMan {

    private LazyMan() {
        System.out.println("当前线程是"+Thread.currentThread().getName()+"懒汉开启~");
    }

    private static volatile    LazyMan lazyMan; // volatile , 避免指令重排

     // 双重检测锁模式 懒汉式单例   DCL懒汉
    public static LazyMan getInstance() {

        if (lazyMan == null) {
            synchronized (LazyMan.class){  // 加class锁
                if (lazyMan == null) {
                    lazyMan = new LazyMan(); // 不是一个原子性操作
                }
            }
        }
        return lazyMan;
    }

  // 多线程并发下
  public static void main(String[] args) throws InterruptedException {
      for (int i = 0; i <30 ; i++) {
          new Thread(()->{
              LazyMan.getInstance();
          }).start();

      }
  }

lazyMan = new LazyMan();不是一个原子性操作。

正常执行步骤:1、分配内存空间 2、执行构造方法、初始化对象 3、把这个对象指向这个空间

预期: 1->2->3 实际有可能: A线程 1->2->3 进来B 线程 1->3->2 ,这时LazyMan还没有构造完成。

保证原子性操作上面代码加: volatile ,避免指令重排。

以上方式都不安全,可以通过反射破坏!

// 懒汉式单例-- 用到时再加载对象
public class LazyMan {

    private LazyMan() {
        System.out.println("当前线程是"+Thread.currentThread().getName()+"懒汉开启~");
    }

    private static  volatile LazyMan lazyMan;

     // 双重检测锁模式 懒汉式单例   DCL懒汉
    public static LazyMan getInstance() {

        if (lazyMan == null) {
            synchronized (LazyMan.class){  // 加class锁
                if (lazyMan == null) {
                    lazyMan = new LazyMan();
                }
            }
        }
        return lazyMan;
    }

    //多线程并发
public static void main(String[] args) throws InterruptedException, NoSuchMethodException, IllegalAccessException, InvocationTargetException, InstantiationException {
      LazyMan instance = LazyMan.getInstance();
      Constructor<LazyMan> constructor = LazyMan.class.getDeclaredConstructor(null);
      constructor.setAccessible(true); // 无视私有权限 private
      LazyMan instance2 = constructor.newInstance();

      System.out.println(instance);   // com.jia.juc.single.LazyMan@8efb846
      System.out.println(instance2);   // com.jia.juc.single.LazyMan@2a84aee7
                                       // 单例被破坏了
  }
}

防止反射破坏,给私有构造器加锁,并抛出异常

// 懒汉式单例-- 用到时再加载对象
public class LazyMan {

    private LazyMan() {
        synchronized (LazyMan.class) {
            if (lazyMan != null) {
                throw new RuntimeException("不要试图使用反射破坏异常");
            }
        }
        //System.out.println("当前线程是" + Thread.currentThread().getName() + "懒汉开启~");
    }

    private static  volatile LazyMan lazyMan;

     // 双重检测锁模式 懒汉式单例   DCL懒汉
    public static LazyMan getInstance() {

        if (lazyMan == null) {
            synchronized (LazyMan.class){  // 加class锁
                if (lazyMan == null) {
                    lazyMan = new LazyMan();
                }
            }
        }
        return lazyMan;
    }

    //多线程并发
  public static void main(String[] args) throws InterruptedException, NoSuchMethodException, IllegalAccessException, InvocationTargetException, InstantiationException {
      LazyMan instance = LazyMan.getInstance();
      Constructor<LazyMan> constructor = LazyMan.class.getDeclaredConstructor(null);
      constructor.setAccessible(true); // 无视私有权限 private
      LazyMan instance2 = constructor.newInstance();

      System.out.println(instance);
      System.out.println(instance2);  // 两种方式结果抛出异常

  }

}

阻止破坏成功:

在这里插入图片描述

但是仍然可以通过如下方式破坏:(都通过反射的方式获取实例)

//多线程并发
  public static void main(String[] args) throws InterruptedException, NoSuchMethodException, IllegalAccessException, InvocationTargetException, InstantiationException {
//      LazyMan instance = LazyMan.getInstance();
      Constructor<LazyMan> constructor = LazyMan.class.getDeclaredConstructor(null);
      constructor.setAccessible(true); // 无视私有权限 private
      LazyMan instance2 = constructor.newInstance();
      LazyMan instance3 = constructor.newInstance();

      System.out.println(instance2);  // com.jia.juc.single.LazyMan@8efb846
      System.out.println(instance3);  // com.jia.juc.single.LazyMan@2a84aee7
                                       // 单例被破坏了
  }

再用别的方式防止破坏,设置一个别人不知道的变量:

// 懒汉式单例-- 用到时再加载对象
public class LazyMan {

    //定义一个别人不知道的变量
    private static boolean flag =false;
    private LazyMan() {
        synchronized (LazyMan.class) {
            if (flag == false) {
                flag = true;
            }else {
                throw new RuntimeException("不要试图使用反射破坏异常");
            }
        }
        //System.out.println("当前线程是" + Thread.currentThread().getName() + "懒汉开启~");
    }

    private static  volatile LazyMan lazyMan;

     // 双重检测锁模式 懒汉式单例   DCL懒汉
    public static LazyMan getInstance() {

        if (lazyMan == null) {
            synchronized (LazyMan.class){  // 加class锁
                if (lazyMan == null) {
                    lazyMan = new LazyMan();
                }
            }
        }
        return lazyMan;
    }

    //多线程并发
  public static void main(String[] args) throws InterruptedException, NoSuchMethodException, IllegalAccessException, InvocationTargetException, InstantiationException {
//      LazyMan instance = LazyMan.getInstance();
      Constructor<LazyMan> constructor = LazyMan.class.getDeclaredConstructor(null);
      constructor.setAccessible(true); // 无视私有权限 private
      LazyMan instance2 = constructor.newInstance();
      LazyMan instance3 = constructor.newInstance();

      System.out.println(instance2);
      System.out.println(instance3);

  }

}

阻止破坏成功

在这里插入图片描述

别人不知道的变量,总还是有牛人会知道定义的变量的,还是不安全

//多线程并发
public static void main(String[] args) throws InterruptedException, NoSuchMethodException, IllegalAccessException, InvocationTargetException, InstantiationException, NoSuchFieldException {

//      LazyMan instance = LazyMan.getInstance();
      Field flag = LazyMan.class.getDeclaredField("flag");// 反射获取变量
      flag.setAccessible(true); // 无视私有权限

      Constructor<LazyMan> constructor = LazyMan.class.getDeclaredConstructor(null);
      constructor.setAccessible(true); // 无视私有权限 private
      LazyMan instance2 = constructor.newInstance();

      flag.set(instance2,false);
      LazyMan instance3 = constructor.newInstance();

      System.out.println(instance2);  // com.jia.juc.single.LazyMan@2a84aee7
      System.out.println(instance3);  // com.jia.juc.single.LazyMan@a09ee92
                                       // 单例被破坏了
  }
}
3.静态内部类单例:
// 静态内部类--单例
public class Holder {

    private Holder(){
    }

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

    public static Holder getInstance(){
        return  InnerClass.HOLDER;
    }
}

也不安全,因为反射,略

4.枚举类:

使用枚举,我们就可以防止反射破坏单例了。

// 枚举 enum 是一个什么? 本身也是一个Class类
public enum EnumSingle {
    INSTANCE;

    public EnumSingle getInstance(){
        return INSTANCE;
    }
}

class Test{
public static void main(String[] args) throws NoSuchMethodException, IllegalAccessException, InvocationTargetException, InstantiationException {
        EnumSingle instance = EnumSingle.INSTANCE;

         //报错  java.lang.NoSuchMethodException: com.jia.juc.single.EnumSingle.<init>()   没有无参构造方法
        Constructor<EnumSingle> declaredConstructor = EnumSingle.class.getDeclaredConstructor(null);// 无参
        declaredConstructor.setAccessible(true);

        EnumSingle instance1 = declaredConstructor.newInstance();

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

    }
}

declaredConstructor.newInstance(); 枚举类有禁止反射异常

在这里插入图片描述

但是,反编译确实有 无参构造方法

在这里插入图片描述

使用jad工具反编译为java(反编译jad.exe 下载: )

反编译源码:

在这里插入图片描述

源码骗了我们,用了一个有参构造器

class Test{
    public static void main(String[] args) throws NoSuchMethodException, 
    IllegalAccessException, InvocationTargetException, InstantiationException {
        EnumSingle instance = EnumSingle.INSTANCE;
         // 报异常,禁止反射构造实例
        // xception in thread "main" java.lang.IllegalArgumentException: 
        // Cannot reflectively create enum objects
        //	at java.lang.reflect.Constructor.newInstance(Constructor.java:417)
        Constructor<EnumSingle> declaredConstructor = 
        EnumSingle.class.getDeclaredConstructor(String.class,int.class); // 有参
        declaredConstructor.setAccessible(true);

        EnumSingle instance1 = declaredConstructor.newInstance();

        System.out.println(instance);
        System.out.println(instance1);  
    }
}
19.深入理解CAS
1.什么是CAS?

进大厂必须要深入研究底层!(连环追问)— 修内功,操作系统,计算机网络原理

public class CASTest1 {

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

        //boolean compareAndSet(int expect, int update)
        //期望值、更新值
        //如果实际值 和 我的期望值相同,那么就更新
        //如果实际值 和 我的期望值不同,那么就不更新
        System.out.println(atomicInteger.compareAndSet(2021, 2022));
        System.out.println(atomicInteger.get());

        //因为期望值是2021  实际值却变成了2022  所以会修改失败
        //CAS 是CPU的并发原语
        atomicInteger.getAndIncrement(); // ++操作
        System.out.println(atomicInteger.compareAndSet(2021, 2022));
        System.out.println(atomicInteger.get());
    }
}
2.Unsafe 类

在这里插入图片描述

方法分析,csa

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

总结:CAS:比较当前工作内存中的值 和 主内存中的值,如果这个值是期望的,那么则执行操作!如果不是就一直循环,使用的是自旋锁。

缺点:

  • 循环会耗时;
  • 一次性只能保证一个共享变量的原子性;
  • 它会存在ABA问题

CAS:ABA问题?(狸猫换太子)
在这里插入图片描述
线程1:期望值是1,要变成2;

线程2:两个操作:

  • 1、期望值是1,变成3
  • 2、期望是3,变成1

所以对于线程1来说,A的值还是1,所以就出现了问题,骗过了线程1;

public class CASTest1 {
    //CAS : compareAndSet 比较并交换
    public static void main(String[] args) {
        AtomicInteger atomicInteger = new AtomicInteger(2021);
        //boolean compareAndSet(int expect, int update)
        //期望值、更新值
        //如果实际值 和 我的期望值相同,那么就更新
        //如果实际值 和 我的期望值不同,那么就不更新
        System.out.println(atomicInteger.compareAndSet(2021, 2022));
        System.out.println(atomicInteger.get());

        //  ============== 捣乱的线程 ==================
        System.out.println(atomicInteger.compareAndSet(2022, 2021));
        System.out.println(atomicInteger.get());

        //因为期望值是2021  实际值却变成了2022  所以会修改失败
        //CAS 是CPU的并发原语
        //  ============== 期望的的线程 ==================
        System.out.println(atomicInteger.compareAndSet(2021, 8888));
        System.out.println(atomicInteger.get());        
    }
}
20.原子引用

解决ABA 问题:引入原子引用,对应的思想:乐观锁

带版本号 的原子操作!

public class CASTest2 {

    /**AtomicStampedReference 注意,如果泛型是一个包装类,注意对象的引用问题
     * 正常在业务操作,这里面比较的都是一个个对象
     */
   static AtomicStampedReference<Integer> atomicStampedReference = new AtomicStampedReference<>(1,20211207);
    //两个参数  第一个是数据  第二个是版本号

    // CAS compareAndSet : 比较并交换!
    public static void main(String[] args) {
       new Thread(()->{
           int stamp = atomicStampedReference.getStamp(); // 获得版本号
           System.out.println("第一次获取版本号"+stamp+"\t值是"+atomicStampedReference.getReference());

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

           // 修改操作时,版本号更新 + 1
           atomicStampedReference.compareAndSet(1,2,
                   atomicStampedReference.getStamp(),atomicStampedReference.getStamp()+1);

           System.out.println("第二次获取版本号"+atomicStampedReference.getStamp()+"\t值是"+atomicStampedReference.getReference());

       },"线程A").start();

        new Thread(()-> {
            int stamp = atomicStampedReference.getStamp(); // 获得版本号
            System.out.println("第三次获取版本号" + stamp+"\t值是"+atomicStampedReference.getReference());

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

            System.out.println(atomicStampedReference.compareAndSet(1, 3,
                    stamp, stamp + 1));

            System.out.println("第四次获取版本号" + stamp+"\t值是"+atomicStampedReference.getReference());

        },"线程B").start();
    }
}
//        第一次获取版本号20211207	值是1
//        第三次获取版本号20211207	值是1
//        第二次获取版本号20211208	值是2
//        false     中间修改了,这里通过版本号已经知道了
//        第四次获取版本号20211207	值是2

注意:

Integer 使用了对象缓存机制,默认范围是 -128 ~ 127 ,推荐使用静态工厂方法 valueOf 获取对象实例,而不是 new,因为 valueOf 使用缓存,而 new 一定会创建新的对象分配新的内存空间;

在这里插入图片描述

21.对各种锁的理解
1.公平锁、非公平锁
  1. 公平锁:非常公平,不能插队,必须先来后到

    /**
     * Creates an instance of {@code ReentrantLock}.
     * This is equivalent to using {@code ReentrantLock(false)}.
     */
    public ReentrantLock() {
        sync = new NonfairSync();
    }
    

    2.非公平锁:非常不公平,允许插队,可以改变顺序

    /**
     * Creates an instance of {@code ReentrantLock} with the
     * given fairness policy.
     *
     * @param fair {@code true} if this lock should use a fair ordering policy
     */
    public ReentrantLock(boolean fair) {
        sync = fair ? new FairSync() : new NonfairSync();
    }
    
    2.可重入锁

    拿到外边的锁,就可以拿到里面的锁,自动获得。

    Synchonized 可重入锁

    public class Test1 {
        //  Synchonized 可重入锁
        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() + "==> 发短信");
            call();  // 还有一把锁
        }
    
        public synchronized void call(){
            System.out.println(Thread.currentThread().getName() + "==> 打电话");
        }
    }
    //A==> 发短信
    //A==> 打电话
    //B==> 发短信
    //B==> 打电话
    

    Lock 可重入锁

    public class Test2 {
    
        //  Synchonized 可重入锁
        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 synchronized void sms(){
            lock.lock();
            try {
                System.out.println(Thread.currentThread().getName() + "==> 发短信");
                call();  // 还有一把锁
            } catch (Exception e) {
                e.printStackTrace();
            } finally {
                lock.unlock();
            }
        }
    
        public synchronized void call(){
    
            lock.lock();
            try {
                System.out.println(Thread.currentThread().getName() + "==> 打电话");
            } catch (Exception e) {
                e.printStackTrace();
            } finally {
                lock.unlock();
            }
        }
    }
    //A==> 发短信
    //A==> 打电话
    //B==> 发短信
    //B==> 打电话
    
    • lock锁必须配对,相当于lock和 unlock 必须数量相同;
    • 在外面加的锁,也可以在里面解锁;在里面加的锁,在外面也可以解锁;
    3.自旋锁

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

    spinlock (Unsafe 类)

    // 自旋锁
    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 SpinlockDemo {
    
        //原子操作:泛型为 int  默认 0
        //原子操作:泛型为thread  默认 null
        AtomicReference<Thread>  atomicReference = new AtomicReference<>();
    
        //加锁
        public void  myLock(){
    
            Thread thread = Thread.currentThread();
            System.out.println(thread.getName()+"\t myLock ");
            // 自旋锁
            while (!atomicReference.compareAndSet(null,thread));
            System.out.println(Thread.currentThread().getName() + "\t ==> 自旋中......");
        }
    
        // 解锁
        public void myUnlock(){
            Thread thread = Thread.currentThread();
            System.out.println(thread.getName()+"\t unLock ");
            atomicReference.compareAndSet(thread,null);
        }
    }
    
    class Test{
        public static void main(String[] args) throws InterruptedException {
    //        Lock lock = new ReentrantLock();
    //        lock.lock();
    //        lock.unlock();
    
            //使用CAS实现自旋锁
            SpinlockDemo spinlockDemo = new SpinlockDemo();
    
            new Thread(()->{
                try {
                    spinlockDemo.myLock();
    
                    TimeUnit.SECONDS.sleep(3);
                } catch (Exception e) {
                    e.printStackTrace();
                } finally {
                    spinlockDemo.myUnlock();
                }
    
            },"A").start();
    
            TimeUnit.SECONDS.sleep(2);
            new Thread(()->{
                try {
                    spinlockDemo.myLock();
    
                    TimeUnit.SECONDS.sleep(3);
                } catch (Exception e) {
                    e.printStackTrace();
                } finally {
                    spinlockDemo.myUnlock();
               }
            },"B").start();
        }
    }
    //A	 myLock 
    //A	 ==> 自旋中......
    //B	 myLock 
    //A	 unLock 
    //B	 ==> 自旋中......
    //B	 unLock 
    

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

    4.死锁

在这里插入图片描述

重点在排查死锁,如何解开死锁

public class DeadLock {
    public static void main(String[] args) {
        String lockA = "lockA";
        String lockB = "lockB";

        new Thread(new MyThread(lockA,lockB),"线程1").start();
        new Thread(new MyThread(lockB,lockA),"线程2").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()+lockA+ "===>获取"+lockB);

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

            synchronized (lockB){
                System.out.println(Thread.currentThread().getName()+lockB+ "===>获取"+lockA);
            }
        }
    }
}

如何解开死锁

1、使用jps定位进程号,jdk的bin目录下: 有一个jps

命令:jps -l

在这里插入图片描述

2、使用jstack 进程进程号 找到死锁信息
在这里插入图片描述

一般情况信息在最后:

在这里插入图片描述
5.悲观锁、乐观锁 略

6.读写锁 略

  • 4
    点赞
  • 8
    收藏
    觉得还不错? 一键收藏
  • 1
    评论
评论 1
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值