Java之JUC编程

108 阅读13分钟

这是我参与「掘金日新计划 · 2 月更文挑战」的第 十 七 天

11、四大函数式接口

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

@FunctionalInterface
public interface Runnable {
    public abstract void run();
}
// foreach(消费者的函数式接口)

测试函数式(Function)接口: Function 函数型接口,有一个参数,一个返回类型

image-20221221220850710

import java.util.function.Function;

public class Demo01 {
    public static void main(String[] args) {
        /*Function<String, String> function = new Function<String, String>() {
            @Override
            public String apply(String str) {
                return str;
            }
        };*/
        Function<String, String> function = (str) -> { return str; };
​
        System.out.println(function.apply("abc"));
    }
}

测试断定型(Predicate)接口: 断定型接口: 有一个参数输入,返回值只能是 布尔值!

image-20221221220107324

import java.util.function.Predicate;

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 = String::isEmpty;
        System.out.println(predicate.test(""));
    }
}

Consumer 消费性接口

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

image-20221221221434328

import java.util.function.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("asd");
    }
}

Supplier 供给型接口

Supplier供给型接口, 没有参数,只有返回值

image-20221221222011616

import java.util.function.Supplier;

public class Demo04 {
    public static void main(String[] args) {
​
        /*Supplier<Integer> supplier = new Supplier<Integer>() {
            @Override
            public Integer get() {
                System.out.println("get()");
                return 1024;
            }
        };*/
        Supplier<Integer> supplier = () -> {return 1024;};
​
        System.out.println(supplier.get());
​
    }
}

12、Stream流式计算

  •     要求:现在有五个用户!筛选
    •     1、ID必须是偶数
    •     2、年龄必须大于23岁
    •     3、用户名转为大写字母
    •     4、用户名字母倒着排序
    •     5、只输出一个用户
import java.util.Arrays;
import java.util.List;

public class Test {
    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(6, "e", 25);
        List<User> list = Arrays.asList(user1, user2, user3, user4, user5);
​
        // 链式编程
        list.stream()
                .filter(u -> {return u.getId() % 2 == 0;})
                .filter(user -> {return user.getAge() > 23;})
                .map(user -> {return user.getName().toUpperCase();})
                .sorted((u1, u2) -> {return u2.compareTo(u1);})
                .limit(1)
                .forEach(System.out::println);
    }
}

13、ForkJoin

什么是ForkJoin

ForkJoin: 在并行执行任务!提高效率,大数据量!把大任务拆成小任务

ForkJoin的特点:工作窃取

当本线程的任务执行完毕之后,回去找一部分其他线程未执行的任务去执行

ForkJoin里面维护的都是双端队列

ForkJoin的操作

  • 1、forkJoin 通过它来执行
  • 2、计算任务 forkJoinPool.execute(ForkJoinTask task)
  • ForkJoinTask
import java.util.concurrent.RecursiveTask;

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 { // forkJoin
            long mid = (start + end) / 2;
            ForkJoinDemo task1 = new ForkJoinDemo(start, mid);
            // 拆分任务
            task1.fork();
            ForkJoinDemo task2 = new ForkJoinDemo(mid + 1, end);
            task2.fork();
​
            return task1.join() + task2.join();
        }
    }
}
​
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();     // 676
        // test2();     // 8909
        test3();        // 289
    }
​
    //
    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));
    }
​
    public static void test2() throws ExecutionException, InterruptedException {
        long start = System.currentTimeMillis();
        ForkJoinPool forkJoinPool = new ForkJoinPool();
        ForkJoinTask<Long> task = new ForkJoinDemo(1L, 10_0000_0000L);
        // 执行任务
        // forkJoinPool.execute(task);
        ForkJoinTask<Long> submit = forkJoinPool.submit(task);
        Long sum = submit.get();
        long end = System.currentTimeMillis();
        System.out.println("sum = " + sum + "时间:" +(end - start));
    }
​
    public static void test3() {
        long start = System.currentTimeMillis();
        // Stream并行流
        LongStream.rangeClosed(0L, 10_0000_0000L).parallel().reduce(0, Long::sum);
        long end = System.currentTimeMillis();
        System.out.println("sum = " + "时间:" +(end - start));
    }
}

14、异步回调

异步调用: CompletableFuture, 异步执行:成功回调、失败回调

import java.util.concurrent.CompletableFuture;
import java.util.concurrent.ExecutionException;
import java.util.concurrent.TimeUnit;
​
public class Demo01 {
    public static void main(String[] args) throws ExecutionException, InterruptedException {
​
        // 没有返回值的异步回调
//        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 异步回调
        CompletableFuture<Integer> completableFuture = CompletableFuture.supplyAsync(() -> {
            System.out.println(Thread.currentThread().getName() + "supplyAsync => Integer");
            return 1024;
        });
​
        System.out.println(completableFuture.whenComplete((t, u) -> {
            System.out.println("t = " + t); // 正常的结果返回
            System.out.println("u = " + u); // 错误信息:打印错误信息
        }).exceptionally((e) -> {
            System.out.println(e.getMessage());
            return 233;
        }).get());
    }
}

15、JMM

请你谈谈你对Volatile的理解

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

1、保证可见性

2、不保证原子性

3、禁止指令重排

什么是JMM

JMM:Java内存模型,不存在的东西

关于JMM的一些同步约定:

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

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

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

image-20221222205042908

Java内存模型定义了以下八种操作来完成:

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

Java内存模型还规定了在执行上述八种基本操作时,必须满足如下规则:

  • 如果要把一个变量从主内存中复制到工作内存,就需要按顺寻地执行read和load操作, 如果把变量从工作内存中同步回主内存中,就要按顺序地执行store和write操作。但Java内存模型只要求上述操作必须按顺序执行,而没有保证必须是连续执行。
  • 不允许read和load、store和write操作之一单独出现
  • 不允许一个线程丢弃它的最近assign的操作,即变量在工作内存中改变了之后必须同步到主内存中。
  • 不允许一个线程无原因地(没有发生过任何assign操作)把数据从工作内存同步回主内存中。
  • 一个新的变量只能在主内存中诞生,不允许在工作内存中直接使用一个未被初始化(load或assign)的变量。即就是对一个变量实施use和store操作之前,必须先执行过了assign和load操作。
  • 一个变量在同一时刻只允许一条线程对其进行lock操作,但lock操作可以被同一条线程重复执行多次,多次执行lock后,只有执行相同次数的unlock操作,变量才会被解锁。lock和unlock必须成对出现
  • 如果对一个变量执行lock操作,将会清空工作内存中此变量的值,在执行引擎使用这个变量前需要重新执行load或assign操作初始化变量的值
  • 如果一个变量事先没有被lock操作锁定,则不允许对它执行unlock操作;也不允许去unlock一个被其他线程锁定的变量。
  • 对一个变量执行unlock操作之前,必须先把此变量同步到主内存中(执行store和write操作)。

16、Volatile

1、保证可见性

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

2、不保证原子性

public class VDemo02 {
​
    // volatile 不保证原子性
    private volatile static int num = 0;
​
    public static void add() {
        num++;
    }
    public static void main(String[] args) {
​
        new Thread(() -> {
            for (int i = 1; i <= 200; i++) {
                for (int j = 0; j < 1000; j++) {
                    add();
                }
            }
        }).start();
​
        while (Thread.activeCount() > 2) {
            Thread.yield();
        }
        System.out.println(Thread.currentThread().getName() + " " + num);
    }
}

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

使用原子类:解决原子性问题。

import java.util.concurrent.atomic.AtomicInteger;

public class VDemo02 {
​
    // volatile 不保证原子性
    // 原子类的 Integer
    private volatile static AtomicInteger num = new AtomicInteger();
​
    public static void add() {
//        num++;
        num.getAndIncrement(); // AtomicInteger 加一操作
    }
    public static void main(String[] args) {
​
        new Thread(() -> {
            for (int i = 1; i <= 200; i++) {
                for (int j = 0; j < 1000; j++) {
                    add();
                }
            }
        }).start();
​
        while (Thread.activeCount() > 2) {
            Thread.yield();
        }
        System.out.println(Thread.currentThread().getName() + " " + num);
    }
}

指令重排

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

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

image-20221222212907700

volatile可以避免指令重排:

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

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

2、可以保证某些变量的内存可见性

image-20221222213233201

17、彻底玩转单例模式

饿汉式

饿汉式: 也就是创建即私有

public class Hungry {
​
    // 可能会造成空间浪费
    byte[] data1 = new byte[1024 * 1024];
    byte[] data2 = new byte[1024 * 1024];
    byte[] data3 = new byte[1024 * 1024];
    byte[] data4 = new byte[1024 * 1024];
​
    private Hungry() {
​
    }
​
    private final static Hungry HUNGRY = new Hungry();
​
    public static Hungry getInstance() {
        return HUNGRY;
    }
​
}

DCL 懒汉式

import java.lang.reflect.Constructor;
import java.lang.reflect.Field;
​
public class LazyMan {
​
    private static boolean chen = false;
    private LazyMan() {
        synchronized (LazyMan.class){
            if (chen == false) {
                chen = true;
            } else {
                throw new RuntimeException("不要试图用反射破坏异常");
            }
            /*if (lazyMan != null) {
                throw new RuntimeException("不要试图用反射破坏异常");
            }*/
        }
        System.out.println(Thread.currentThread().getName() + "ok");
    }
​
    private volatile static LazyMan lazyMan;
​
    public static LazyMan getInstance() {
        // 加锁,双重检测锁模式的 懒汉模式 DCL模式
        if (lazyMan == null) {
            synchronized (LazyMan.class) {
                if (lazyMan == null) {
                    // 不是原子性操作
                    lazyMan = new LazyMan();
                }
            }
        }
        return lazyMan;
    }
​
​
    // 反射
    public static void main(String[] args) throws Exception {
        // LazyMan instance = LazyMan.getInstance();
​
        Field chen1 = LazyMan.class.getDeclaredField("chen");
        chen1.setAccessible(true);
        Constructor<LazyMan> declaredConstructor = LazyMan.class.getDeclaredConstructor(String。class, int.class);
        declaredConstructor.setAccessible(true);
        LazyMan instance = declaredConstructor.newInstance();
​
        chen1.set(instance, false);
        LazyMan instance2 = declaredConstructor.newInstance();
​
        System.out.println(instance);
        System.out.println(instance2);
​
    }
}
​
// 1、分配内存空间
// 2、执行构造方法,初始化对象
// 3、把这个对象指向这个空间

静态内部类

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

单例不安全,反射:使用枚举

public enum EnumSingle {
​
    INSTANCE;
​
    public EnumSingle getInstance() {
        return INSTANCE;
    }
}
​
class Test {
    public static void main(String[] args) {
​
        EnumSingle instance1 = EnumSingle.INSTANCE;
​
    }
}

18、深入理解CAS

什么是CAS

CAS是一种有名的无锁算法 。无锁编程,即不使用锁的情况下实现多线程之间的变量同步,也就是在没有x线程被阻塞的情况下实现变量的同步。

  • CAS(Compare And Swap)比较并替换,是线程并发运行时用到的一种技术

  • CAS是原子操作,保证并发安全,而不能保证并发同步

  • CAS是CPU的一个指令(需要JNI调用Native方法,才能调用CPU的指令)

  • CAS是非阻塞的、轻量级的乐观锁

import java.util.concurrent.atomic.AtomicInteger;
​

public class CASDemo {
​
    // CAS compareAndSet: 比较并交换
    public static void main(String[] args) {
        AtomicInteger atomicInteger = new AtomicInteger(2020);
​
        // public final boolean compareAndSet(int expect, int update) 期望、更新
        // 如果我期望的值拿到了就更新,否则就不更新
        System.out.println(atomicInteger.compareAndSet(2020, 2021));
        System.out.println(atomicInteger.get());
​
        System.out.println(atomicInteger.compareAndSet(2020, 2021));
        System.out.println(atomicInteger.get());
    }
}

Unsafe类

image-20221222222946800

image-20221222223513050

image-20221222223554731

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

缺点:

1、循环会耗时

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

3、ABA问题

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

image-20221222224130455

import java.util.concurrent.atomic.AtomicInteger;
​
public class CASDemo {
​
    // CAS compareAndSet: 比较并交换
    public static void main(String[] args) {
        AtomicInteger atomicInteger = new AtomicInteger(2020);
​
        // public final boolean compareAndSet(int expect, int update) 期望、更新
        // 如果我期望的值拿到了就更新,否则就不更新
        // ============= 捣乱的线程 ==============
        System.out.println(atomicInteger.compareAndSet(2020, 2021));
        System.out.println(atomicInteger.get());
​
        System.out.println(atomicInteger.compareAndSet(2021, 2020));
        System.out.println(atomicInteger.get());
​
        // ============= 期望的线程 ==============
        System.out.println(atomicInteger.compareAndSet(2020, 6666));
        System.out.println(atomicInteger.get());
    }
}

19、原子引用

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

带版本号的 原子操作!

import java.util.Date;
import java.util.Objects;
import java.util.concurrent.TimeUnit;
import java.util.concurrent.atomic.AtomicInteger;
import java.util.concurrent.atomic.AtomicStampedReference;
​

public class CASDemo {
​
    // CAS compareAndSet: 比较并交换
    public static void main(String[] args) {
//        AtomicInteger atomicInteger = new AtomicInteger(2020);
​
        AtomicStampedReference<Integer> atomicStampedReference = new AtomicStampedReference<>(123,1);
​
​
        // public final boolean compareAndSet(int expect, int update) 期望、更新
        // 如果我期望的值拿到了就更新,否则就不更新
        // ============= 捣乱的线程 ==============
        /*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());*/
​
        new Thread(() -> {
            int stamp = atomicStampedReference.getStamp(); // 获得版本号
            System.out.println("a1 => " + stamp);
​
            try {
                TimeUnit.SECONDS.sleep(2);
            } catch (InterruptedException e) {
                throw new RuntimeException(e);
            }
​
            System.out.println(atomicStampedReference.compareAndSet(123, 124,
                    atomicStampedReference.getStamp(), atomicStampedReference.getStamp() + 1));
            System.out.println("a2 => " + atomicStampedReference.getStamp());
​
            System.out.println(atomicStampedReference.compareAndSet(124, 123,
                    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(123, 125,
                    stamp, stamp + 1));
​
        }, "B").start();
​
    }
}

20、各种锁的理解

1、可重入锁

Synchronized

synchronized是Java中的关键字,是一种同步锁 。它修饰的对象有以下几种: 

  1. 修饰一个代码块,被修饰的代码块称为同步语句块,其作用的范围是大括号{}括起来的代码,作用的对象是调用这个代码块的对象;
  2. 修饰一个方法,被修饰的方法称为同步方法,其作用的范围是整个方法,作用的对象是调用这个方法的对象;
  3. 修改一个静态的方法,其作用的范围是整个静态方法,作用的对象是这个类的所有对象;
  4. 修改一个类,其作用的范围是synchronized后面括号括起来的部分,作用主的对象是这个类的所有对象。
package com.chen.lock;

public class Demo1 {
    public static void main(String[] args) {
        Phone phone1 = new Phone();
        Phone phone2 = new Phone();
​
        new Thread(() -> {
            phone1.sms();
        }, "A").start();
​
        new Thread(() -> {
            phone1.sms();
        }, "B").start();
​
    }
}
class Phone {
​
    public synchronized void sms() {
        System.out.println(Thread.currentThread().getName() + "sms()");
        // call也有锁
        call();
    }
​
    public synchronized void call() {
        System.out.println(Thread.currentThread().getName() + "call()");
    }
}

lock

Lock锁: 可以得到和 synchronized一样的效果,即实现原子性、有序性和可见性。 相较于synchronized,Lock锁可手动获取锁和释放锁、可中断的获取锁、超时获取锁。 Lock 是一个接口,两个直接实现类:ReentrantLock(重入锁), ReentrantReadWriteLock(读写锁)。

package com.chen.lock;
​
import java.util.concurrent.locks.Lock;
import java.util.concurrent.locks.ReentrantLock;
​

public class Demo2 {
    public static void main(String[] args) {
        Phone1 phone1 = new Phone1();
​
        new Thread(() -> {
            phone1.sms();
        }, "A").start();
​
        new Thread(() -> {
            phone1.sms();
        }, "B").start();
​
    }
}
class Phone1 {
​
    Lock lock = new ReentrantLock();
​
    public void sms() {
        // 获得锁
        lock.lock(); // 细节:lock锁必须配对,否则就会产生死锁
        try {
            System.out.println(Thread.currentThread().getName() + "sms()");
            // call也有锁
            call();
        } catch (Exception e) {
            throw new RuntimeException(e);
        } finally {
            // 释放锁
            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();
        }
    }
}

2、自旋锁

自旋锁: 是计算机科学 用于多线程 同步 的一种 锁 ,线程反复检查锁变量是否可用。. 由于线程在这一过程中保持执行,因此是一种 忙等待 。. 一旦获取了自旋锁,线程会一直保持该锁,直至显式释放自旋锁。. 自旋锁避免了进程上下文的调度开销,因此对于线程只会阻塞很短时间的场合是有效的。

spinlock:

image-20221222232726646

自定义自旋锁测试:

package com.chen.lock;
​
import java.util.concurrent.atomic.AtomicReference;
​
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);
    }
}

测试:

package com.chen.lock;
​
import java.util.concurrent.TimeUnit;
import java.util.concurrent.locks.ReentrantLock;
​

public class TestSpinLock {
    public static void main(String[] args) {
//        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();
​
        try {
            TimeUnit.SECONDS.sleep(1);
        } catch (InterruptedException e) {
            throw new RuntimeException(e);
        }
        new Thread(() -> {
            lock.myLock();
            try {
                TimeUnit.SECONDS.sleep(1);
            } catch (Exception e) {
                throw new RuntimeException(e);
            } finally {
                lock.myUnLock();
            }
        }, "T2").start();
    }
}

3、死锁

死锁是什么?

所谓死锁: 是指多个进程在运行过程中因争夺资源而造成的一种僵局,当进程处于这种僵持状态时,若无外力作用,它们都将无法再向前推进。 因此我们举个例子来描述,如果此时有一个线程A,按照先锁a再获得锁b的的顺序获得锁,而在此同时又有另外一个线程B,按照先锁b再锁a的顺序获得锁。

image-20221222234258178

死锁测试

package com.chen.lock;
​
import java.util.concurrent.TimeUnit;

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-20221222235536200

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

image-20221222235651187