狂神说学习笔记:JUC并发编程

1|0JUC并发编程

1|11、什么是JUC

JUC:java.util.concurrent

在这里插入图片描述

Runnable 没有返回值、效率相比入 Callable 相对较低!

在这里插入图片描述

在这里插入图片描述

1|22、线程和进程

进程:一个程序,QQ.exe,music.exe 本质是程序的集合

一个进程往往可以包含多个线程,至少包含一个!

Java默认有几个线程? 2个,main 和 GC

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

开启线程,对于Java而言:Thread、Runnable、Callable

Java 真的可以开启线程吗? 开不了

public synchronized void start() { if (threadStatus != 0) throw new IllegalThreadStateException(); group.add(this); boolean started = false; try { start0(); started = true; } finally { try { if (!started) { group.threadStartFailed(this); } } catch (Throwable ignore) { } } } // 本地方法, 底层的C++, Java无法直接操作硬件 private native void start0();

1|02.1、并发、并行

并发编程:并发、并行

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

  • CPU一核,模拟出来多条线程,天下武功,为快不破,快速交替

并行(多个人一起行走)

  • CPU多核,多个线程可以同时执行;线程池
public class Test1 { public static void main(String[] args) { // 获取CPU核数 // CPU 密集型,IO密集型 System.out.println(Runtime.getRuntime().availableProcessors()); } }

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

1|02.2、线程有几个状态

public enum State { // 新生 NEW, // 运行 RUNNABLE, // 堵塞 BLOCKED, // 等待,死死地等 WAITING, // 超时等待 TIMED_WAITING, // 终止 TERMINATED; }

1|02.3、wait/sleep 区别

1、来自不同的类

  • wait = > Object
  • sleep => Thread

2、关于锁的释放

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

3、使用的范围是不同的

  • wait 必须在同步代码块中
  • sleep 可以在任何敌方睡

4、是否需要捕获异常(忽略)

  • wait 必须需要捕获异常
  • sleep 必须要捕获异常

1|33、Lock锁(重点)

1|03.1、传统Synchronized

// 基本的卖票例子 /** * 真正的多线程的开发,公司中的开发 * 线程就是一个单独的资源类,没有任何附属的操作 * 属性,方法 */ public class SaleTicketDemo01 { 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 number = 50; // 卖票的方式 // synchronized 本质:队列,锁 public synchronized void sale() { if (number > 0) { System.out.println(Thread.currentThread().getName() + "卖出了第" + number + "票,剩余:" + (50-number)); number--; } } }

1|03.2、Lock 接口

在这里插入图片描述

在这里插入图片描述

在这里插入图片描述

公平锁:十分公平:可以先来后到

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

// 基本的卖票例子 /** * 真正的多线程的开发,公司中的开发 * 线程就是一个单独的资源类,没有任何附属的操作 * 属性,方法 */ public class SaleTicketDemo02 { 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(); } } // Lock三部曲 // 1、 new ReentrantLock(); // 2、 lock.lock(); // 加锁 // 3、 finally=> lock.unlock(); // 解锁 class Ticket2 { // 属性,方法 private int number = 50; Lock lock = new ReentrantLock(); // 卖票的方式 public void sale() { lock.lock(); // 加锁 try { // 业务代码 if (number > 0) { System.out.println(Thread.currentThread().getName() + "卖出了第" + number + "票,剩余:" + (50-number)); number--; } } finally { lock.unlock(); // 解锁 } } }

1|03.3、Synchronized 和 Lock 区别

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

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

3、Synchronized 会自动释放锁,Lick 必须要手动释放锁!如果不释放就会 死锁

4、Synchronized 线程1(获得锁,阻塞)、线程2(等待,傻傻的等);Lock 锁就不一定会等待下去

5、Synchronized 可重入锁,不可以中段的,非公平;Lock,可重入锁,可以判断锁,非公平(可以自己设置)

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

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

1|44、生产者和消费者问题

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

1|04.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 number = 0; // +1 public synchronized void increment() throws InterruptedException { if (number != 0) { // 等待 this.wait(); } number++; System.out.println(Thread.currentThread().getName() + "=>" + number); // 通知线程其他线程,我+1完毕了 this.notifyAll(); } // +1 public synchronized void decrement() throws InterruptedException { if (number == 0) { // 等待 this.wait(); } number--; System.out.println(Thread.currentThread().getName() + "=>" + number); // 通知线程其他线程,我-1完毕了 this.notifyAll(); } }

1|04.1.1、问题存在,A B C D 4 个线程! 虚假唤醒

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

结果:

image-20220801102551915

在这里插入图片描述

解决办法:if 改为 while 判断(防止虚假唤醒问题)

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(); 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 Data {// 数字 资源类 private int number = 0; // +1 public synchronized void increment() throws InterruptedException { while (number != 0) { // 等待 this.wait(); } number++; System.out.println(Thread.currentThread().getName() + "=>" + number); // 通知线程其他线程,我+1完毕了 this.notifyAll(); } // +1 public synchronized void decrement() throws InterruptedException { while (number == 0) { // 等待 this.wait(); } number--; System.out.println(Thread.currentThread().getName() + "=>" + number); // 通知线程其他线程,我-1完毕了 this.notifyAll(); } }

结果正常:

image-20220801103142391

1|04.2、JUC版的生产者和消费者问题

在这里插入图片描述

通过Lock 找到 Condition

image-20220801103820815

在这里插入图片描述

代码实现:

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 number = 0; Lock lock = new ReentrantLock(); Condition condition = lock.newCondition(); // condition.await(); // 等待 // condition.signalAll(); // 唤醒全部 // +1 public void increment() throws InterruptedException { lock.lock(); try { while (number != 0) { // 等待 condition.await(); } number++; System.out.println(Thread.currentThread().getName() + "=>" + number); // 通知线程其他线程,我+1完毕了 condition.signalAll(); } catch (Exception e) { e.printStackTrace(); } finally { lock.unlock(); } } // +1 public void decrement() throws InterruptedException { lock.lock(); try { while (number == 0) { // 等待 condition.await(); } number--; System.out.println(Thread.currentThread().getName() + "=>" + number); // 通知线程其他线程,我-1完毕了 condition.signalAll(); } catch (Exception e) { e.printStackTrace(); } finally { lock.unlock(); } } }

问题:随机的状态,我希望能有序执行ABCD

image-20220801110222416

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

1|04.3、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(); } } // 资源类 Lock class Data3 { private Lock lock = new ReentrantLock(); private Condition condition1 = lock.newCondition(); private Condition condition2 = lock.newCondition(); private Condition condition3 = lock.newCondition(); private int number = 1; // 1A 2B 3C public void printA() { lock.lock(); try { // 业务,判断-> 执行-> 通知 while (number != 1) { // 等待 condition1.await(); } System.out.println(Thread.currentThread().getName() + "=>AAA"); // 唤醒,唤醒指定的人,B number = 2; condition2.signal(); } catch (Exception e) { e.printStackTrace(); } finally { lock.unlock(); } } public void printB() { lock.lock(); try { // 业务,判断-> 执行-> 通知 while (number != 2) { // 等待 condition2.await(); } System.out.println(Thread.currentThread().getName() + "=>BBB"); // 唤醒,唤醒指定的人,B number = 3; condition3.signal(); } catch (Exception e) { e.printStackTrace(); } finally { lock.unlock(); } } public void printC() { lock.lock(); try { // 业务,判断-> 执行-> 通知 while (number != 3) { // 等待 condition3.await(); } System.out.println(Thread.currentThread().getName() + "=>CCC"); // 唤醒,唤醒指定的人,B number = 1; condition1.signal(); } catch (Exception e) { e.printStackTrace(); } finally { lock.unlock(); } } }

结果:

image-20220801123443094

1|55、8锁现象

如何判断锁的是谁!永远的知道什么锁,锁到底锁的是谁!

8锁,就是关于锁的8个问题

  • 1、标准情况下,两个线程先打印 发短信 还是 打电话? 1/发短信 2/打电话
  • 2、sendSms延迟4秒,两个线程先打印 发短信 还是 打电话? 1/发短信 2/打电话
/** * 8锁,就是关于锁的8个问题 * 1、标准情况下,两个线程先打印 发短信 还是 打电话? 1/发短信 2/打电话 * 2、sendSms延迟4秒,两个线程先打印 发短信 还是 打电话? 1/发短信 2/打电话 */ 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 (InterruptedException e) { e.printStackTrace(); } new Thread(() -> { phone.call(); }, "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("打电话"); } }
  • 3、增加了一个普通方法!先执行 发短信 还是 hello? 1/hello(普通方法) 2/发短信
  • 4、两个对象、两个同步方法,先执行 发短信 还是 打电话? 1/打电话 2/发短信
/** * 3、增加了一个普通方法!先执行 发短信 还是 hello? 1/hello(普通方法) 2/发短信 * 4、两个对象、两个同步方法,先执行 发短信 还是 打电话? 1/打电话 2/发短信 */ public class Test2 { 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.call(); }, "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 call() { System.out.println("打电话"); } // 这里没有锁!不是同步方法,不受锁的影响 public void hello() { System.out.println("Hello"); } }
  • 5、增加两个静态的同步方法,只有一个对象,先打印 发短信 还是 打电话? 1/发短信 2/打电话 (锁的是Class)
  • 6、两个对象!增加两个静态的同步方法,只有一个对象,先打印 发短信 还是 打电话? 1/发短信 2/打电话 (锁的还是Class)
/** * 5、增加两个静态的同步方法,只有一个对象,先打印 发短信 还是 打电话? 1/发短信 2/打电话 (锁的是Class) * 6、两个对象!增加两个静态的同步方法,只有一个对象,先打印 发短信 还是 打电话? 1/发短信 2/打电话 (锁的还是Class) */ public class Test3 { public static void main(String[] args) { // 两个对象的class类模板只有一个,static Phone3 phone1 = new Phone3(); Phone3 phone2 = new Phone3(); // 有锁的存在 new Thread(() -> { phone1.sendSms(); }, "A").start(); // 捕获 try { TimeUnit.SECONDS.sleep(1); } catch (InterruptedException e) { e.printStackTrace(); } new Thread(() -> { phone2.call(); }, "B").start(); } } // Phone3唯一的一个 class 对象 class Phone3 { // 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 call() { System.out.println("打电话"); } }
  • 7、一个静态同步方法,一个普通同步方法,只有一个对象,先打印 发短信 还是 打电话? 1/打电话 2/发短信
  • 8、一个静态同步方法,一个普通同步方法,两个对象,先打印 发短信 还是 打电话? 1/打电话 2/发短信
/** * 7、一个静态同步方法,一个普通同步方法,只有一个对象,先打印 发短信 还是 打电话? 1/打电话 2/发短信 * 8、一个静态同步方法,一个普通同步方法,两个对象,先打印 发短信 还是 打电话? 1/打电话 2/发短信 */ public class Test4 { public static void main(String[] args) { // 两个对象的class类模板只有一个,static 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.call(); }, "B").start(); } } // Phone3唯一的一个 class 对象 class Phone4 { // 静态同步方法 锁的是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("打电话"); } }

小结:

new this 具体的一个手机

static Class 唯一的模板

1|66、集合类不安全

image-20220802183308432

1|06.1、List 不安全

public class ListTest { public static void main(String[] args) { List<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(); } } }

运行有概率会报错:

image-20220802163552901

java.util.ConcurrentModificationException:并发修改异常

解决方案:

  1. 使用 Vector 类,因为 Vector 的 add 方法带有synchronized 关键字,线程安全(Vector为JDK1.0出的,ArrayList为JDK1.2出的)

    image-20220802165747966

  2. 使用 Collections.synchronizedList 包装 ArrayList类

    image-20220802171254308

    SynchronizedRandomAccessList 类继承的是 SynchronizedList 类,而 SynchronizedList 类可以看到所有的操作都是上了锁的,synchronized (mutex),锁对象是mutex是来自SynchronizedCollection父类

    image-20220802171818778

  3. 使用 CopyOnWriteArrayList 类,它的 add 方法 使用的是 Lock 的 ReentrantLock锁(可重入锁)

    image-20220802173935085

// java.util.ConcurrentModificationException 并发修改异常! public class ListTest { public static void main(String[] args) { // 并发下 ArrayList 不安全的 /** * 解决方案: * 1、List<String> list = new Vector<>(); // Vector是jdk1.0出的,ArrayList是jdk1.2出的 * 2、List<String> list = Collections.synchronizedList(new ArrayList<>()); * 3、List<String> list = new CopyOnWriteArrayList<>(); */ // CopyOnWrite 写入时复制 COW 计算机程序设计领域的一种优化策略 // 多个线程调用的时候,list,读取的时候,固定的,写入(覆盖) // 在写入的时候避免覆盖,造成数据问题! // 读写分离 // CopyOnWriteArrayList 比 Vector nb 在哪里? 使用 synchronized 相对 lock 效率低 List<String> list = new CopyOnWriteArrayList<>(); 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(); } } }

1|06.2、Set 不安全

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

运行有概率会报错:

image-20220802175901475

java.util.ConcurrentModificationException 并发修改异常

解决方案:

  1. 使用 Collections.synchronizedSet 包装 HashSet

    image-20220802180443054

    SynchronizedSet 类可以看到继承 SynchronizedCollection 父类

    image-20220802180912439

    而父类所有操作都上了锁

    image-20220802181107479

  2. 使用 CopyOnWriteArraySet 类,底层和 CopyOnWriteArrayList 类相似,都用了 Lock 的 ReentrantLock锁(可重入锁)

    image-20220802181504132

    image-20220802181612522

    image-20220802181706313

/** * 同理可证 java.util.ConcurrentModificationException * 1、Set<String> set = Collections.synchronizedSet(new HashSet<>()); * 2、Set<String> set = new CopyOnWriteArraySet<>(); */ public class SetTest { public static void main(String[] args) { Set<String> set = new CopyOnWriteArraySet<>(); for (int i = 1; i <= 10; i++) { new Thread(() -> { set.add(UUID.randomUUID().toString().substring(0, 5)); System.out.println(set); }, String.valueOf(i)).start(); } } }

1|06.2.1、HashSet底层是什么?

本质:

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

add 方法的本质:

// set 本质就是 map的key,key是无法重复的 public boolean add(E e) { return map.put(e, PRESENT)==null; } // 与支持 Map 中的对象关联的虚拟值 private static final Object PRESENT = new Object(); // 不变的值

1|06.3、Map不安全

回顾Map的基本操作

image-20220802184735519

// java.util.ConcurrentModificationException public class MapTest { public static void main(String[] args) { // map 是这样用的吗? 不是,工作中不用 HashMap // 默认等价于什么? new HashMap<>(16, 0.75); Map<String, String> map = new HashMap<>(); // 加载因子(默认0.75)、初始化容量(默认16) for (int i = 1; i <= 30; i++) { new Thread(() -> { map.put(Thread.currentThread().getName(), UUID.randomUUID().toString().substring(0, 5)); System.out.println(map); }, String.valueOf(i)).start(); } } }

运行报错:

image-20220802185502839

java.util.ConcurrentModificationException 并发修改异常

解决方案:

  1. 使用 Collections.synchronizedMap 包装 HashMap

    image-20220802185850692

    可以看到 SynchronizedMap 所有操作都上了锁

    image-20220802190119622

  2. 使用 ConcurrentHashMap 类

    put() 调用了 putVal()

    image-20220802193336673

    而 putVal() 中使用了 synchronized 同步块

    image-20220802193930682
/** * java.util.ConcurrentModificationException * 1、Map<String, String> map = Collections.synchronizedMap(new HashMap<>()); * 2、Map<String, String> map = new ConcurrentHashMap<>(); */ public class MapTest { public static void main(String[] args) { // map 是这样用的吗? 不是,工作中不用 HashMap // 默认等价于什么? new HashMap<>(16, 0.75); Map<String, String> map = new ConcurrentHashMap<>(); // 加载因子(默认0.75)、初始化容量(默认16) for (int i = 1; i <= 30; i++) { new Thread(() -> { map.put(Thread.currentThread().getName(), UUID.randomUUID().toString().substring(0, 5)); System.out.println(map); }, String.valueOf(i)).start(); } } }

1|77、Callable(简单)

image-20220802212102870

Callable 相较于 Runnable 的区别

1、Callable 可以有返回值

2、Callable 可以抛出异常

3、方法不同,Runnable是run() Callable是call()

论 Thread 如何调用 Callable

image-20220802214707575

image-20220802214111628

image-20220802214606069

代码测试

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 thread = new MyThread(); // 适配类 FutureTask<Integer> futureTask = new FutureTask<>(thread); new Thread(futureTask, "A").start(); new Thread(futureTask, "B").start(); // 不执行(具体实现查看源码) // 获取 Callable 的返回结果 Integer integer = futureTask.get(); // 这个 get 方法可能会产生堵塞,把它放到最后 // 或者使用异步通信来处理! System.out.println(integer); } } class MyThread implements Callable<Integer> { @Override public Integer call() throws Exception { System.out.println("call()"); // 会打印几个call // 耗时的操作 return 1024; } }

运行结果:

image-20220802215526086

细节:

1、会保存数据在 outcome 中,第二次会判断 status 然后直接 return,只执行一次 FutureTask 的 run 方法

image-20220802222017851

2、结果可能需要等待,会阻塞!

1|88、常用的辅助类(必会)

1|08.1、CountDownLatch

image-20220802222733154

代码演示

// 计数器 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"); } }

结果:

image-20220802223936582

原理:

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

countDownLatch.await(); // 等待计数器归零,然后再向下执行

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

1|08.2、CyclicBarrier

image-20220803123209520

加法计数器

代码演示

public class CyclicBarrierDemo { public static void main(String[] args) { /** * 集齐7颗龙珠召唤神龙 */ // 召唤龙珠的线程 CyclicBarrier cyclicBarrier = new CyclicBarrier(7, () -> { System.out.println("召唤神龙成功"); }); for (int i = 1; i <= 7; 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(); } } }

结果:

image-20220803123934471

1|08.3、Semaphore

Semaphore:信号量

image-20220803125127179

代码演示

抢车位!

6车—3个停车位置

public class SemaphoreDemo { public static void main(String[] args) { // 构造参数 默认线程数量:类似停车位 用来限流 Semaphore semaphore = new Semaphore(3); for (int i = 1; i <= 6; i++) { new Thread(() -> { // acquire() 得到 // release() 释放 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(); } } }

运行结果:线程释放完后其他线程才能进入

image-20220803132250327

原理:

semaphore.acquire() 获得,假设如果已经满了,等待,等待被释放为止!

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

作用: 多个共享资源互斥的使用!并发限流,控制最大的线程数!

1|99、读写锁

ReadWriteLock

image-20220804105446830

未加读写锁之前

/** * ReadWriteLock */ public class ReadWriteLockDemo { public static void main(String[] args) { MyCache myCache = new MyCache(); // 写入 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 MyCache { // volatile 保证内存可见性 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() + "读取完毕"); } }

运行结果:发现写入的时候被插队了

image-20220804105415970

加上读写锁后

/** * 独占锁、排它锁(写锁):一次只能被一个线程占有 * 共享锁(读锁):多个线程可以同时占有 * ReadWriteLock * 读-读 可以共存! * 读-写 不能共存! * 写-写 不能共存! */ public class ReadWriteLockDemo { public static void main(String[] args) { // MyCache myCache = new MyCache(); MyCacheLock myCacheLock = new MyCacheLock(); // 写入 for (int i = 1; i <= 5 ; i++ ) { final int temp = i; new Thread(() -> { myCacheLock.put(temp + "", temp + ""); }, String.valueOf(i)).start(); } // 读取 for (int i = 1; i <= 5 ; i++ ) { final int temp = i; new Thread(() -> { myCacheLock.get(temp + ""); }, String.valueOf(i)).start(); } } } // 加锁的 class MyCacheLock { // volatile 保证内存可见性 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(); } } }

结果:不会存在插队

image-20220804110938681

要点:

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

1|1010、阻塞队列

image-20220804153133919

阻塞队列:

image-20220804153915919

image-20220804160231645

1|010.1、BlockingQueue

BlockingQueue 不是新的东西

image-20220804160335645

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

学会使用队列

添加、移除

四组API

方式 抛出异常 有返回值,不抛出异常 阻塞 等待 超时等待
添加 add offer() put() offer(,)
移除 remove poll() take() poll(,)
检测队首元素 element peek - -

代码演示

/** * 抛出异常 */ public static void test1() { // 队列的大小 ArrayBlockingQueue blockingQueue = new ArrayBlockingQueue<>(3); System.out.println(blockingQueue.add("a")); System.out.println(blockingQueue.add("b")); System.out.println(blockingQueue.add("c")); // java.lang.IllegalStateException: Queue full 抛出异常 // System.out.println(blockingQueue.add("d")); System.out.println("===================="); System.out.println(blockingQueue.remove()); System.out.println(blockingQueue.remove()); System.out.println(blockingQueue.remove()); // java.util.NoSuchElementException 抛出异常 // System.out.println(blockingQueue.remove()); }
/** * 有返回值,不抛出异常 */ public static void test2() { // 队列的大小 ArrayBlockingQueue<Object> blockingQueue = new ArrayBlockingQueue<>(3); System.out.println(blockingQueue.offer("a")); System.out.println(blockingQueue.offer("b")); System.out.println(blockingQueue.offer("c")); System.out.println(blockingQueue.offer("d")); // false 不抛出异常 System.out.println("========================="); System.out.println(blockingQueue.poll()); System.out.println(blockingQueue.poll()); System.out.println(blockingQueue.poll()); System.out.println(blockingQueue.poll()); // null 不出抛出异常 }
/** * 等待,阻塞(一直阻塞) */ public static void test3() throws InterruptedException { ArrayBlockingQueue<Object> blockingQueue = new ArrayBlockingQueue<>(3); // 一直阻塞 blockingQueue.put("a"); blockingQueue.put("b"); blockingQueue.put("c"); // blockingQueue.put("d"); // 队列没有位置了,一直等待 System.out.println(blockingQueue.take()); System.out.println(blockingQueue.take()); System.out.println(blockingQueue.take()); // System.out.println(blockingQueue.take()); // 没有这个元素,一直等待 }
/** * 等待,阻塞(等待超时) */ public static void test4() throws InterruptedException { ArrayBlockingQueue<Object> blockingQueue = new ArrayBlockingQueue<>(3); System.out.println(blockingQueue.offer("a")); System.out.println(blockingQueue.offer("b")); System.out.println(blockingQueue.offer("c")); System.out.println(blockingQueue.offer("d", 2, TimeUnit.SECONDS)); // 等待超过2秒就退出 System.out.println("=================="); System.out.println(blockingQueue.poll()); System.out.println(blockingQueue.poll()); System.out.println(blockingQueue.poll()); System.out.println(blockingQueue.poll(2, TimeUnit.SECONDS)); // 等待超过2秒就退出 }

1|010.2、SynchronousQueue 同步队列

没有容量,

进去一个元素,必须等待取出来之后,才能再往里面放一个元素!

put:放入 take:取出

代码演示

/** * 同步队列 * 和其他的 BlockingQueue 不一样,SynchronousQueue 不存储元素 * put了一个元素,必须从里面先take取出来,否则不能再put进去值! */ public class SynchronousQueueDemo { public static void main(String[] args) { SynchronousQueue<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() + " => " + synchronousQueue.take()); TimeUnit.SECONDS.sleep(3); System.out.println(Thread.currentThread().getName() + " => " + synchronousQueue.take()); TimeUnit.SECONDS.sleep(3); System.out.println(Thread.currentThread().getName() + " => " + synchronousQueue.take()); } catch (InterruptedException e) { e.printStackTrace(); } }, "T2").start(); } }

运行结果:

image-20220805111920627

1|1111、线程池(重点)

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

1|011.1、池化技术

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

线程池,连接池,内存池,对象池(创建和销毁,十分浪费资源)

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

线程池的好处:

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

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

1|011.2、线程池:三大方法

以下为阿里巴巴规范手册中:

image-20220805113719826

代码演示

// Executors 工具类、三大方法 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(); } } }

Executors.newSingleThreadExecutor()单个线程运行:

image-20220805115910916

Executors.newFixedThreadPool(5)固定线程池大小运行:

image-20220805115946490

Executors.newCachedThreadPool()可伸缩大小运行:

image-20220805120114913

1|011.3、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>()); } // 本质:ThreadPoolExecutor() /** * @param corePoolSize // 核心线程池大小 * @param maximumPoolSize // 最大线程池大小 * @param keepAliveTime // 空闲线程存活时间 * @param unit // 存活时间单位 * @param workQueue // 阻塞队列 * @param threadFactory // 线程工厂:创建线程的,一般不用动 * @param handler // 拒绝策略 */ public ThreadPoolExecutor(int corePoolSize, int maximumPoolSize, long keepAliveTime, TimeUnit unit, BlockingQueue<Runnable> workQueue, ThreadFactory threadFactory, RejectedExecutionHandler handler) { if (corePoolSize < 0 || maximumPoolSize <= 0 || maximumPoolSize < corePoolSize || keepAliveTime < 0) throw new IllegalArgumentException(); if (workQueue == null || threadFactory == null || handler == null) throw new NullPointerException(); this.acc = System.getSecurityManager() == null ? null : AccessController.getContext(); this.corePoolSize = corePoolSize; this.maximumPoolSize = maximumPoolSize; this.workQueue = workQueue; this.keepAliveTime = unit.toNanos(keepAliveTime); this.threadFactory = threadFactory; this.handler = handler; }

举个例子理解:

在这里插入图片描述

手动创建一个线程池

public class Demo02 { public static void main(String[] args) { // 自定义线程池 ExecutorService threadPool = new ThreadPoolExecutor( 2, 5, 3, TimeUnit.SECONDS, new LinkedBlockingQueue<>(3), Executors.defaultThreadFactory(), new ThreadPoolExecutor.AbortPolicy() // 银行满了,还有人进来,不处理这个人的,抛出异常 ); try { // 最大承载是:maximumPoolSize + 阻塞队列的 capacity // 超过最大承载会抛出 java.util.concurrent.RejectedExecutionException 异常 for (int i = 1; i <= 9; i++) { // 使用线程池之后,使用线程池来创建线程 threadPool.execute(() -> { System.out.println(Thread.currentThread().getName() + " ok"); }); } } catch (Exception e) { e.printStackTrace(); } finally { // 线程池用完,程序结束,关闭线程池 threadPool.shutdown(); } }

1|011.4、4种拒绝策略

image-20220807145211148

/** * new ThreadPoolExecutor.AbortPolicy() // 银行满了,还有人进来,不处理这个人的,抛出异常 * new ThreadPoolExecutor.CallerRunsPolicy() // 哪来的去哪里!由调用线程处理 * new ThreadPoolExecutor.DiscardPolicy() // 队列满了,丢掉任务,不会抛出异常! * new ThreadPoolExecutor.DiscardOldestPolicy() // 队列满了,尝试去和最早的竞争,也不会抛出异常! */

1|011.5、小结及IO密集型和CPU密集型拓展

池的最大的大小如何去设置!

了解:IO密集型,CPU密集型:(调优)

public class Demo02 { public static void main(String[] args) { // 自定义线程池 // 最大线程池如何定义 // 1、CPU 密集型 几核,就是几 可以保证CPU效率最高 12条线程同时执行 // 2、IO 密集型 > 判断你程序中十分耗IO的线程 // 程序 15个大型任务 io十分占用资源! System.out.println(Runtime.getRuntime().availableProcessors()); ExecutorService threadPool = new ThreadPoolExecutor( 2, 5, 3, TimeUnit.SECONDS, new LinkedBlockingQueue<>(3), Executors.defaultThreadFactory(), new ThreadPoolExecutor.AbortPolicy() // 银行满了,还有人进来,不处理这个人的,抛出异常 ); try { // 最大承载是:maximumPoolSize + 阻塞队列的 capacity // 超过最大承载会抛出 java.util.concurrent.RejectedExecutionException 异常 for (int i = 1; i <= 9; i++) { // 使用线程池之后,使用线程池来创建线程 threadPool.execute(() -> { System.out.println(Thread.currentThread().getName() + " ok"); }); } } catch (Exception e) { e.printStackTrace(); } finally { // 线程池用完,程序结束,关闭线程池 threadPool.shutdown(); } } }

1|1212、四大函数式接口(必需掌握)

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

1|012.1、函数式接口:

只有一个方法的接口

@FunctionalInterface public interface Runnable { public abstract void run(); } // 泛型、枚举、反射 // lambda表达式、链式编程、函数式接口、Stream流式计算 // 超级多FunctionalInterface // 简化编程模型,在新版本的框架底层大量应用! // foreach(消费者类型的函数式接口)

在这里插入图片描述

代码测试:

Function函数式接口

image-20221006122549052

/** * function 函数型接口,有一个输入参数,有一个返回类型 * 只要是 函数型接口 可以 用 lambda表达式简化 */ public class Demo01 { public static void main(String[] args) { // 工具类,输出输入的值 // Function function = new Function<String, String>() { // @Override // public String apply(String str) { // return str; // } // }; // Function<String, String> function = (Function<String, String>) str -> { // return str; // }; // Function<String, String> function = str -> { // return str; // } ; Function<String, String> function = str -> str; System.out.println(function.apply("abc")); } }

1|012.2、断定型接口:

有一个输入参数,返回值只能是 布尔值!

image-20221006124804533

/** * 断定型接口:有一个输入参数,返回值只能时 布尔值! */ public class Demo02 { 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 -> str.isEmpty(); System.out.println(predicate.test("")); } }

1|012.3、Consumer 消费型接口

只有输入,没有返回值

image-20221006125852298

/** * Consumer 消费型接口: 只有输入,没有返回值 */ public class Demo03 { public static void main(String[] args) { // Consumer<String> consumer = new Consumer<String>() { // @Override // public void accept(String str) { // System.out.println(str); // } // }; Consumer<String> consumer = str -> System.out.println(str); consumer.accept("abx"); } }

1|012.4、Supplier 供给型接口

没有参数,只有返回值

image-20221006130603544

/** * Supplier 供给型接口 没有参数,只有返回值 */ public class Demo04 { public static void main(String[] args) { // Supplier<String> supplier = new Supplier<String>() { // @Override // public String get() { // return "1024"; // } // }; Supplier<String> supplier = () -> "1024"; System.out.println(supplier.get()); } }

1|1313、Stream流式计算

1|0什么是Stream流式计算

Stream(流)是一个来自数据源的元素队列并支持聚合操作

大数据时代:存储 + 计算

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

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

在这里插入图片描述

/** * 题目要求:一分钟内完成此题,只能用一行代码实现! * 现在有5个用户!筛选: * 1、ID 必须是偶数 * 2、年龄必须大于23岁 * 3、用户名转为大写字母 * 4、用户名字母倒着排序 * 5、只输出一个用户! */ public class Test { public static void main(String[] args) { User u1 = new User(1, "a", 21); User u2 = new User(2, "b", 22); User u3 = new User(3, "c", 23); User u4 = new User(4, "d", 24); User u5 = new User(5, "e", 25); // 集合就是存储 List<User> list = Arrays.asList(u1, u2, u3, u4, u5); // 计算交给stream流 // lambda表达式、链式编程、函数式接口、Stream流式计算 // 第一种倒序排序 // list.stream() // .filter(u -> u.getId() % 2 == 0) // .filter(u -> u.getAge() > 23) // .peek(u -> u.setName(u.getName().toUpperCase())) // .sorted((a, b) -> b.getName().compareTo(a.getName())) // .limit(1) // .forEach(System.out::println); // 第二种倒序排序 list.stream() .filter(u -> u.getId() % 2 == 0) .filter(u -> u.getAge() > 23) .peek(u -> u.setName(u.getName().toUpperCase())) .sorted(Comparator.comparing(User::getName).reversed()) .limit(1) .forEach(System.out::println); } }

1|1414、ForkJoin

什么是 ForkJoin

ForkJoin 在 JDK 1.7 , 并行执行任务!提高效率。大数据量!

大数据:Map Reduce (把大任务拆分为小任务)

在这里插入图片描述

ForkJoin 特点:工作窃取

这个里面维护的都是双端队列

img

ForkJoin

在这里插入图片描述

在这里插入图片描述

/** * 求和计算的任务! * 3000 6000(ForkJoin) 9000(Stream并行流) * // 如何使用 forkjoin * // 1、forkjoinPool 通过它来执行 * // 2、计算任务 forkjoinPool.execute(ForkJoinTask task) * // 3. 计算类要继承 ForkJoinTask */ public class ForkJoinDemo extends RecursiveTask<Long> { private Long start; private Long end; // 临界值 private Long temp = 10000L; public ForkJoinDemo(Long start, Long end) { this.start = start; this.end = end; } // 计算方法 @Override protected Long compute() { if ((end - start) < temp) { long sum = 0L; for (Long i = start; i <= end; i++) { sum+=i; } return sum; } else { long middle = (start + end) / 2; // 中间值 ForkJoinDemo task1 = new ForkJoinDemo(start, middle); task1.fork(); // 拆分任务,把任务压入线程队列 ForkJoinDemo task2 = new ForkJoinDemo(middle + 1, end); task2.fork(); // 拆分任务,把任务压入线程队列 return task1.join() + task2.join(); } } }

测试

/** * 同一个任务,别人效率高你几十倍! */ public class Test { public static void main(String[] args) throws ExecutionException, InterruptedException { // test1(); // 10035 // test2(); // 5409 // test3(); // 434 } // 普通程序员 public static void test1() { Long sum = 0L; long start = System.currentTimeMillis(); for (Long i = 1L; i <= 10_0000_0000; i++) { sum += i; } long end = System.currentTimeMillis(); System.out.println("sum="+ sum +" 时间:" + (end - start)); } // 会使用ForkJoin public static void test2() throws ExecutionException, InterruptedException { long start = System.currentTimeMillis(); ForkJoinPool forkJoinPool = new ForkJoinPool(); ForkJoinTask<Long> task = new ForkJoinDemo(1L, 10_0000_0000L); ForkJoinTask<Long> submit = forkJoinPool.submit(task);// 提交任务 long sum = submit.get(); long end = System.currentTimeMillis(); System.out.println("sum="+ sum +" 时间:" + (end - start)); } // stream 并行流 public static void test3() { long start = System.currentTimeMillis(); long sum = LongStream.rangeClosed(1L, 10_0000_0000L).parallel().reduce(0, Long::sum); long end = System.currentTimeMillis(); System.out.println("sum="+ sum +" 时间:" + (end - start)); } }

1|1515、异步回调

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

在这里插入图片描述

/** * 异步调用: CompletableFuture * // 异步执行 * // 成功回调 * // 失败回调 */ public class Demo01 { public static void main(String[] args) throws ExecutionException, InterruptedException { // 没有返回值的 runAsync 异步回调 // CompletableFuture<Void> completableFuture = CompletableFuture.runAsync(() -> { // try { // TimeUnit.SECONDS.sleep(2); // } catch (InterruptedException e) { // throw new RuntimeException(e); // } // System.out.println(Thread.currentThread().getName() + "runAsync=>Void"); // }); // // System.out.println("1111"); // completableFuture.get(); // 获取执行结果 // 有返回值的 supplyAsync 异步回调 // ajax,成功和失败的回调 // 返回的是错误信息; CompletableFuture<Integer> completableFuture = CompletableFuture.supplyAsync(() -> { System.out.println(Thread.currentThread().getName() + "supplyAsync=>Integer"); int i = 10/0; return 1024; }); System.out.println(completableFuture.whenComplete((t, u) -> { System.out.println("t=" + t); // 正常的返回结果 System.out.println("u=" + u); // 错误信息 java.util.concurrent.CompletionException: java.lang.ArithmeticException: / by zero }).exceptionally((e) -> { System.out.println(e.getMessage()); return 233; }).get()); } }

1|1616、JMM

请你谈谈你对 Volatile 的理解

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

1、保证可见性

2、不保证原子性

3、禁止指令重排

1|0什么是JMM

JMM(Java memory model): Java内存模型。不存在的东西,概念!约定!

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

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

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

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

线程 工作内存 、主内存

8种操作:

在这里插入图片描述

这里是先store再write

在这里插入图片描述

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

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

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

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

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

public class JMMDemo { private static int num = 0; public static void main(String[] args) { // main new Thread(() -> { while (num == 0) { } }).start(); try { TimeUnit.SECONDS.sleep(1); } catch (InterruptedException e) { throw new RuntimeException(e); } num = 1; System.out.println(num); } }

输出结果了,但是程序没有停

在这里插入图片描述

1|1717、Volatile

1|017.1、保证可见性

加上volatile

public class JMMDemo { // 不加 volatile 程序就会死循环 // 加 volatile 可以保证可见性 private volatile static int num = 0; public static void main(String[] args) { // main new Thread(() -> { while (num == 0) { } }).start(); try { TimeUnit.SECONDS.sleep(1); } catch (InterruptedException e) { throw new RuntimeException(e); } num = 1; System.out.println(num); } }

1|017.2、不保证原子性

原子性 : 不可分割

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

// 不保证原子性 public class VDemo02 { // volatile 不保证原子性 private volatile static int num = 0; public static void add() { num++; // 不是一个原子性操作 } public static void main(String[] args) { // 理论上为两万 for (int i = 1; i <= 20; i++) { new Thread(() -> { for (int j = 0; j < 1000; j++) { add(); } }).start(); } while (Thread.activeCount() > 2) { // main 和 gc 默认执行 Thread.yield(); } System.out.println(Thread.currentThread().getName() + " " + num); } }

如果不加 lock 和 synchronized ,怎么样保证原子性

在这里插入图片描述

使用原子类,解决 原子性问题

在这里插入图片描述

// 不保证原子性 public class VDemo02 { // volatile 不保证原子性 // 原子类的 Integer private volatile static AtomicInteger num = new AtomicInteger(0); public static void add() { // num++; // 不是一个原子性操作 num.getAndIncrement(); // AtomicInteger + 1 方法, CAS } public static void main(String[] args) { // 理论上为两万 for (int i = 1; i <= 20; i++) { new Thread(() -> { for (int j = 0; j < 1000; j++) { add(); } }).start(); } while (Thread.activeCount() > 2) { // main 和 gc 默认执行 Thread.yield(); } System.out.println(Thread.currentThread().getName() + " " + num); } }

这些类的底层都直接和操作系统挂钩!在内存中修改值!Unsafe类是一个很特殊的存在!

1|017.3、指令重排

什么是 指令重排:你写的程序,计算机并不是按照你写的那样去执行的。

源代码–>编译器优化的重排–> 指令并行也可能会重排–> 内存系统也会重排—> 执行

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

int x = 1; // 1 int y = 2; // 2 x = x + 5; // 3 y = x + x; // 4 我们所期望的:1234 2134 1324 不可能是 4213

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

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

正常的结果: x = 0;y = 0;但是可能由于指令重排

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

指令重排导致的诡异结果: x = 2;y = 1;

非计算机专业

volatile可以避免指令重排:

内存屏障。CPU指令。作用:

1、保证特定的操作的执行顺序!

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

在这里插入图片描述

Volatile 是可以保持 可见性。不能保证原子性,由于内存屏障,可以保证避免指令重排的现象产生!

1|1818、彻底玩转单例模式

饿汉式 DCL懒汉式 ,探究!

1|0饿汉式

// 饿汉式单例 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; } }

1|0懒汉式单例

// 懒汉式单例 // 道高一尺魔高一丈 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() + " ok"); } private volatile static LazyMan lazyMan; // 双重检测锁模式的 懒汉式单例 DCL懒汉式 public static LazyMan getInstance() { if (lazyMan == null) { synchronized (LazyMan.class) { if (lazyMan == null) { lazyMan = new LazyMan(); // 不是一个原子性操作 /** * 1.分配内存空间 * 2.执行构造方法,初始化内存对象 * 3.把这个对象指向这个空间 */ } } } return lazyMan; } // 反射! 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 instance2 = declaredConstructor.newInstance(); System.out.println(instance); System.out.println(instance2); } }

1|0静态内部类

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

单例不安全,有反射

1|0枚举

查看反射的newInstance()方法,发现不能对枚举类使用

image-20221007114436514

测试

// enum 是一个什么? 本身也是一个Class类 public enum EnumSingle { INSTANCE; public EnumSingle getInstance() { return INSTANCE; } } class Test{ public static void main(String[] args) throws NoSuchMethodException, InvocationTargetException, InstantiationException, IllegalAccessException { 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); // NoSuchMethodException: com.dt.single.EnumSingle.<init>(java.lang.String, java.lang.Integer) System.out.println(instance2); } }

反编译class文件:

image-20221007113810853

枚举类型的最终反编译源码:

// Decompiled by Jad v1.5.8g. Copyright 2001 Pavel Kouznetsov. // Jad home page: http://www.kpdus.com/jad.html // Decompiler options: packimports(3) // Source File Name: EnumSingle.java package com.dt.single; 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/dt/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 }); } }

1|1919、深入理解CAS

1|019.1、什么是 CAS(compare and swap)

大厂你必须要深入研究底层!有所突破! 修内功,操作系统,计算机网络原理

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()); System.out.println(atomicInteger.compareAndSet(2020, 2021)); System.out.println(atomicInteger.get()); } }

1|019.2、Unsafe 类

image-20221007121549596

image-20221007122549930

image-20221007122649147

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

缺点:

1、 循环会耗时

2、一次性只能保证一个共享变量的原子性

3、ABA问题

1|019.3、CAS : ABA 问题(狸猫换太子)

在这里插入图片描述

例子

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()); System.out.println(atomicInteger.compareAndSet(2021, 2020)); System.out.println(atomicInteger.get()); // ============== 期望的线程 ================== System.out.println(atomicInteger.compareAndSet(2020, 6666)); System.out.println(atomicInteger.get()); } }

1|2020、原子引用

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

带版本号 的原子操作!

public class CASBAB { public static void main(String[] args) { // AtomicStampedReference 注意,如果泛型是一个包装类,注意对象的引用问题 // 正常在业务操作中,这里面比较的都是一个个对象 // integer默认缓存-128->127,超过这个范围就要new对象了,就会分配新的地址,我们看到源码是==,非数值类型,我们比较的是对象的地址 AtomicStampedReference<Integer> atomicStampedReference = new AtomicStampedReference<>(1, 1); new Thread(() -> { int stamp = atomicStampedReference.getStamp(); // 获得版本号 System.out.println("a1=>" + stamp); try { TimeUnit.SECONDS.sleep(1); } catch (InterruptedException e) { throw new RuntimeException(e); } System.out.println(atomicStampedReference.compareAndSet(1, 2, atomicStampedReference.getStamp(), atomicStampedReference.getStamp() + 1)); System.out.println("a2=>" + atomicStampedReference.getStamp()); System.out.println(atomicStampedReference.compareAndSet(2, 1, atomicStampedReference.getStamp(), atomicStampedReference.getStamp() + 1)); System.out.println("a3=>" + atomicStampedReference.getStamp()); }, "a").start(); // 乐观锁的原理相同 new Thread(() -> { int stamp = atomicStampedReference.getStamp(); // 获得版本号 System.out.println("b1=>" + stamp); try { TimeUnit.SECONDS.sleep(2); } catch (InterruptedException e) { throw new RuntimeException(e); } System.out.println(atomicStampedReference.compareAndSet(1, 6, stamp, stamp + 1)); System.out.println("b2=>" + atomicStampedReference.getStamp()); }, "b").start(); } }

注意:

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

在这里插入图片描述

1|2121、各种锁的理解

1|021.1、公平锁、非公平锁

公平锁: 非常公平, 不能够插队,必须先来后到!

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

public ReentrantLock() { sync = new NonfairSync(); } public ReentrantLock(boolean fair) { sync = fair ? new FairSync() : new NonfairSync(); }

1|021.2、可重入锁

可重入锁(递归锁)

在这里插入图片描述

Synchronized版

// Synchronized 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"); } }

结果:

A sms A call B sms B call

lock版

// lcok 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.lock(); lock.unlock(); // lock 锁必须配对,否则就会死在里面 lock.lock(); try { System.out.println(Thread.currentThread().getName() + " sms"); call(); // 这里也有锁 } catch (Exception e) { throw new RuntimeException(e); } finally { lock.unlock(); lock.unlock(); } } public void call() { lock.lock(); try { System.out.println(Thread.currentThread().getName() + " call"); } catch (Exception e) { throw new RuntimeException(e); } finally { lock.unlock(); } } }

1|021.3、自旋锁

spinlock

image-20221007140011737

我们来自定义一个锁测试

/** * 自旋锁 */ public class SpinlockDemo { 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(); // 底层使用自旋锁 SpinlockDemo lock = new SpinlockDemo(); new Thread(() -> { lock.myLock(); try { TimeUnit.SECONDS.sleep(3); } catch (Exception e) { throw new RuntimeException(e); } finally { lock.myUnlock(); } }, "T1").start(); TimeUnit.SECONDS.sleep(1); new Thread(() -> { lock.myLock(); try { TimeUnit.SECONDS.sleep(1); } catch (Exception e) { throw new RuntimeException(e); } finally { lock.myUnlock(); } }, "T2").start(); } }

结果:

T1==> myLock T2==> myLock T1==> myUnlock T2==> myUnlock

1|021.4、死锁

死锁是什么

img

死锁测试,怎么排除死锁:

public class DeadLockDemo { 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) { throw new RuntimeException(e); } synchronized (lockB) { System.out.println(Thread.currentThread().getName() + "lock:" + lockB + "=>get" + lockA); } } } }

解决问题

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

image-20221007142920695

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

image-20221007143313673

面试,工作中! 排查问题:

1、日志 9人回答

2、堆栈 1人回答


__EOF__

本文作者userName
本文链接https://www.cnblogs.com/dt746294093/p/16767955.html
关于博主:评论和私信会在第一时间回复。或者直接私信我。
版权声明:本博客所有文章除特别声明外,均采用 BY-NC-SA 许可协议。转载请注明出处!
声援博主:如果您觉得文章对您有帮助,可以点击文章右下角推荐一下。您的鼓励是博主的最大动力!
posted @   D..T  阅读(127)  评论(0编辑  收藏  举报
相关博文:
阅读排行:
· 【自荐】一款简洁、开源的在线白板工具 Drawnix
· 没有Manus邀请码?试试免邀请码的MGX或者开源的OpenManus吧
· 无需6万激活码!GitHub神秘组织3小时极速复刻Manus,手把手教你使用OpenManus搭建本
· C#/.NET/.NET Core优秀项目和框架2025年2月简报
· DeepSeek在M芯片Mac上本地化部署
点击右上角即可分享
微信分享提示