0
点赞
收藏
分享

微信扫一扫

1.JUC并发编程

浮游图灵 2022-03-31 阅读 114
java

JUC并发编程

1、什么是JUC

学习方式:源码 + 官方文档

JUC 是 java.util.concurrent

面试高频问 JUC

java.util 是 Java 的一个工具包

业务:普通的线程代码 Thread

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

2、线程和进程

进程:一个程序,QQ.exe , Music.exe;数据 + 代码 + pcb ;是程序的集合

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

Java默认有几个线程? 2 个线程! main线程、GC线程

线程:开了一个进程Typora,写字,等待几分钟会进行自动保存(线程负责的)

对于 Java 而言:Thread、Runnable、Callable 进行开启线程的,之前。

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

        /* Notify the group that this thread is about to be started
         * so that it can be added to the group's list of threads
         * and the group's unstarted count can be decremented. */
        group.add(this);

        boolean started = false;
        try {
            start0();
            started = true;
        } finally {
            try {
                if (!started) {
                    group.threadStartFailed(this);
                }
            } catch (Throwable ignore) {
                /* do nothing. If start0 threw a Throwable then
                  it will be passed up the call stack */
            }
        }
    }
	
	// 这是一个 C++ 底层,Java是没有权限操作底层硬件的
    private native void start0();

Java 是没有权限去开启线程,操作硬件的,这是一个 native 的一个本地方法,它调用的底层的 C++代码。

并发:多线程操作同一个资源。

  • CPU只有一核,模拟出来多条线程。 天下武功,唯快不破。那么我们就可以使用CPU快速交替,来模拟多线程。

**并行:**多个人一起行走。

  • CPU多核,多个线程可以同时执行。我们可以使用线程池!

获取本机cpu 的核数

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

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

线程的状态: 6 个状态

public enum State {
        // 新生
        NEW,

        // 运行
        RUNNABLE,

        // 阻塞
        BLOCKED,

        // 等待
        WAITING,

        // 超时等待
        TIMED_WAITING,

        // 终止
        TERMINATED;
    }
    
  1. 来自不同的类
    1. wait ==> Object
    2. sleep ==> Thread
  2. 关于锁的释放
    1. wait 会释放锁
    2. sleep 睡着了 ,不会释放锁
  3. 使用的范围是不同的
    1. wait 必须在同步代码块中
    2. sleep 可以在任何地方睡
  4. 是否需要抛出异常
    1. wait 是不需要捕获异常的
    2. sleep 必须捕获异常

3、Lock锁(重点)

package com.hou.demo;


/**
 * 真正的多线程开发
 * 线程就是一个单独的资源类,没有任何的附属操作
 */
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 +"张票,剩余:" + number);
            number--;
        }
    }
}

  • lock() 加锁
  • unlock() 释放锁
  • 所有已知的实现类
    • ReentrantLock 可重入锁
    • ReentrantReadWriteLock.ReadLock 可重入读锁
    • ReentrantReadWriteLock.WriteLock 可重入写锁
public ReentrantLock() {
        sync = new NonfairSync(); // 非公平锁
    }

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

**公平锁:**十分公平,必须先来后到 1s 1hour

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

package com.hou.demo;

import java.util.concurrent.locks.Lock;
import java.util.concurrent.locks.ReentrantLock;

public class SaleTicketDemo2 {
    public static void main(String[] args) {
        // 多线程操作
        // 并发:多线程同时操作同一个资源类,把资源类丢入线程
        Ticket2 ticket2 = new Ticket2();
        new Thread(()->{for (int i=0; i<40; i++) ticket2.sale();},"A").start();
        new Thread(()->{for (int i=0; i<40; i++) ticket2.sale();},"B").start();
        new Thread(()->{for (int i=0; i<40; i++) ticket2.sale();},"C").start();
    }
}

// lock 三部曲
// 1.  Lock lock=new ReentrantLock();
// 2.  lock.lock() 加锁
// 3.  finally => 解锁:lock.unlock();
class Ticket2{
    private int number = 50;

    Lock lock = new ReentrantLock();

    // 卖票的方式
    // 使用Lock锁
    public void sale(){
        // 加锁
        lock.lock();

        try {
            // 业务代码
            if (number>=0){
                System.out.println(Thread.currentThread().getName() + "卖出了第:" + number + "张票,剩余:" + number);
                number--;
            }

        }catch (Exception e){
            e.printStackTrace();
        }finally {
            lock.unlock();
        }
    }
}

Synchronized 和 Lock 区别

  • 1、Synchronized 是内置的 Java 关键字,Lock是一个 Java 类
  • 2、Synchronized 无法判断获取锁的状态,Lock可以判断
  • 3、Synchronized 会自动释放锁,Lock 必须要手动加锁和手动释放锁!如果不释放锁,死锁
  • 4、Synchronized 线程1(获得锁->阻塞)、线程2(一直等待),Lock就不一定会一直等待下去,Lock会有一个trylock() 方法去尝试获取锁,不会造成长久的等待
  • 5、Synchronized 是可重入锁,不可以中断的,非公平的; Lock,可重入的,可以判断锁,可以自己设置公平锁和非公平锁
  • 6、Synchronized 适合锁少量的代码同步问题,Lock适合锁大量的代码同步问题

锁到底是什么? 如何判断锁的是谁?

4、生产者和消费者问题!

Synchronized wait notify 可以实现,该方法是传统方法

Synchronized 版本

package com.hou.pc;

// 生产者消费者问题 Synchronized
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 {
        // if 存在问题
        while (number!=0){
            // 等待
            this.wait();
        }
        number++;
        System.out.println(Thread.currentThread().getName() + "=>" + number);
        // 通知其他线程,我+1执行完了
        this.notifyAll();
    }

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

使用 if 判断 问题存在,A线程B线程,现在如果有更多个线程执行,就会产生虚假唤醒的问题

解决:将 if 改为 while 即可, 防止虚假唤醒,这样问题就不存在了

JUC 版的生产者消费者问题:

await、signal 替换 wait、notify

通过Lock找到Condition

package com.hou.pc;

import java.util.concurrent.locks.Condition;
import java.util.concurrent.locks.Lock;
import java.util.concurrent.locks.ReentrantLock;

public class B {
    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 number = 0;

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

    // +1
    public void increment(){
        lock.lock();
        try {
            // 业务代码
            while (number!=0){
                // 等待
                condition.await();
            }
            number++;
            System.out.println(Thread.currentThread().getName() + "=>" + number);
            condition.signalAll(); // 通知其他线程
        }catch (Exception e){
            e.printStackTrace();
        }finally {
            lock.unlock();
        }
    }

    // -1
    public void decrement(){
        lock.lock();
        try {
            // 业务代码
            while (number==0){
                // 等待
                condition.await();
            }
            number--;
            System.out.println(Thread.currentThread().getName() + "=>" + number);
            condition.signalAll(); // 通知其他线程
        }catch (Exception e){
            e.printStackTrace();
        }finally {
            lock.unlock();
        }

    }

}

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

Condition 的优势:精准的通知和唤醒的线程

如果我们要指定通知的下一个进行顺序怎么办呢?我们可以使用Condition来指定通知进程~

package com.hou.pc;

import java.util.concurrent.locks.Condition;
import java.util.concurrent.locks.Lock;
import java.util.concurrent.locks.ReentrantLock;

// 生产者消费者问题  使用 Condition 精准唤醒线程
public class C {
    public static void main(String[] args) {
        Data3 data = new Data3();

        new Thread(()->{
            for (int i = 0; i < 10; i++) {
                data.printA();
            }
        },"A").start();
        new Thread(()->{
            for (int i = 0; i < 10; i++) {
                data.printB();
            }
        },"B").start();
        new Thread(()->{
            for (int i = 0; i < 10; i++) {
                data.printC();
            }
        },"C").start();
    }
}

// 资源类
class Data3{
    private int number = 1;
    private Lock lock = new ReentrantLock();
    private Condition condition1 = lock.newCondition();
    private Condition condition2 = lock.newCondition();
    private Condition condition3 = lock.newCondition();

    public void printA(){
        lock.lock();

        try {
            while (number!=1){
                // 等待
                condition1.await();
            }
            System.out.println(Thread.currentThread().getName() + "=>AAAAAA");
            number = 2;
            // 唤醒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() + "=>BBBBBB");
            number = 3;
            // 唤醒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() + "=>CCCCCCC");
            number = 1;
            // 唤醒1
            condition1.signal();
        }catch (Exception e){
            e.printStackTrace();
        }finally {
            lock.unlock();
        }
    }
}

5、8锁现象

如何判断锁的是谁! 锁到底锁的是谁?

锁会锁住:对象,Class

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

深刻理解我们的锁

  • 问题1
package com.hou.lock8;

import java.util.concurrent.TimeUnit;

/**
 * 8锁,其实就是关于锁的8个问题
 * 1.标准情况下,俩个线程,先打印发短信,还是打电话?
 */
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{
    public synchronized void sendSms(){
        System.out.println("发短信");
    }

    public synchronized void call(){
        System.out.println("打电话");
    }
}


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

  • 问题2 让发短信 延时4s
package com.hou.lock8;

import java.util.concurrent.TimeUnit;

public class Test2 {
    public static void main(String[] args) {
        Phone2 phone2 = new Phone2();

        new Thread(()->{phone2.sendSms();},"A").start();

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

        new Thread(()->{phone2.call();},"B").start();
    }
}

class Phone2{
    public synchronized void sendSms(){
        try {
            TimeUnit.SECONDS.sleep(4);
        } catch (InterruptedException e) {
            e.printStackTrace();
        }
        System.out.println("发短信");

    }

    public synchronized void call(){
        System.out.println("打电话");
    }
}

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

原因:并不是因为顺序执行,是因为synchronized锁的对象是方法的调用,对于俩个方法用的是同一个锁,谁先拿到谁先执行

  • 问题3

在添加一个普通方法,那么先执行哪一个方法呢?

package com.hou.lock8;

import java.util.concurrent.TimeUnit;

public class Test3 {
    public static void main(String[] args) {
        Phone3 phone3 = new Phone3();

        new Thread(()->{phone3.sendSms();},"A").start();
        new Thread(()->{phone3.call();},"A").start();
        new Thread(()->{phone3.hello();},"A").start();
    }
}

class Phone3{
    public synchronized void sendSms(){
        try {
            TimeUnit.SECONDS.sleep(4package com.hou.lock8;

import java.util.concurrent.TimeUnit;

public class Test4 {
    public static void main(String[] args) {
        Phone4 phone1 = new Phone4();
        Phone4 phone2 = new Phone4();

        new Thread(()->{phone1.sendSms();}).start();

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

        new Thread(()->{phone2.call();}).start();
    }
}

class Phone4{
    public synchronized void sendSms(){
        try {
            TimeUnit.SECONDS.sleep(4);
        } catch (InterruptedException e) {
            e.printStackTrace();
        }
        System.out.println("发短信");
    }

    public synchronized void call(){
        System.out.println("打电话");
    }
}
);
        } catch (InterruptedException e) {
            e.printStackTrace();
        }
        System.out.println("发短信");
    }

    public synchronized void call(){
        System.out.println("打电话");
    }

    public void hello(){
        System.out.println("hello");
    }
}

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

原因:hello 是一个普通方法,不受synchronized锁的影响,普通方法是顺序执行的

  • 问题4

如果我们使用的是俩个对象,一个调用发短信,一个调用打电话,那么整个顺序是怎么样的呢?

package com.hou.lock8;

import java.util.concurrent.TimeUnit;

public class Test4 {
    public static void main(String[] args) {
        Phone4 phone1 = new Phone4();
        Phone4 phone2 = new Phone4();

        new Thread(()->{phone1.sendSms();}).start();

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

        new Thread(()->{phone2.call();}).start();
    }
}

class Phone4{
    public synchronized void sendSms(){
        try {
            TimeUnit.SECONDS.sleep(4);
        } catch (InterruptedException e) {
            e.printStackTrace();
        }
        System.out.println("发短信");
    }

    public synchronized void call(){
        System.out.println("打电话");
    }
}

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

原因:在发短信方法中延时了4s,又因为synchronized锁的是对象,但是我们这使用的是俩个对象,所以每个对象都有一把锁,所以不会造成锁的等待。正常执行

  • 问题5,问题6

我们把synchronized的方法加上static变成静态方法,那么顺序又是怎样的?

(1)先使用一个对象调用俩个方法

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

(2)使用俩个对象调用俩个方法

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

原因:对于static静态方法来说,对于整个类Class来说只有一份,对于不同的对象使用的是同一个锁,

如果静态static方法使用的synchronized锁,那么这个锁是这个class的,不管多少对象,对于class锁都只有一个,谁先拿到这个锁就先执行,其他的进程都需要等待!

  • 问题7

使用一个静态同步方法,一个同步方法,一个对象调用顺序是什么?

package com.hou.lock8;

import java.util.concurrent.TimeUnit;

public class Test5 {
    public static void main(String[] args) {
        Phone5 phone5 = new Phone5();
        new Thread(()->{Phone5.sendSms();}).start();

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

        new Thread(()->{phone5.call();}).start();
    }
}

class Phone5{
    public static synchronized void sendSms(){
        try {
            TimeUnit.SECONDS.sleep(4);
        } catch (InterruptedException e) {
            e.printStackTrace();
        }

        System.out.println("发短信");
    }

    public synchronized void call(){
        System.out.println("打电话");
    }
}

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

原因:因为 一个锁锁的是Class类模板,一个锁的是对象调用者

  • 问题8

使用一个静态同步方法,一个同步方法,俩个对象调用顺序是什么?

package com.hou.lock8;

import java.util.concurrent.TimeUnit;

public class Test5 {
    public static void main(String[] args) {
        Phone5 phone1 = new Phone5();
        Phone5 phone2 = new Phone5();
        new Thread(()->{phone1.sendSms();}).start();

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

        new Thread(()->{phone2.call();}).start();
    }
}

class Phone5{
    public static synchronized void sendSms(){
        try {
            TimeUnit.SECONDS.sleep(4);
        } catch (InterruptedException e) {
            e.printStackTrace();
        }

        System.out.println("发短信");
    }

    public synchronized void call(){
        System.out.println("打电话");
    }
}

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

原因:俩吧锁锁的不是同一个东西,不需要等待释放锁

小结:

锁 锁的是调用者 (对象 或者 Class)

  • new 出来的 this 是具体的一个对象

  • static Class 是唯一的一个模板

6、集合类不安全

List 在并发情况下是不安全

java.util.ConcurrentModificationException 并发修改异常

package com.hou.unsafe;

import java.util.ArrayList;
import java.util.UUID;

public class ListTest {
    public static void main(String[] args) {
        ArrayList<Object> arrayList = new ArrayList<>();

        // java.util.ConcurrentModificationException 并发修改异常
        for (int i = 0; i < 10; i++) {
            new Thread(()->{
                arrayList.add(UUID.randomUUID().toString().substring(0,5));
                System.out.println(arrayList);
            },String.valueOf(i)).start();
        }
    }
}

会造成 并发修改异常 (ConcurrentModificationException)

解决方案:

1、切换成Vector

package com.hou.unsafe;

import java.util.UUID;
import java.util.Vector;

public class ListTest {
    public static void main(String[] args) {
        Vector<Object> vector = new Vector<>();

        // Vector add()方法采用的是 synchronized方法
        for (int i = 0; i < 10; i++) {
            new Thread(()->{
                vector.add(UUID.randomUUID().toString().substring(0,5));
                System.out.println(vector);
            },String.valueOf(i)).start();
        }
    }
}

2、使用Collections.synchronizedList(new ArrayList<>()) 方法

package com.hou.unsafe;

import java.util.ArrayList;
import java.util.Collections;
import java.util.List;
import java.util.UUID;

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

3、使用 JUC 中的包:new CopyOnWriteArrayList<>();

package com.hou.unsafe;

import java.util.ArrayList;
import java.util.Collections;
import java.util.List;
import java.util.UUID;
import java.util.concurrent.CopyOnWriteArrayList;

public class ListTest {
    public static void main(String[] args) {
        CopyOnWriteArrayList<Object> arrayList = new CopyOnWriteArrayList<>();

        // CopyOnWriteArrayList   add()方法采用的是lock.lock()锁的方法
        for (int i = 0; i < 10; i++) {
            new Thread(()->{
                arrayList.add(UUID.randomUUID().toString().substring(0,5));
                System.out.println(arrayList);
            },String.valueOf(i)).start();
        }
    }
}

CopyOnWriteArrayList:写入时复制!COW计算机程序设计领域的一种优化策略

多个线程调用的时候,list,读取的时候,固定的,写入(存在覆盖操作);在写入的时候避免覆盖,造成数据错乱的问题,读写分离

CopyOnWriteArrayList 比 Vector 厉害在哪里?

  • Vector 底层是使用 synchronized 关键字来实现的,效率比较低下
  • CopyOnWriteArrayList 使用的是Lock锁,效率会更加高效

Set 在并发情况下不安全

和List、Set 同级的还有一个BlockingQueue 阻塞队列;

Set 和 List 同理可得:在多线程情况下,普通的Set集合是线程不安全的

解决方案还是俩种:

  • 使用 Collections 工具类的 synchronized 包装的 Set类
  • 使用CopyOnWriteArraySet 写入时复制的 JUC 解决方案
package com.hou.unsafe;

import java.util.*;
import java.util.concurrent.CopyOnWriteArraySet;

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

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

    }
}

HashSet 底层是什么?

  • hashSet 底层就是HashMap

    [外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-QsUy0LQI-1648735892686)(C:\Users\17,\AppData\Roaming\Typora\typora-user-images\image-20220331213009044.png)]

    [外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-qp2Gdcbs-1648735892688)(C:\Users\17,\AppData\Roaming\Typora\typora-user-images\image-20220331213051678.png)]

  • add() 方法的本质就是一个map的key,map 的key是无法重复的,所以使用的就是map存储

  • hashSet 就是使用了 hashmap key 不重复的原理

  • PRESENT 是什么? 是一个常量,不会改变的常量, 无用的占位符

Map在并发情况下不安全

回顾 map 的基本操作:

  • 默认的加载因子是0.75,默认的初始容量是16
  • new HashMap<>() 等价于 new HashMap<>(16,0.75)

同样的HashMap 基础类也存在并发修改异常

解决方案:

  • 使用Collections.synchronizedMap(new HashMap<>()) 处理
  • 使用ConcurrentHashMap 进行并发处理
package com.hou.unsafe;

import java.util.Collections;
import java.util.HashMap;
import java.util.Map;
import java.util.UUID;
import java.util.concurrent.ConcurrentHashMap;

public class MapTest {
    public static void main(String[] args) {
        // HashMap<String, String> hashMap = new HashMap<>();
        // Map<String, String> map = Collections.synchronizedMap(new HashMap<String, String>());
        ConcurrentHashMap<String, String> map = new ConcurrentHashMap<>();

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

研究ConcurrentHashMap底层原理

7、Callable (简单)

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

Callable的特点

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

代码测试

使用传统的线程方式:

package com.hou.callable;

public class CallableTest {
    public static void main(String[] args) {
        for (int i = 1; i <= 10; i++) {
            new Thread(new MyThread()).start();
        }
    }
}

class MyThread implements Runnable{

    @Override
    public void run() {
        System.out.println(Thread.currentThread().getName());
    }
}

使用Callable进行多线程操作

[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-NJ34v5XM-1648735892689)(C:\Users\17,\AppData\Roaming\Typora\typora-user-images\image-20220331212636815.png)]

Callable 泛型 V 就是call() 运行方法的返回值类型

如何使用呢?

Callable 怎么放入到 Thread里面呢?

源码分析:

[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-o7zArTFH-1648735892691)(C:\Users\17,\AppData\Roaming\Typora\typora-user-images\image-20220331212604894.png)]

对于Thread运行,只能传入Runnable类型的参数

我们这是Callable怎么办呢?

看JDK api文档:

​ 在Runnable里面有一个叫做FutureTask的实现类

​ FutureTask中可以接收Callable参数

这样我们就可以先把Callable放入到FurureTask中,然后再把FutureTask 放入到Thread当中就可以了

package com.hou.callable;

import java.util.concurrent.Callable;
import java.util.concurrent.ExecutionException;
import java.util.concurrent.FutureTask;

public class CallableTest2 {
    public static void main(String[] args) throws ExecutionException, InterruptedException {
        MyThread2 thread = new MyThread2();

        for (int i = 1; i < 10; i++) {
            // 适配类:FutureTask
            FutureTask<String> futureTask = new FutureTask<String>(thread);
            // 放入Thread使用
            new Thread(futureTask,String.valueOf(i)).start();
            // 获取返回值
            String s = futureTask.get();
            // get()方法可能会被阻塞,如果在call方法中是一个耗时的方法,所以一般情况我们会把这个放在最后,或者使用异步通信	
            System.out.println("返回值:" + s);

        }
    }
}

class MyThread2 implements Callable{

    @Override
    public String call() throws Exception {
        System.out.println("Call:" + Thread.currentThread().getName());
        return "String:" + Thread.currentThread().getName();
    }
}

这样我们就可以使用Callable来进行多线程编程了,并且我们发现可以有返回值,并且可以抛出异常[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-JHgCR1Jb-1648735892693)(C:\Users\七七\AppData\Roaming\Typora\typora-user-images\image-20220309164241391.png)]

重点:

  • new Thread(futureTask,“A”).start(); //结果会被缓存
  • futureTask.get() get()方法可能会被阻塞,如果在call方法中是一个耗时的方法,所以一般情况我们会把这个放在最后,或者使用异步通信

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

8.1、CountDownLatch

其实就是一个减法计数器,对于计数器归零之后在进行后面的操作,这是一个计数器

package com.hou.auxiliarywclass;

import java.util.concurrent.CountDownLatch;

// 这是一个技术去,减法
public class CountDownLatchTest {
    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");
    }
}

主要方法:

  • countDown() 减一操作
  • await() 等待计数器归零

await() 等待计数器为0,就唤醒,再继续向下执行

8.2、CyclickBarrier

其实就是一个加法计数器

package com.hou.auxiliarywclass;

import java.util.concurrent.BrokenBarrierException;
import java.util.concurrent.CyclicBarrier;

public class CyclickBarrierTest {
    public static void main(String[] args) {

        // 主线程
        CyclicBarrier cyclicBarrier = new CyclicBarrier(7,()->{
            System.out.println("召唤神龙~");
        });

        for (int i = 1; i <= 7; i++) {
            // 子线程
            int finallI = i;  // 由于作用域的问题,在线程中拿不到 i 的值,所以需要一个中间常量
            new Thread(()->{
                System.out.println(Thread.currentThread().getName() + "收集了第 " + finallI + " 颗龙珠");
                try {
                    cyclicBarrier.await(); // 加法计数,等待
                } catch (InterruptedException e) {
                    e.printStackTrace();
                } catch (BrokenBarrierException e) {
                    e.printStackTrace();
                }
            }).start();
        }
    }
}

8.3、Semaphore

Semaphore:信号量

抢车位:

3个车位,6辆车:

package com.hou.auxiliarywclass;

import java.util.concurrent.Semaphore;
import java.util.concurrent.TimeUnit;

public class SemaphoreTest {
    public static void main(String[] args) {
        // 停车位为3个
        Semaphore semaphore = new Semaphore(3);

        for (int i = 1; i <= 6; i++) {
            int finalI = i;
            new Thread(()->{
                try {
                    semaphore.acquire(); // 得到
                    // 抢到车位
                    System.out.println(Thread.currentThread().getName() + " 抢到了车位 " + finalI);
                    TimeUnit.SECONDS.sleep(2); // 停车2s
                    System.out.println(Thread.currentThread().getName() + "离开了车位");
                } catch (InterruptedException e) {
                    e.printStackTrace();
                } finally {
                    semaphore.release(); // 释放
                }
            },String.valueOf(i)).start();
        }
    }
}

原理:

  • semaphore.acquire() 获得资源,如果资源已经使用完了,就等待资源释放后在进行使用
  • semaphore.relese() 释放,会将当前的信号量释放 +1,然后唤醒等待的线程
  • 作用:多个共享资源互斥的使用!并发限流,控制最大的线程数

9、读写锁

ReadWriteLock

先对于不加锁的情况:

如果我们做一个我们自己的cache缓存。分别有写入操作,读取操作

我们采用五个线程区写入,使用十个线程区读取。

我们来看下这个效果,在不加锁的情况下:

package com.hou.rw;

import java.util.HashMap;
import java.util.Map;
import java.util.concurrent.locks.Lock;
import java.util.concurrent.locks.ReadWriteLock;
import java.util.concurrent.locks.ReentrantLock;
import java.util.concurrent.locks.ReentrantReadWriteLock;

public class ReadWriteLockDemo {
    public static void main(String[] args) {
        MyCache_ReadWriteLock mycache = new MyCache_ReadWriteLock();

        // 开启五个线程  写入数据
        for (int i = 1; i <= 5; i++) {
            int finalI = i;
            new Thread(()->{
                mycache.put(String.valueOf(finalI),String.valueOf(finalI));
            },String.valueOf(i)).start();
        }

        // 开启十个线程  读取数据
        for (int i = 1; i <= 10; i++) {
            int finalI = i;
            new Thread(()->{
                mycache.get(String.valueOf(finalI));
            },String.valueOf(i)).start();
        }
    }
}

class MyCache_ReadWriteLock{
    private volatile Map<String,String> map = new HashMap<>();
    // 使用读写锁
    private ReadWriteLock readWriteLock = new ReentrantReadWriteLock();
    // 普通锁
    private Lock lock = new ReentrantLock();

    public void put(String key, String value){
        // 写入
        System.out.println(Thread.currentThread().getName() + "线程  开始写入");
        map.put(key, value);
        System.out.println(Thread.currentThread().getName() + "线程  写入OK");
    }

    public String get(String key){
        // 读取
        System.out.println(Thread.currentThread().getName() + "线程  开始读取");
        String s = map.get(key);
        System.out.println(Thread.currentThread().getName() + "线程  读取OK");
        return s;
    }
}

运行效果如下:

[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-r5XHr0Hl-1648735892695)(C:\Users\17,\AppData\Roaming\Typora\typora-user-images\image-20220331211803685.png)]

在一个线程写入的情况下,插入了其他线程的写入

对于这种情况会出现 数据不一致等情况

所以如果我们在多线程的情况下不加锁,会造成数据不可靠的问题

我们也可以采用Synchronized这种重量锁和轻量锁 lock 去保证数据的可靠

但是我们采用更细粒度的锁:ReadWriteLock 读写锁来保证,读写锁,也叫(独占锁,共享锁)

package com.hou.rw;

import java.util.HashMap;
import java.util.Map;
import java.util.concurrent.locks.Lock;
import java.util.concurrent.locks.ReadWriteLock;
import java.util.concurrent.locks.ReentrantLock;
import java.util.concurrent.locks.ReentrantReadWriteLock;

// 写,指可以一个一个线程的写,读,可以一起读
public class ReadWriteLockDemo {
    public static void main(String[] args) {
        MyCache_ReadWriteLock mycache = new MyCache_ReadWriteLock();

        // 开启五个线程  写入数据
        for (int i = 1; i <= 5; i++) {
            int finalI = i;
            new Thread(()->{
                mycache.put(String.valueOf(finalI),String.valueOf(finalI));
            },String.valueOf(i)).start();
        }

        // 开启十个线程  读取数据
        for (int i = 1; i <= 10; i++) {
            int finalI = i;
            new Thread(()->{
                mycache.get(String.valueOf(finalI));
            },String.valueOf(i)).start();
        }
    }
}

class MyCache_ReadWriteLock{
    private volatile Map<String,String> map = new HashMap<>();
    // 使用读写锁
    private ReadWriteLock readWriteLock = new ReentrantReadWriteLock();
    // 普通锁
    private Lock lock = new ReentrantLock();

    public void put(String key, String value){
        // 加锁 写锁,也叫独占锁
        readWriteLock.writeLock().lock();
        try {
            // 写入
            System.out.println(Thread.currentThread().getName() + "线程  开始写入");
            map.put(key, value);
            System.out.println(Thread.currentThread().getName() + "线程  写入OK");
        }catch (Exception e){
            e.printStackTrace();
        }finally {
            readWriteLock.writeLock().unlock();
        }

    }

    public String get(String key){
        // 加锁 读锁 也叫共享锁
        readWriteLock.readLock().lock();
        String s = "";
        try {
            // 读取
            System.out.println(Thread.currentThread().getName() + "线程  开始读取");
            s = map.get(key);
            System.out.println(Thread.currentThread().getName() + "线程  读取OK");
        }catch (Exception e){
            e.printStackTrace();
        }finally {
            readWriteLock.readLock().unlock();
        }
        return s;
    }
}

运行结果:

[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-JbOvVFDK-1648735892696)(C:\Users\七七\AppData\Roaming\Typora\typora-user-images\image-20220309211721162.png)]

在整个过程没有在出现错乱的情况,对于读取,我们运行多个线程同时读取,

对于写入, 我们一个线程一个线程的写入,这样就不会造成数据不一致问题

10、阻塞队列 BlockingQueue

BlockingQueue

阻塞

队列

FIFO 先进先出

写入:如果队列满了,就必须阻塞等待

读取:如果队列是空的,必须阻塞等待生产

BlockingQueue

blockingQueue 是 Collection 的一个子类

什么情况下我们会使用 阻塞队列呢?

graph TB;
Collection --> Set;
Collection --> Queue;
Collection --> List;
Queue --> Deque;
Queue --> BlockingQueue;
Queue --> AbstractQueue;
BlockingQueue --> LinkedBlockingQueue;
BlockingQueue --> ArrayBlockingQueue;

整个阻塞队列的家族如下:Queue以下实现的有Deque(双端队列)、AbstractQueue(非阻塞队列)、BlockingQueue(阻塞队列)

BlockingQueue 以下有Link 链表实现的阻塞队列、也有Array数组实现的阻塞队列

如何使用阻塞队列呢?

new ArrayBlockingQueue<>(); LinkedBlockingQueue<>();


BlockingQueue四组API

方式抛出异常不会抛出异常,有返回值阻塞等待超时等待
添加add()offer()put()offer(timenum,timeUnit)
移除remove()poll()take()poll(timenum,timeUnit)
判断队列首element()peek()
package com.hou.queue;

import java.util.concurrent.ArrayBlockingQueue;
import java.util.concurrent.TimeUnit;

public class BlockingQueueTest {
    public static void main(String[] args) throws InterruptedException {
        test04();
    }

    // 抛出异常
    public static void test01(){
        // 需要初始化队列的大小
        ArrayBlockingQueue<String> blockingQueue = new ArrayBlockingQueue<String>(3);

        System.out.println(blockingQueue.add("a"));
        System.out.println(blockingQueue.add("b"));
        System.out.println(blockingQueue.add("c"));
        // 抛出异常 java.lang.IllegalStateException   Queue null
        // System.out.println(blockingQueue.add("d"));

        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 test02(){
        ArrayBlockingQueue<String> blockingQueue = new ArrayBlockingQueue<>(3);

        System.out.println(blockingQueue.offer("a"));
        System.out.println(blockingQueue.offer("b"));
        System.out.println(blockingQueue.offer("c"));
        // 添加,一个不能添加的元素,使用offer只会返回false,不会抛出异常
        // System.out.println(blockingQueue.offer("d"));

        System.out.println(blockingQueue.poll());
        System.out.println(blockingQueue.poll());
        System.out.println(blockingQueue.poll());
        // 弹出 如果没有元素,只会返回null,不会抛出异常
        // System.out.println(blockingQueue.poll());
    }

    // 等待,一直阻塞   阻塞等待
    public static void test03() throws InterruptedException {
        ArrayBlockingQueue<String> 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 test04() throws InterruptedException {
        ArrayBlockingQueue<String> 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("开始等待");
        System.out.println(blockingQueue.offer("d",2, TimeUnit.SECONDS)); // 超时2s,等待如果超时2s就结束等待
        System.out.println("结束等待");


        System.out.println("========取值========");
        System.out.println(blockingQueue.poll());
        System.out.println(blockingQueue.poll());
        System.out.println(blockingQueue.poll());
        System.out.println("开始等待");
        System.out.println(blockingQueue.poll(2,TimeUnit.SECONDS));//超时等待2s
        System.out.println("结束等待");

    }
}

同步队列 SynchronousQueue

同步队列 没有容量,也可以视为容量为 1 的队列

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

put 方法 和 take 方法

SynchronousQueue 和其他的 BlockingQueue 不一样,它不存储元素

put 了一个元素,就必须从里面先 take 出来,否则不能再 put 进去值

并且SynchronousQueue 的take是使用了 lock锁保证线程安全的

package com.hou.queue;

import java.util.concurrent.BlockingQueue;
import java.util.concurrent.SynchronousQueue;
import java.util.concurrent.TimeUnit;

/**
 * 同步队列
 * 同步队列 没有容量 ,也可以视为容量为1的队列
 * SynchronousQueue 和其他的 BlockingQueue 不一样
 * put 了一个元素,必须从里面先 take 取出来,否则不能在put进去值
 */
public class SynchronousQueueTest {
    public static void main(String[] args) {
        BlockingQueue<String> synchronousQueue = new SynchronousQueue<>(); // 同步队列

        new Thread(()->{
            try {
                System.out.println(Thread.currentThread().getName() + "put  1");
                synchronousQueue.put("1");
                System.out.println(Thread.currentThread().getName() + "put  2");
                synchronousQueue.put("2");
                System.out.println(Thread.currentThread().getName() + "put  3");
                synchronousQueue.put("3");
            } catch (InterruptedException e) {
                e.printStackTrace();
            }
        },"T1").start();

        new Thread(()->{
            try {
                TimeUnit.SECONDS.sleep(3);
                System.out.println(Thread.currentThread().getName() + "  " + 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();
    }
}

11、线程池(重点)

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

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

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

池化技术:事先准备好一些资源,有人要用,就来拿,用完归还,以此来提高效率

线程池的好处:

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

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

  • ExecutorService threadPool = Executors.newSingleThreadExecutor() 单个线程
  • ExecutorService threadPool2 = Executors.newFixedThreadPool(5) 创建一个固定的线程池大小
  • ExecuttorService threadPool3 = Executors.newCachedThraedPool() 可伸缩的

// 工具类 Executors 三大方法

package com.hou.pool;

import java.util.concurrent.ExecutorService;
import java.util.concurrent.Executors;

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

        // 线程池用完必须关闭
        try {

            for (int i = 1; i <= 100; i++) {
                // 通过线程池创建线程
                threadPool2.execute(()->{
                    System.out.println(Thread.currentThread().getName() + "  ok");
                });
            }
        }catch (Exception e){
            e.printStackTrace();
        }finally {
            threadPool.shutdown(); // 关闭
        }
    }
}

源码分析

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

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.corePoolSize = corePoolSize;
        this.maximumPoolSize = maximumPoolSize;
        this.workQueue = workQueue;
        this.keepAliveTime = unit.toNanos(keepAliveTime);
        this.threadFactory = threadFactory;
        this.handler = handler;
    }

阿里巴巴的Java操作手册中明确说明:对于 Integer.MAX_VALUE初始值较大,所以一般情况我们要使用底层的ThreadPoolExecutor来创建线程池

[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-Ad9CxItY-1648735892697)(C:\Users\17,\AppData\Roaming\Typora\typora-user-images\image-20220317220453682.png)]

OOM 溢出 ,内存溢出

[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-tQcNXQi5-1648735892709)(C:\Users\17,\AppData\Roaming\Typora\typora-user-images\image-20220318000608755.png)]

package com.hou.pool;

import java.util.concurrent.Executors;
import java.util.concurrent.LinkedBlockingQueue;
import java.util.concurrent.ThreadPoolExecutor;
import java.util.concurrent.TimeUnit;

// 使用原生的 ThreadPoolExecutor  实现银行办理业务
public class Demo2 {
    public static void main(String[] args) {
    	// 七大参数
        ThreadPoolExecutor threadPool = new ThreadPoolExecutor(
                2, // 核心线程大小
                5, // 最大的线程池大小
                3, // 超时了没有人调用就会释放
                TimeUnit.SECONDS, // 超时单位
                new LinkedBlockingQueue<>(3), // 阻塞队列
                Executors.defaultThreadFactory(), // 线程工厂  创建线程的,一般不用动
                new ThreadPoolExecutor.AbortPolicy());// 拒绝策略,
  (// ThreadPoolExecutor.AbortPolicy()  功能: 银行满了,还有人进来,不处理这个人的,抛出异常)

        try {
            // 最大承载  队列(Deque) + max 值
            // 超过异常 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();
        }
    }
}

[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-W3ZvzmL4-1648735892711)(C:\Users\17,\AppData\Roaming\Typora\typora-user-images\image-20220318001242899.png)]

package com.hou.pool;

import java.util.concurrent.Executors;
import java.util.concurrent.LinkedBlockingQueue;
import java.util.concurrent.ThreadPoolExecutor;
import java.util.concurrent.TimeUnit;

// 使用原生的 ThreadPoolExecutor  实现银行办理业务
public class Demo2 {
    public static void main(String[] args) {
        ThreadPoolExecutor threadPool = new ThreadPoolExecutor(
                2,
                5,
                3,
                TimeUnit.SECONDS,
                new LinkedBlockingQueue<>(3),
                Executors.defaultThreadFactory(),
                // new ThreadPoolExecutor.AbortPolicy() // 银行满了,还有人进来,不处理这个人的,抛出异常
                // new ThreadPoolExecutor.CallerRunsPolicy() // 哪里来的去哪里 main线程进行处理
                // new ThreadPoolExecutor.DiscardOldestPolicy() // 队列满了,尝试去和最早的进程竞争,不会抛出异常
                new ThreadPoolExecutor.DiscardPolicy()  // 队列满了,丢掉,不会抛出异常
                );


        try {
            // 最大承载  队列(Deque) + max 值
            // 超过异常 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();
        }
    }
}

  • new ThreadPoolExecutor.AbortPolicy() :该拒绝策略为:银行满了,还有人进来,不处理这个人,抛出异常

    超出最大承载,就会抛出异常,最大承载:队列容量大小 + maxPoolSize

  • new ThreadPoolExecutor.CallerRunsPolicy() :该拒绝策略为:哪里来的去哪里 main线程进行处理

  • new ThreadPoolExecutor.DiscardPolicy() :该拒绝策略为:队列满了,丢掉,不会抛出异常

  • new ThreadPoolExecutor.DiscardOldestPolicy() :该拒绝策略为:队列满了,尝试去和最早的进程竞争,不会抛出异常

小结和拓展

**如何去设置线程池的最大大小? ** CPU密集型 和 IO密集型

  • CPU密集型:电脑的核数是几核就选择几,选择maximunPoolSize的大小
    • 我们可以使用代码来获取逻辑处理器的数量
    • [外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-A4e04p0a-1648735892713)(C:\Users\17,\AppData\Roaming\Typora\typora-user-images\image-20220318141450878.png)]
  • IO密集型:在程序中有15个大型任务,IO十分占资源,I/O密集型就是判断我们程序中十分耗I/O的线程数量,大约是最大I/O数的一倍到俩倍之间

12、四大函数式接口(必须掌握)

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

@FunctionalInterface
public interface Runnable {
    public abstract void run();
}
// 超级多的@FunctionalInterface
// 简化编程模型,在新版本的框架底层大量应用
// foreach()的参数也是一个函数式接口,消费者类的函数式接口

[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-tG6ZO1wu-1648735892714)(C:\Users\17,\AppData\Roaming\Typora\typora-user-images\image-20220318145840221.png)]

函数型接口可以使用 lambda表达式

代码测试:

[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-nY4OqaTU-1648735892715)(C:\Users\17,\AppData\Roaming\Typora\typora-user-images\image-20220318150307852.png)]

package com.hou.function;

import java.util.function.Function;

/**
 * Function 函数型接口
 */
public class Demo1 {
    public static void main(String[] args) {
        Function<String,String> function = new Function<String, String>() {
            @Override
            public String apply(String s) {
                return s;
            }
        };

        // 使用 lambda 表达式
        Function<String,String> function1 = (str)->{return str;};

        System.out.println(function1.apply("string"));
    }
}

[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-OEDpmt1I-1648735892716)(C:\Users\17,\AppData\Roaming\Typora\typora-user-images\image-20220318151437425.png)]

package com.hou.function;

import java.util.function.Predicate;

/**
 * Predicate 断定型接口:有一个输入参数,返回值只能是 布尔值
 */
public class Demo2 {
    public static void main(String[] args) {
        // 判断字符串是否为空
        Predicate<String> predicate = new Predicate<String>() {
            @Override
            public boolean test(String s) {
                return s.isEmpty();
            }
        };

        // 使用lambda表达式
        Predicate<String> predicate1 = (str)->{return str.isEmpty();};

        System.out.println(predicate1.test("string"));
        System.out.println(predicate1.test(""));

    }
}

[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-7QPnqerR-1648735892717)(C:\Users\17,\AppData\Roaming\Typora\typora-user-images\image-20220318153156970.png)]

package com.hou.function;

import java.util.function.Consumer;

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

        // 使用lambda表达式
        Consumer consumer1 = (str) ->{
            System.out.println(str);
        };

        consumer1.accept("abc");
    }
}

[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-wBByzmFQ-1648735892717)(C:\Users\17,\AppData\Roaming\Typora\typora-user-images\image-20220318153854608.png)]

package com.hou.function;

import java.util.function.Supplier;

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

        // 使用 lambda 表达式
        Supplier<Integer> supplier1 = ()->{
            return 1024;
        };

        System.out.println(supplier.get());
        System.out.println(supplier1.get());

    }
}

13、Stream流式计算

存储+计算

存储:集合、MySQL

计算:流式计算

=== 链式编程 ===

package com.hou.stream;

import java.util.Arrays;
import java.util.List;

public class TestStream {
    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);

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

        // 计算交给 stream流
        // 链式编程
        list.stream()
                .filter((u)->{return u.getId()%2==0;})
                .filter((u)->{return u.getAge()>23;})
                .map((u)->{return u.getName().toUpperCase();})
                .sorted((uu1,uu2)->{return uu2.compareTo(uu1);})
                .limit(1)
                .forEach(System.out::println);
    }
}

14、ForkJoin

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

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

[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-imUY7UBY-1648735892718)(C:\Users\17,\AppData\Roaming\Typora\typora-user-images\image-20220321172034139.png)]

实际原理是:双端队列 ! 从上面和下面都可以去拿到任务进行执行

[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-zB33EoXN-1648735892718)(C:\Users\17,\AppData\Roaming\Typora\typora-user-images\image-20220321173043398.png)]

  • 1、通过ForkJoinPool来执行

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

[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-YSN92mLa-1648735892719)(C:\Users\17,\AppData\Roaming\Typora\typora-user-images\image-20220321173621799.png)]

  • 3、计算类要去继承ForkJoinTask

ForkJoin的计算类

package com.hou.forkjoin;

import java.util.concurrent.RecursiveTask;

public class ForkJoinDemo extends RecursiveTask<Long> {

    private long star;
    private long end;

    // 临界值
    private long temp = 10000L;

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


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

            return taskSum;
        }
    }
}

测试类

package com.hou.forkjoin;

import java.util.concurrent.ExecutionException;
import java.util.concurrent.ForkJoinPool;
import java.util.concurrent.ForkJoinTask;
import java.util.stream.LongStream;

public class Test {
    public static void main(String[] args) throws ExecutionException, InterruptedException {
        test1(); // 34547
        test2(); // 371045
        test3(); // 5440
    }

    /**
     * 普通计算
     */
    public static void test1(){
        long star = System.currentTimeMillis();
        long sum = 0L;

        for (long i = 1L; i < 1000_0000_0000L; i++) {
            sum+=i;
        }

        long end = System.currentTimeMillis();
        System.out.println("时间:" + (end-star));
        System.out.println(sum);
    }


    /**
     * 使用 ForkJoin
     */
    public static void test2() throws ExecutionException, InterruptedException {
        long star = System.currentTimeMillis();
        ForkJoinPool forkJoinPool = new ForkJoinPool();
        ForkJoinTask<Long> task = new ForkJoinDemo(0L, 1000_0000_0000L);
        // forkJoinPool.execute(task); // 没有返回值  submit 有返回值
        ForkJoinTask<Long> submit = forkJoinPool.submit(task);
        Long sum = submit.get();
        System.out.println(sum);

        long end = System.currentTimeMillis();
        System.out.println("时间:" + (end-star));
    }


    /**
     * 使用 Stream 并行流
     */
    public static void test3(){
        long star = System.currentTimeMillis();

        // Stream 并行流
        long sum = LongStream.range(0L, 1000_0000_0000L).parallel().reduce(0, Long::sum);

        System.out.println(sum);
        long end = System.currentTimeMillis();
        System.out.println("时间:" + (end-star));
    }
}

.parallel().reduce(0,Long::sum)使用一个并行流去计算整个计算,提高效率

[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-n2Z4H8nn-1648735892719)(C:\Users\17,\AppData\Roaming\Typora\typora-user-images\image-20220321182646652.png)]

reduce()方法的优点

[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-RdbX4QDi-1648735892720)(C:\Users\17,\AppData\Roaming\Typora\typora-user-images\image-20220321183716216.png)]

15、异步回调

其实就是前端 --> 发送ajax异步请求给后端

[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-sGRtSKTC-1648735892720)(C:\Users\17,\AppData\Roaming\Typora\typora-user-images\image-20220321184148296.png)]

但是我们平时都是使用CompletableFuture

(1)没有返回值的 runAsync 异步回调

package com.hou.future;

import java.util.concurrent.CompletableFuture;
import java.util.concurrent.ExecutionException;
import java.util.concurrent.TimeUnit;

/**
 * CompletableFuture   异步回调
 * runAsync  没有返回值
 * supplyAsync 有返回值
 */
public class TestCompletableFuture {
    public static void main(String[] args) throws ExecutionException, InterruptedException {
        testRunFuture();
        testSupplyAsync();
    }

    // 没有返回值的异步回调
    public static void testRunFuture() throws ExecutionException, InterruptedException {
        // 发起一个请求
        CompletableFuture<Void> future = CompletableFuture.runAsync(() -> {
            // 发起一个异步任务
            try {
                TimeUnit.SECONDS.sleep(2);
            } catch (InterruptedException e) {
                e.printStackTrace();
            }
        });
        // 输出执行结果
        System.out.println(future.get()); // 获取执行结果
    }

    // 有返回值的异步回调
    public static void testSupplyAsync() throws ExecutionException, InterruptedException {
        CompletableFuture<Integer> future = CompletableFuture.supplyAsync(() -> {
            System.out.println(Thread.currentThread().getName());

            try {
                TimeUnit.SECONDS.sleep(2);
                // int i = 1/0;
            } catch (InterruptedException e) {
                e.printStackTrace();
            }

            return 1024;
        });

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

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

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

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

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

16、JMM

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

1、保证可见性

2、不保证原子性 原子性:不可分割,即一个操作或者多个操作,要么全部执行并且不被打断,要么就都不执行

3、禁止指令重排

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

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

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

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

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

八种操作(每个操作都为原子操作)

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

[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-zNG4CBVb-1648735892721)(C:\Users\17,\AppData\Roaming\Typora\typora-user-images\image-20220322183805608.png)]

[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-D9oANjis-1648735892722)(C:\Users\17,\AppData\Roaming\Typora\typora-user-images\image-20220322184220910.png)]

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操作之前,必须把此变量同步回主内存

[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-fVLBqkkx-1648735892723)(C:\Users\17,\AppData\Roaming\Typora\typora-user-images\image-20220322185109771.png)]

遇到问题:程序不知道主存中的值已经被修改过了!

17、Volatile

1、保证可见性

2、不保证原子性

3、禁止指令重排

package com.hou.jmm;

import java.util.concurrent.TimeUnit;

public class JMMDemo01 {

    // 如果不加volatile 程序会死循环
    // 加了volatile 是可以保证可见性的
    private volatile static Integer number = 0;

    public static void main(String[] args) {
        // main 线程
        // 子线程 1
        new Thread(()->{
            while (number == 0){

            }
        }).start();

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

        // 子线程 2
        new Thread(()->{
            while (number == 0){

            }
        }).start();

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

        number = 1;
        System.out.println(number);

    }
}

原子性:不可分割

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

package com.hou.jmm;

/**
 * 不保证原子性
 * number <= 2W
 */
public class JMMDemo02 {

    private static volatile int number = 0;

    public static void add(){
        number++;
        // ++ 不是一个原子性操作,是俩个~三个操作
    }

    public static void main(String[] args) {
        // 理论上 number = 2w
        for (int i = 1; i < 20; i++) {
            new Thread(()->{
                for (int j = 0; j < 1000; j++) {
                    add();
                }
            }).start();

        }

        // Thread.activeCount()  运行中的线程数量
        while (Thread.activeCount() > 2){
            // main gc
            Thread.yield(); // 礼让
        }

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

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

使用 java 反编译 javap

[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-nBKZeVLv-1648735892724)(C:\Users\17,\AppData\Roaming\Typora\typora-user-images\image-20220322192841596.png)]

解决方法:使用JUC下的原子包下的class

​ [外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-NpSnhGpl-1648735892724)(C:\Users\17,\AppData\Roaming\Typora\typora-user-images\image-20220322193122422.png)]

代码如下:

package com.hou.jmm;

import java.util.concurrent.atomic.AtomicInteger;

public class VDemo01 {

    private static volatile AtomicInteger number = new AtomicInteger();

    public static void add(){
        number.incrementAndGet(); // 底层是CAS  保证的原子性
    }

    public static void main(String[] args) {
        // 理论上 number = 2w

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

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

[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-ER8dt3Kn-1648735892725)(C:\Users\17,\AppData\Roaming\Typora\typora-user-images\image-20220322194257231.png)]

Unsafe 类是一个很特殊的存在

什么是指令重排?

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

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

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

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
x = ay = b
b = 1a = 2

可能在线程A中会出现,先执行 b=1 ,然后在执行 x= a

在线程B中会出现,先执行 a = 2,然后执行 y = b

那么就有可能结果如下:x = 2; y =1

volatile可以避免指令重排

volatile 中会加一道内存的屏障,这个内存屏可以保证在这个内存屏障中的指令顺序

内存屏障:CPU指令,使用:

  1. 保证特定操作的执行顺序
  2. 可以保证某些变量的内存可见性(利用这些特性,就可以保证volatile实现的可见性)

[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-K0L9Fbf9-1648735892726)(C:\Users\17,\AppData\Roaming\Typora\typora-user-images\image-20220322200019938.png)]

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

面试官:那么你知道在哪里用这个内存屏障用的最多呢? 单例模式

18、玩转单例模式

饿汉式、DCL懒汉式

package com.hou.single;

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

在多线程并发的情况下,单例是有问题的

package com.hou.single;

public class LazyMan {

    private LazyMan(){
        System.out.println(Thread.currentThread().getName() + " ok");
    }

    private static LazyMan lazyMan;

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

        return lazyMan;
    }

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

[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-ACTLQSKO-1648735892727)(C:\Users\17,\AppData\Roaming\Typora\typora-user-images\image-20220326232216872.png)]

所以需要加锁

package com.hou.single;

public class LazyMan {

    private LazyMan(){
        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、把这个对象指向这个空间
                     * 		可能会由于指令重排的原因,造成其他线程进入之后没有完成构造
                     *		所以使用 volatile 保证不会被指令重排
                     */
                }
            }
        }

        return lazyMan;
    }

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

采用静态内存类的方法

package com.hou.single;

// 静态内部类
public class Holder {

    private Holder(){

    }

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

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

但是在这种情况下单例还是可以被破坏,因为Java有强大的反射

package com.hou.single;

import java.lang.reflect.Constructor;

public class LazyMan1 {

    private LazyMan1(){
        System.out.println(Thread.currentThread().getName() + "  ok");
    }

    private volatile static LazyMan1 lazyMan1;

    // 双重检测锁模式的 懒汉式单例  DCL单例
    public static LazyMan1 getInstance(){
        if (lazyMan1==null){
            synchronized (LazyMan1.class){
                if (lazyMan1==null){
                    lazyMan1 = new LazyMan1(); // 不是一个原子性操作
                }
            }
        }

        return  lazyMan1;
    }


    // 反射
    public static void main(String[] args) throws Exception {
        LazyMan1 instance = LazyMan1.getInstance();
        // 获取空参构造器
        Constructor<LazyMan1> declaredConstructor = LazyMan1.class.getDeclaredConstructor(null);
        declaredConstructor.setAccessible(true);
        LazyMan1 instance1 = declaredConstructor.newInstance();

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

    }
}

[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-YKXoLTJd-1648735892728)(C:\Users\17,\AppData\Roaming\Typora\typora-user-images\image-20220327210745194.png)]

这时发现我们的单例已经被破坏

解决方式:(在无参构造中再添加一把锁)

package com.hou.single;

import java.lang.reflect.Constructor;

public class LazyMan1 {

    private LazyMan1(){
        synchronized (LazyMan1.class){
            if (lazyMan1!=null){
                throw new RuntimeException("不要试图使用反射破坏异常");
            }
        }
    }

    private volatile static LazyMan1 lazyMan1;

    // 双重检测锁模式的 懒汉式单例  DCL单例
    public static LazyMan1 getInstance(){
        if (lazyMan1==null){
            synchronized (LazyMan1.class){
                if (lazyMan1==null){
                    lazyMan1 = new LazyMan1(); // 不是一个原子性操作
                }
            }
        }

        return  lazyMan1;
    }


    // 反射
    public static void main(String[] args) throws Exception {
        LazyMan1 instance = LazyMan1.getInstance();
        // 获取空参构造器
        Constructor<LazyMan1> declaredConstructor = LazyMan1.class.getDeclaredConstructor(null);
        declaredConstructor.setAccessible(true);
        LazyMan1 instance1 = declaredConstructor.newInstance();

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

    }
}

[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-8MNvG2rS-1648735892729)(C:\Users\17,\AppData\Roaming\Typora\typora-user-images\image-20220327211057388.png)]

这时发现我们的单例已经避免了这种反射的破坏

但是我们换一个反射方式破坏还是有问题的

这次我们不使用getInstance()方式创建对象

package com.hou.single;

import java.lang.reflect.Constructor;

public class LazyMan1 {

    private LazyMan1(){
        synchronized (LazyMan1.class){
            if (lazyMan1!=null){
                throw new RuntimeException("不要试图使用反射破坏异常");
            }
        }
    }

    private volatile static LazyMan1 lazyMan1;

    // 双重检测锁模式的 懒汉式单例  DCL单例
    public static LazyMan1 getInstance(){
        if (lazyMan1==null){
            synchronized (LazyMan1.class){
                if (lazyMan1==null){
                    lazyMan1 = new LazyMan1(); // 不是一个原子性操作
                }
            }
        }

        return  lazyMan1;
    }


    // 反射
    public static void main(String[] args) throws Exception {
        // LazyMan1 instance = LazyMan1.getInstance();
        // 获取空参构造器
        Constructor<LazyMan1> declaredConstructor = LazyMan1.class.getDeclaredConstructor(null);
        declaredConstructor.setAccessible(true);
        LazyMan1 instance = declaredConstructor.newInstance();
        LazyMan1 instance1 = declaredConstructor.newInstance();

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

    }
}

[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-Zg93xzuh-1648735892730)(C:\Users\17,\AppData\Roaming\Typora\typora-user-images\image-20220327211548187.png)]

发现我们的单例模式又被破坏了

解决方式: (加一个加密变量)

package com.hou.single;

import java.lang.reflect.Constructor;

public class LazyMan1 {

    private static boolean flag = false;

    private LazyMan1(){
        synchronized (LazyMan1.class){
            if (flag==false){
                flag=true;
            }else {
                throw new RuntimeException("不要试图使用反射破坏异常");
            }
        }
    }

    private volatile static LazyMan1 lazyMan1;

    // 双重检测锁模式的 懒汉式单例  DCL单例
    public static LazyMan1 getInstance(){
        if (lazyMan1==null){
            synchronized (LazyMan1.class){
                if (lazyMan1==null){
                    lazyMan1 = new LazyMan1(); // 不是一个原子性操作
                }
            }
        }

        return  lazyMan1;
    }


    // 反射
    public static void main(String[] args) throws Exception {
        // LazyMan1 instance = LazyMan1.getInstance();
        // 获取空参构造器
        Constructor<LazyMan1> declaredConstructor = LazyMan1.class.getDeclaredConstructor(null);
        declaredConstructor.setAccessible(true);
        LazyMan1 instance = declaredConstructor.newInstance();
        LazyMan1 instance1 = declaredConstructor.newInstance();

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

    }
}

[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-zvxRkPqm-1648735892731)(C:\Users\17,\AppData\Roaming\Typora\typora-user-images\image-20220327211832280.png)]

这时发现我们的单例没有被破坏

但是无论怎么加密都是可以被解密的

如果我们的变量被知道了

package com.hou.single;

import java.lang.reflect.Constructor;
import java.lang.reflect.Field;

public class LazyMan1 {

    private static boolean flag = false;

    private LazyMan1(){
        synchronized (LazyMan1.class){
            if (flag==false){
                flag=true;
            }else {
                throw new RuntimeException("不要试图使用反射破坏异常");
            }
        }
    }

    private volatile static LazyMan1 lazyMan1;

    // 双重检测锁模式的 懒汉式单例  DCL单例
    public static LazyMan1 getInstance(){
        if (lazyMan1==null){
            synchronized (LazyMan1.class){
                if (lazyMan1==null){
                    lazyMan1 = new LazyMan1(); // 不是一个原子性操作
                }
            }
        }

        return  lazyMan1;
    }


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

        Field flag = LazyMan1.class.getDeclaredField("flag");
        flag.setAccessible(true);

        // 获取空参构造器
        Constructor<LazyMan1> declaredConstructor = LazyMan1.class.getDeclaredConstructor(null);
        declaredConstructor.setAccessible(true);
        LazyMan1 instance = declaredConstructor.newInstance();

        flag.set(instance,false);

        LazyMan1 instance1 = declaredConstructor.newInstance();

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

    }
}

[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-9ZEI54SS-1648735892731)(C:\Users\17,\AppData\Roaming\Typora\typora-user-images\image-20220327212327187.png)]

这时发现我们的单例又被破坏了

怎们解决呢?

通过源码分析

[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-QeIPAREi-1648735892732)(C:\Users\17,\AppData\Roaming\Typora\typora-user-images\image-20220327212744728.png)]

发现枚举类型不能被破坏

enum 是一个什么? 本身也是一个Class类

从编译之后的代码中发现类中有一个无参构造

[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-fmjoYZIg-1648735892732)(C:\Users\17,\AppData\Roaming\Typora\typora-user-images\image-20220327213553625.png)]

所以我们使用这个无参构造

package com.hou.single;

import java.lang.reflect.Constructor;
import java.lang.reflect.InvocationTargetException;

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

    INSTANCE;

    public EnumSingle getInstance(){
        return INSTANCE;
    }


}


class Test{

    public static void main(String[] args) throws InvocationTargetException, InstantiationException, IllegalAccessException, NoSuchMethodException {
        EnumSingle instance1 = EnumSingle.INSTANCE;
        Constructor<EnumSingle> declaredConstructor = EnumSingle.class.getDeclaredConstructor();
        declaredConstructor.setAccessible(true);
        EnumSingle instance2 = declaredConstructor.newInstance();

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

[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-xsD9G6rV-1648735892733)(C:\Users\17,\AppData\Roaming\Typora\typora-user-images\image-20220327213853007.png)]

这时发现出现的异常尽然是 没有无参构造方法 而不是newInstance()方法的源码中所说的异常

这时再用java反编译查看编译后的class文件

[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-7qHUGfi9-1648735892734)(C:\Users\17,\AppData\Roaming\Typora\typora-user-images\image-20220327214558275.png)]

发现这里也是有一个无参的构造器

最后我们使用专业工具反编译

[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-Sgz1XS2e-1648735892734)(C:\Users\17,\AppData\Roaming\Typora\typora-user-images\image-20220327220944021.png)]

发现这里其实是一个有参的构造器

最后我们修改代码

package com.hou.single;

import java.lang.reflect.Constructor;
import java.lang.reflect.InvocationTargetException;

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

    INSTANCE;

    public EnumSingle getInstance(){
        return INSTANCE;
    }


}


class Test{

    public static void main(String[] args) throws InvocationTargetException, InstantiationException, IllegalAccessException, NoSuchMethodException {
        EnumSingle instance1 = EnumSingle.INSTANCE;
        Constructor<EnumSingle> declaredConstructor = EnumSingle.class.getDeclaredConstructor(String.class,int.class);
        declaredConstructor.setAccessible(true);
        EnumSingle instance2 = declaredConstructor.newInstance();

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

[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-35REykcI-1648735892735)(C:\Users\17,\AppData\Roaming\Typora\typora-user-images\image-20220327221124193.png)]

发现结果抛出的异常和我们的源码抛出的异常一致

19、深入理解CAS

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

AtomicInteger 原子类

package com.hou.cas;

import java.util.concurrent.atomic.AtomicInteger;

public class CASDemo {

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

        // boolean compareAndSet(int expectedValue, int newValue)
        // 期望值,更新值
        // 如果实际值 和 期望值相同就更新   不相投,就不更新
        System.out.println(atomicInteger.compareAndSet(2020, 2021));
        System.out.println(atomicInteger.get());


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

[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-rKr3fYQj-1648735892735)(C:\Users\17,\AppData\Roaming\Typora\typora-user-images\image-20220331182821309.png)]

[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-BeX6aX4j-1648735892736)(C:\Users\17,\AppData\Roaming\Typora\typora-user-images\image-20220331183430361.png)]

[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-lzi0YeSG-1648735892737)(C:\Users\17,\AppData\Roaming\Typora\typora-user-images\image-20220331184033546.png)]

总结:

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

缺点:

  • 使用的是自旋锁,所以循环会耗时
  • 一次性只能保证一个共享变量的原子性
  • 它会存在ABA问题

[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-u97IZmPk-1648735892737)(C:\Users\17,\AppData\Roaming\Typora\typora-user-images\image-20220331191045712.png)]

测试:

线程1:期望值是1,要变成2

线程2:俩个操作:

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

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

package com.hou.cas;

import java.util.concurrent.atomic.AtomicInteger;

public class CASDemo1 {
    // CAS  ABA 问题
    public static void main(String[] args) {
        AtomicInteger atomicInteger = new AtomicInteger(1);

        // 对于我们平是写的SQL: 采用 乐观锁
        // 假设
        System.out.println("=========捣乱的线程===========");
        System.out.println(atomicInteger.compareAndSet(1,3));
        System.out.println(atomicInteger.get());
        System.out.println(atomicInteger.compareAndSet(3,1));
        System.out.println(atomicInteger.get());

        // 这种情况是我们不想存在的,被操作过都不知道
        System.out.println("=========期望的线程==============");
        System.out.println(atomicInteger.compareAndSet(1,2));
        System.out.println(atomicInteger.get());

    }

}

20、原子引用

带版本号的 原子操作!

Integer 使用了对象缓存机制,默认范围是 -128~127,推荐使用静态工厂方法valueOf 获取对象实例,

而不是new,因为valueOf 使用cache(缓存),而new一定会创建新的对象分配新的内存空间

[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-zo7DsVbD-1648735892738)(C:\Users\17,\AppData\Roaming\Typora\typora-user-images\image-20220331192607497.png)]

所以如果遇到,使用大于128的时候,使用原子引用的时候,如果超过了这个值,那么就不会进行版本上升

[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-OmhTsAoQ-1648735892739)(C:\Users\17,\AppData\Roaming\Typora\typora-user-images\image-20220331193903395.png)]

修改使用小于128的时候

[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-Jd2uqINh-1648735892739)(C:\Users\17,\AppData\Roaming\Typora\typora-user-images\image-20220331194932527.png)]

这时发现我们的版本号上升了

package com.hou.cas;

import java.util.concurrent.TimeUnit;
import java.util.concurrent.atomic.AtomicStampedReference;

public class CASDemo2 {
    public static void main(String[] args) {
        // 正常情况不会使用 Integer包装类,一般使用的是一个个对象
        AtomicStampedReference<Integer> atomicStampedReference = new AtomicStampedReference<>(1, 1);

        new Thread(()->{
            int stamp = atomicStampedReference.getStamp();  // 获取版本号
            System.out.println("A=>" + stamp);

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

            // Version + 1
            atomicStampedReference.compareAndSet(1, 2,
                    atomicStampedReference.getStamp(), atomicStampedReference.getStamp() + 1);
            System.out.println("A2=>" + atomicStampedReference.getStamp());

            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("B=>" + stamp);

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

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

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

正常业务操作中,我们一般使用的是一个个对象,一般情况不会使用Integer

21、各种锁的理解


1、公平锁、非公平锁

  • 公平锁:非常公平,不允许插队的,必须先来后到

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

    public ReentrantLock() {
        sync = new NonfairSync();
    }
    

2、可重入锁

[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-dMJc2VIv-1648735892740)(C:\Users\17,\AppData\Roaming\Typora\typora-user-images\image-20220331204548727.png)]

package com.hou.lock;

public class Demo1 {
    public static void main(String[] args) {
        Phone phone = new Phone();

        new Thread(()->{
            phone.sms();
        },"A").start();

        new Thread(()->{
            phone.call();
        },"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");
    }
}

package com.hou.lock;

import java.util.concurrent.locks.Lock;
import java.util.concurrent.locks.ReentrantLock;

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

        new Thread(()->{
            phone2.call();
        },"B").start();
    }

}

class Phone2{
    // 细节:这个是俩吧锁,俩个钥匙
    Lock lock = new ReentrantLock();
    // lock锁必须配对,否则就会死锁在里面

    public void sms(){
        lock.lock();
        // lock.lock();

        try {
            System.out.println(Thread.currentThread().getName() + " => sms");
            call();
        }catch (Exception e){
            e.printStackTrace();
        }finally {
            lock.unlock();
            // lock.unlock();
        }

    }

    public void call(){
        lock.lock();

        try {
            System.out.println(Thread.currentThread().getName() + " => call");
        }catch (Exception e){
            e.printStackTrace();
        }finally {
            lock.unlock();
        }
    }
}

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

3、自旋锁

spinlock

[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-1yOah3kf-1648735892741)(C:\Users\17,\AppData\Roaming\Typora\typora-user-images\image-20220331213749149.png)]

设计自旋锁

package com.hou.lock;

import java.util.concurrent.TimeUnit;

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

        // 使用CAS实现自旋锁
        SpinlockDemo spinlockDemo = new SpinlockDemo();
        new Thread(()->{
            spinlockDemo.myLock();

            try {
                TimeUnit.SECONDS.sleep(3);
            }catch (Exception e){
                e.printStackTrace();
            }finally {
                spinlockDemo.unMyLock();
            }

        },"T1").start();

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

        new Thread(()->{
            spinlockDemo.myLock();

            try {
                TimeUnit.SECONDS.sleep(3);
            }catch (Exception e){
                e.printStackTrace();
            }finally {
                spinlockDemo.unMyLock();
            }
        },"T2").start();
    }
}

运行结果:

T2 进程必须等待 T1 进程unlock后,才能Unlock,在这之前进行自旋等待。

[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-fmiCYxEY-1648735892741)(C:\Users\17,\AppData\Roaming\Typora\typora-user-images\image-20220331214913093.png)]

4、死锁

[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-AmQPVyPd-1648735892742)(C:\Users\17,\AppData\Roaming\Typora\typora-user-images\image-20220331215134453.png)]

死锁测试,怎么排除死锁

package com.hou.lock;

import java.util.concurrent.TimeUnit;

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

        new Thread(new MyThread(lockA,lockB),"T1").start();
        new Thread(new MyThread(lockB,lockA),"T2").start();
    }
}

class MyThread implements Runnable{

    private String lockA;
    private String lockB;

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

    @Override
    public void run() {
        synchronized (lockA){
            System.out.println(Thread.currentThread().getName() + "lock" + lockA + "===>get" + lockB);

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

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

运行结果:死锁

[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-lKHPKGzu-1648735892743)(C:\Users\17,\AppData\Roaming\Typora\typora-user-images\image-20220331215641594.png)]

死锁排查

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

命令 jps -l

[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-v2dWbvwW-1648735892744)(C:\Users\17,\AppData\Roaming\Typora\typora-user-images\image-20220331215920572.png)]

2、 使用 jstack 进程号 找到死锁信息

一般情况信息在最后

[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-SBWJsgPc-1648735892747)(C:\Users\17,\AppData\Roaming\Typora\typora-user-images\image-20220331220631976.png)]

[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-tfuZzUEL-1648735892747)(C:\Users\17,\AppData\Roaming\Typora\typora-user-images\image-20220331220806145.png)]

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

  • 日志

  • 堆栈信息

    },“B”).start();
    }

}

class Phone2{
// 细节:这个是俩吧锁,俩个钥匙
Lock lock = new ReentrantLock();
// lock锁必须配对,否则就会死锁在里面

public void sms(){
    lock.lock();
    // lock.lock();

    try {
        System.out.println(Thread.currentThread().getName() + " => sms");
        call();
    }catch (Exception e){
        e.printStackTrace();
    }finally {
        lock.unlock();
        // lock.unlock();
    }

}

public void call(){
    lock.lock();

    try {
        System.out.println(Thread.currentThread().getName() + " => call");
    }catch (Exception e){
        e.printStackTrace();
    }finally {
        lock.unlock();
    }
}

}


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



3、自旋锁

spinlock

[外链图片转存中...(img-1yOah3kf-1648735892741)]



设计自旋锁

```java
package com.hou.lock;

import java.util.concurrent.TimeUnit;

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

        // 使用CAS实现自旋锁
        SpinlockDemo spinlockDemo = new SpinlockDemo();
        new Thread(()->{
            spinlockDemo.myLock();

            try {
                TimeUnit.SECONDS.sleep(3);
            }catch (Exception e){
                e.printStackTrace();
            }finally {
                spinlockDemo.unMyLock();
            }

        },"T1").start();

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

        new Thread(()->{
            spinlockDemo.myLock();

            try {
                TimeUnit.SECONDS.sleep(3);
            }catch (Exception e){
                e.printStackTrace();
            }finally {
                spinlockDemo.unMyLock();
            }
        },"T2").start();
    }
}

运行结果:

T2 进程必须等待 T1 进程unlock后,才能Unlock,在这之前进行自旋等待。

[外链图片转存中…(img-fmiCYxEY-1648735892741)]

4、死锁

[外链图片转存中…(img-AmQPVyPd-1648735892742)]

死锁测试,怎么排除死锁

package com.hou.lock;

import java.util.concurrent.TimeUnit;

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

        new Thread(new MyThread(lockA,lockB),"T1").start();
        new Thread(new MyThread(lockB,lockA),"T2").start();
    }
}

class MyThread implements Runnable{

    private String lockA;
    private String lockB;

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

    @Override
    public void run() {
        synchronized (lockA){
            System.out.println(Thread.currentThread().getName() + "lock" + lockA + "===>get" + lockB);

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

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

运行结果:死锁

[外链图片转存中…(img-lKHPKGzu-1648735892743)]

死锁排查

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

命令 jps -l

[外链图片转存中…(img-v2dWbvwW-1648735892744)]

2、 使用 jstack 进程号 找到死锁信息

一般情况信息在最后

[外链图片转存中…(img-SBWJsgPc-1648735892747)]

[外链图片转存中…(img-tfuZzUEL-1648735892747)]

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

  • 日志
  • 堆栈信息
举报

相关推荐

0 条评论