JUC学习

1、线程和进程

  • 线程、进程

    • 线程:一个程序

      一个进程往往包含很多线程,至少包含一个
      
      java默认有两个线程:main、GC
      
    • 线程:线程是进程中的实际运作单位,是操作系统能够进行运算调度的最小单位

    • Java真的能开启线程吗?

    不能,java不能直接操作硬件

    Thread中的start方法是一个同步方法,调用时会将此线程加入线程组,然后调用start0方法,start0方法是一个本地方法

    //本地方法,调用底层的C++,Java运行在虚拟机,不能直接操作硬件
    priavte native void start0();
    
  • 并发、并行

    • 并发(单核):多线程操作同一个资源,快速交替

    • 并行(多核):多个线程同时执行

    ​ cpu多核,多个线程可以同时执行

    • 并发编程的本质:充分利用CPU资源
  • 线程有几个状态? 6种

    • NEW 新生
    • RUNNABLE 运行
    • BLOCKED 阻塞
    • WAITING 等待
    • TIMED_WAITING 超时等待
    • TERMINATED 终止
  • wait/sleep区别

    • 来自不同的类
      • wait -->Object
      • sleep -->Thread
    • 关于锁的释放
      • wait会释放锁
      • sleep不释放,抱着锁睡觉
    • 使用的范围不同
      • wait必须在同步代码块中
      • sleep可以在任何地方睡
    • 是否需要捕获异常
      • wait不需要捕获异常
      • sleep需要捕获异常
    • 唤醒方式
      • wait需要其他线程调用同一对象的nofity()/nofityAll()才能重新恢复
      • sleep在时间到了之后会自动重新恢复

2、LOCK锁(重点)

  • 传统Synchronized

    本质:队列、锁

  • Lock

    • 实现类

      • ReentrantLock 可重入锁(常用)
      • ReentrantReadWriteLock.ReadLock 读锁
      • ReentrantReadWriteLock.WriteLock 写锁
    • 公平锁与非公平锁

      • 公平锁:十分公平,先来后到
      • 非公平锁:是十分不公平,可以插队(默认)
    • 使用

      Lock lock = new 实现类;
      //加锁
      lock.lock();
      //尝试获取锁
      //lock.tryLock();
      try{
          //业务代码
      }catch(Exception e){
          e.printStackTrace();
      }finally{
          //解锁
          lock.unlock
      }
      
  • Synchronized和Lock区别

    • Synchronized 是内置的Java关键字;Lock是一个Java接口
    • Synchronized 无法判断获取所得状态;Lock可以判断是否获取到了锁
    • Synchronized 会自动释放锁;Lock必须手动释放锁,如果不释放锁,死锁
    • Synchronized 只要没有获得锁就一直等待;Lock锁不一定会一直等待下去
    • Synchronized 可重入锁,不可以中断,非公平;Lock 可重入锁,可以判断锁,默认非公平(可以设置)
    • Synchronized 适合锁少量的代码同步问题;Lock适合锁大量的同步代码
  • 锁是什么,如何判断锁的是谁

3、线程通信—生产者和消费者问题

  • Synchronized版 Synchronized wait() notify()

    A、B 两个线程,线程安全 if判断

    A、B、C、D四个线程可能会出现虚假唤醒的情况 if判断一定要改成while判断

  • JUC版 Lock await() signal()

    • Confition 精准通知和唤醒线程

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

4、8锁现象(关于锁的8个问题)

  • 如何判断锁的是谁,锁的是谁

    **深刻理解锁 **

    • 一个对象两个同步方法,因为有锁,Synchronized 锁的对象是方法的调用者,谁先拿到谁先执行
    • 增加在第一个线程调用方法里等待4秒,还是谁先拿到谁执行
    • 增加了一个普通方法后,先普通方法,再锁方法
    • 两个对象两个同步方法,不等待的先执行完,按时间
    • 增加两个静态同步方法一个对象,谁先拿到谁先执行,因为static静态方法,类刚加载就有了,锁的是Class
    • 两个静态同步方法两个对象,谁先拿到谁先执行,static方法,锁的是Class ,两个对象的Class模板只有一个
    • 普通同步方法和静态同步方法一个对象,普通同步方法先执行,静态的同步方法锁的是类,普通同步方法锁的是方法
    • 普通同步方法和静态同步方法两个对象,普通同步方法先执行,静态的同步方法锁的是类,普通同步方法锁的是方法
  • 小结

    • new
      • ​ this 具体的某个方法
    • static
      • ​ 唯一的Class模板

5、集合类不安全

  • List不安全

    • 并发下ArrayList不安全 (会报出并发修改异常) java.util.ConcurrentModificationException

      • (Vector默认就是安全的) 但ArrayList是1.2出的 Vector是1.0出的

        List<String> list = new Vector<>();
        
      • 可以用Collections.sysnchroizedList(new ArrayList<>()); 变得安全

        List<String> list = Collections.sysnchroizedList(new ArrayList<>);
        
      • 可以用JUC下的CopyOnWriteArrayList<>();变得安全

        List<String> list = new CopyOnWriteArrayList<>();
        
        • CopyOnWrite 写入时复制 COW 计算机程序设计领域的一种优化策略

        • 多个线程调用list时,读取的时候是固定的,写入的时候会有覆盖问题,CopyOnWrite可以在写入的时候避免覆盖,造成数据问题

        • CopyOnwrite与Vector相比优势?

          • Vector中使用了Sysnchronized锁,影响性能
          • CopyOnWrite中使用Lock锁,写入时候先复制一份,写入之后再set回去
  • Set不安全

    • 同List理可证,HashSet也不安全,会报出(并发修改异常) java.util.ConcurrentModificationException

      • 可以用Collections.sysnchroized(new HashSet<>())变得安全

        Set<String> set = Collections.sysnchroizedSet(new HashSet<>());
        
      • 可以用JUC下的CopyOnWriteHashSet<>()方法变得安全

        Set<String> set = new CopyOnWriteArraySet<>();
        
    • HashSet底层是什么?

      • 源码:
      //HashSet源码public HashSet() {        map = new HashMap<>();    }//add方法源码public boolean add(E e) {        return map.put(e, PRESENT)==null;    }
      

      HashSet的不可重复就是利用了HashMap中Key不可重复

  • Map不安全

    • 同理也会抛出并发修改异常

      • 可以用Collections.sysnchrized(new HashMap<>())变得安全

        Map<String,String> map = Collections.sysnochrizedMap(new HashMap<>());
        
      • 可以用JUC下的ConcurrentHashMap<>()变得安全

        Map<String,String> map = new ConcurrentHashMap<>();
        

6、Callable

  • 与Runnable类似,创建线程的第三种方式

    • 可以有返回值
    • 可以抛出异常
    • 方法不同,run()/call()
  • 使用方式

    • new Thread(new FutureTask<Integer>(new CallableTest())).start();
      
      • 因为Thread中只能接收Runnable对象,FutureTask与Runnable和Callable都有关,互相转换,所以需要FutureTask中传入Callable对象,再把FutureTask传入到Thread中进行启动

      • 细节

        • FutureTask可以get得到Callable的返回值,所以可能会产生阻塞,要把它放在最后一行或者使用异步通信来处理

          futureTask.get()
          
        • FutureTask的结果会被缓存,效率高

7、常用的辅助类

  • CountDownLatch

    • 减法计数器

       CountDownLatch countDownLatch = new CountDownLatch(6);
      
      • counDownLatch.countDown(); 数量-1 不会阻塞
      • countDownLatch.await();等待计数器归零 假设计数器变为0,countDownLatch.await就会被唤醒,继续执行
  • CyclicBarrier

    • 加法计数器

      CyclicBarrier cyclicBarrier = new CyclicBarrier(7, () -> {            //达到预设数值之后的业务        });
      
      • cyclicBarrier.await等待计数器到数值 假设计数器变为预设的数值,cyclicbarrier.await就会执行
  • Semaphore

    • 信号量

      Semaphore semaphore = new Semaphore(3);
      
      • 多个共享资源互斥的时候使用
      • 并发限流,控制最大的线程数
        • semaphore.acquire(); 获得 假设如果线程满了,等待,等待被释放为止
        • semaphore.release();释放 将当前信号量+1,然后唤醒等待的线程 放到finally中

8、读写锁

  • 读-读 可以共存 读-写不可共存 写-写不可共存

  • 创建ReadWriteLock对象

ReadWriteLock readWriteLock = new ReentrantReadWriteLock();
  • 读锁readWriteLock.readLock() 共享锁

    readWriteLock.readLock().lock();读锁上锁

    readWriteLock.readLock().unlock();读锁释放

  • 写锁readWriteLock.writeLock() 独占锁

    readWriteLock.writeLock().lock();写锁上锁

    readWriteLock.writeLock().unlock();写锁释放

9、阻塞队列

  • BlockingQueue不是新的东西 BlockingQueue和List和Set同级

  • 使用情况

    • 多线程并发执行、线程池
  • LinkedBlockingQueue

  • ArrayBlockingQueue

    • 队列定义

      ArrayBlockingQueue arrayBlockingQueue = new ArrayBlockingQueue<>(队列大小)

    • 添加元素

      • add

        arrayBlockingQueue.add("a");如果队列已满会抛出队列已满异常 Queue full

      • offer

        arrayBlockingQueue.offer("a");如果队列已满不会抛出异常,会返回false

      • put

        arrayBlockingQueue.put("a");如果队列已满不会抛出异常,会一直等待,知道有空闲位置可以添加元素

      • offer(元素,等待时间,时间单位)

        arrayBlockingQueue.offer("d",2,TimeUnit.SECONDS);如果队列已满会等待2秒,2秒过后如果还没有空闲位置可以添加元素,就退出返回false

    • 取元素 FIFO先进先出

      • remove

        arrayBlockingQueue.remove();如果队列为空会抛出没有异常 NoSuchElementException

      • poll

        arrayBlockingQueue.poll();如果队列为空不会抛出异常,会返回null值

      • take

        arrayBlockingQueue.take();如果队列为空,会一直等待,直到队列中有元素可以被取出

      • poll(等待时间,时间单位)

        arrayBlockingQueue.poll(2, TimeUnit.SECONDS);如果队列为空,会等待2秒,2秒过后如果队列中还没有元素可以取出,就退出返回null值

    • 查看队首元素

      • element

        arrayBlockingQueue.element();如果队列为空会抛出异常 NoSuchElementException

      • peek

        arrayBlockingQueue.peek();如果队列为空,会返回null值

  • 四组API

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

    • 没有容量,添加一个元素必须取出一个元素,之后才能再次添加元素,只能放一个元素 不然会一直等待!

    • put

      blockingQueue.put("1");

    • take

      blockingQueue.take();

10、线程池(重点)

  • 线程池:3大方法,7大参数,4种拒绝策略

  • 池化技术

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

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

    事先准备好资源,有人要用,就来我这里拿,用完之后还给我

    • 线程池的好处

      • 降低资源的消耗

      • 提高相应的速度

      • 方便管理

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

  • 3大方法

    • ExecutorService threadPool1 = Executors.newSingleThreadExecutor(); //单个线程的线程池
      
    • ExecutorService threadPool2 = Executors.newFixedThreadPool(5); //固定大小的线程池
      
    • ExecutorService threadPool3 = Executors.newCachedThreadPool(); //可伸缩的线程池,最大数量为Integer.MAX_VALUE会造成OOM内存泄漏
      

      最好不要使用Excutors创建线程,可能会出现OOM内存泄漏,有资源耗尽的风险,而是通过ThreadPoolExecutor的方式

    • ExecutorService threadPool4 = new ThreadPoolExecutor(                2,                5,                 10,                TimeUnit.SECONDS,                new ArrayBlockingQueue<>(3),                Executors.defaultThreadFactory(),                new ThreadPoolExecutor.DiscardPolicy());
      
  • 7大参数

    • 核心线程池大小

    • 最大线程池大小

      • 定义方式

        • CPU密集型

          • 几核CPU就定义为几,可以保证CPU的效率最高

            Runtime.getRuntime().availableProcessors();//获取CPU核数
            
        • IO密集型

          • 大于程序中十分耗IO的线程数,不会造成阻塞,一般2倍
    • 超时未使用就释放

    • 超时单位

    • 阻塞队列

    • 线程工厂 Executors.defaultThreadFactory()

    • 拒绝策略

      • 4种拒绝策略
        • AbortPolicy() 抛出异常 默认
        • DiscardPolicy() 直接丢弃
        • DiscardOldestPolicy() 抛弃最老的线程,提交新的线程
        • CallerRunsPolicy() 交给申请的线程执行

11、四大函数式接口(重点必须掌握)

  • 传统程序员:泛型、反射、枚举 (JDK1.5)

  • 新时代程序员:lambda表达式链式编程函数式接口Stream流式计算 (JDK1.8)

  • @FunctionalInterface函数式接口:只有一个方法的接口,简化编程,再新版本的框架底层大量应用,

    • 例如:Runnbale接口

      @FunctionalInterfacepublic interface Runnable {    public abstract void run();}
      
    • 例如foreach的参数,典型的消费型函数式接口

      @FunctionalInterfacepublic interface BiConsumer<T, U> {    void accept(T t, U u);}
      
  • 四大函数式接口

    只要是函数时接口,就能拿lambda表达式简化

    • Function函数型接口 输入参数T返回R类型的数据

      @FunctionalInterfacepublic interface Function<T, R> {    R apply(T t);}
      
    • Predicate断定型接口 输入一个参数,返回一个布尔类型数据

      @FunctionalInterfacepublic interface Predicate<T> {    boolean test(T t);}
      
    • Consumer消费型接口 输入一个参数,没有返回值

      @FunctionalInterfacepublic interface Consumer<T> {    void accept(T t);}
      
    • Supplier供给型接口 不传入参数,返回一个数据T

      @FunctionalInterfacepublic interface Supplier<T> {    T get();}
      

12、Stream流式计算

  • 什么是Stream流式计算

    /** * 题目要求:一行代码实现 * 现在有6个用户,筛选 * 1.ID必须是偶数 * 2.年龄必须大于23岁 * 3.用户名转为大写字母 * 4.用户名倒着输出 * 5.只输出一位用户 */User u1 = new User(1,"a",21);User u2 = new User(2,"b",22);User u3 = new User(3,"c",23);User u4 = new User(4,"d",24);User u5 = new User(6,"e",26);User u6 = new User(8,"f",28);//存储List<User> list = Arrays.asList(u1, u2, u3, u4, u5,u6);//计算list.stream()        .filter(user -> {return user.getId()%2==0;})        .filter(user -> {return user.getAge()>23;})        .map(user -> {user.setName(user.getName().toUpperCase()); return user;})        .sorted((user1,user2)->{return user2.getName().compareTo(user1.getName());})        .limit(1)        .forEach(System.out::println);
    

13、ForkJoin

  • ForkJoin分支合并JDK1.7就存在了 在大数据量时,并行执行任务,提高效率

  • 在必要的情况下,将一个大任务,进行拆分(fork) 成若干个子任务(拆到不能再拆,这里就是指我们制定的拆分的临界值),再将一个个小任务的结果进行join汇总。

  • 特点

    ​ 工作窃取 (维护的都是双端队列)

  • 使用

    1. 创建ForkJoinPool,通过ForkJoinPool执行

      ForkJoinPool forkJoinPool = new ForkJoinPool();
      
    2. 计算任务

      forkjoinpool.execute(ForkJoinTask task)//执行,同步  无返回值
      
      forkJoinPool.submit(ForkJoinTask task);//提交,异步  有返回值
      
    3. 计算类要继承ForkJoinTask的两个子类:RecursiveAction或者RecursiveTask(V),其中RecursiveAction任务没有返回值,RecursiveTask任务有返回值.这两个子类都有一个抽象的方法,叫做compute(),用来定义任务的逻辑.

14、异步回调

  • Future设计的初衷:对将来的某个时间的结果进行建模

    • 无返回值的异步调用CompletableFuture.runAsync()

      //没有返回值的runAsync异步回调
      CompletableFuture<Void> completableFuture = CompletableFuture.runAsync(() -> {
          try {
              TimeUnit.SECONDS.sleep(2);
          } catch (InterruptedException e) {
              e.printStackTrace();
          }
          System.out.println("runAsync");
      });
      
      System.out.println("================");
      
      Void aVoid = completableFuture.get();//获取阻塞执行结果
      
      //先输出================,等待2秒后输出runAsync
      
    • 有返回值的异步调用CompletableFuture.supplyASync()

      //有返回值的supplyAsync异步回调CompletableFuture<Integer> uCompletableFuture = CompletableFuture.supplyAsync(() -> {    //int i = 10/0;    return 1024;});System.out.println("---------------");System.out.println(uCompletableFuture.whenComplete((t, u) -> {    System.out.println(t);//正常的返回结果    System.out.println(u);//错误信息}).exceptionally((e) -> {    e.getMessage();    return 233;//可以获取到错误的返回结果}).get());
      

15、JMM

  • 请你谈谈你对Volatile的理解

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

    1. 保证可见性
    2. 不保证原子性
    3. 禁止指令重排
  • 什么是JMM?

    • JMM是Java内存模型,不存在东西,是概念、约定
    • 关于JMM的一些同步的约定
      • 线程解锁前,必须把共享变量刷回主存
      • 线程加锁前,必须读取主存中的最新值到工作内存中
      • 加锁和解锁是同一把锁
    • 8种操作
      • lock(锁定)
      • unlock(解锁)
      • read(读取)
      • load(载入)
      • use(使用)
      • assign(赋值)
      • write(写入)
      • store(存储)
    • 使用规则
      • 不允许read和load、write和store操作之一单独出现。即使用了read必须load,使用了write必须stroe
      • 不允许一个线程将没有assign的数据从工作内存同步回主内存
      • 一个新的变量必须在主存中诞生,不允许工作内存直接使用一个未被初始化的变量。即对变量实施use、store操作之前,必须经过assign和load操作
      • 一个变量同一时间只有一个线程能对其进行lock。多次lock之后,必须执行相同次数的unlock才能解锁
      • 如果对一个变量进行lock操作,会清空所有工作内存种此变量的值,在执行引擎使用这个变量前,必须重新进行load或assign操作初始化变量的值
      • 如果一个变量两没有被lock,就不能对其进行unlock操作。也不能unlock一个被其他线程锁住的变量
      • 对一个变量进行unlock操作之前,必须把变量同步回主内存

16、Volatile

  • 保证可见性

    //不加volatile线程就感知不到num值发生了变化private volatile static int num = 0;
    
  • 不保证原子性

    • 原子性:不可分割

    • 不使用synchronized和了lock怎么保证num++的原子性?

      • 使用JUC包下的原子类 比用锁更高效

        • AtomicInteger

          private volatile static AtomicInteger num = new AtomicInteger();num.incrementAndGet(); //++i    使用的是CASnum.getAndIncrement(); //i++    使用的是CAS
          
        • AtomicLong

        • AtomicBoolean

      • 这些类的底层都与系统挂钩,在内存中修改值,Unsafe是很特殊的存在

  • 禁止指令重排

    • 指令重排: 计算机并不按照程序员写的去执行

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

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

        int x = 1;//1int y = 2;//2x = x + 1;//3y = x * x;//4//我们所期望的 1234   但可能执行的时候 2134 1324//不可能是 4123 
        

        默认 a b x y 这4个值都为零

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

        正常情况下: a=2 b=1 x=0 y=0

        指令重排可能会: a=2 b=1 x=2 y=1

    • Volatile可以避免指令重排:

      • 内存屏障 CPU指令
      • 作用
        • 保证特定的操作的执行顺序
        • 可以保证某些变量的可见性(利用这些特性,volatile实现了可见性)
      • Volatile是可以保持可见性.不能保证原子性,由于内存屏障,可以保证避免指令重排的现象产生!

17、彻底玩转单例模式

  • 饿汉式单例

    • 可能会浪费空间

      //饿汉式单例public class Hungry {    private Hungry(){}        private  final static  Hungry HUNGRY = new Hungry();        public static Hungry getInstance(){        return HUNGRY;    }}
      
  • 懒汉式单例

    //懒汉式单例模式public class LazyMan {    private LazyMan(){}        private static LazyMan lazyMan;        public static LazyMan getInstance(){        if (lazyMan==null){            lazyMan = new LazyMan();        }        return lazyMan;    }}
    
    • 单线程下确实单例ok,多线程并发情况下无法单例,需要为class加锁,并且加锁 ===>双重检测锁模式的懒汉式单例 DCL懒汉式

      //懒汉式双重检测锁DLC单例模式public class LazyMan {    private LazyMan(){}    private volatile static LazyMan lazyMan;    public static LazyMan getInstance() {        if (lazyMan == null) {            synchronized (LazyMan.class) {                if (lazyMan == null) {                    lazyMan = new LazyMan();//不是原子性操作                    /**                     * 1、分配内存空间                     * 2、执行构造方法,初始化对象                     * 3、把对象指向空间                     * 期望:123  可能132  此时lazyMan还没有完成构造                     * 避免指令重排需要加 volatile                     */                }            }        }        return lazyMan;    }}
      
      //道高一尺,魔高一丈public class LazyMan {    private static  boolean qwe = false;    private LazyMan(){        synchronized (LazyMan.class) {            if (qwe==false){                qwe = true;            }else {                throw new RuntimeException("不要试图拿反射破坏单例");            }        }    }    private volatile static LazyMan lazyMan;    public static LazyMan getInstance() {        if (lazyMan == null) {            synchronized (LazyMan.class) {                if (lazyMan == null) {                    lazyMan = new LazyMan();                }            }        }        return lazyMan;    }        //反射破坏!    public static void main(String[] args) throws Exception {        //LazyMan instance1 = LazyMan.getInstance();        Field qwe = LazyMan.class.getDeclaredField("qwe");        qwe.setAccessible(true);        Constructor<LazyMan> declaredConstructor = LazyMan.class.getDeclaredConstructor(null);        declaredConstructor.setAccessible(true);        LazyMan instance1 = declaredConstructor.newInstance();        qwe.set(instance1, false);        LazyMan instance2 = declaredConstructor.newInstance();        System.out.println(instance1);        System.out.println(instance2);    }}
      
  • 静态内部类

    //静态内部类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;    }}
    
    • 反射不能破坏枚举

    • 枚举也是一个类,只是继承了枚举接口 枚举默认就是单例

    • 没有无参构造,只有一个有参构造 (String,int)

18、深入理解CAS

  • 什么是CAS

    • compareAndSet 比较并交换 CAS是CPU的并发原语

    • CAS:比较并交换当前工作内存种的值,如果这个值是期望的,那么则执行操作,如果不是就一直循环(自旋锁)

    • CAS缺点:

      • 循环会耗时
      • 一次只能保证一个共享变量的原子性
    • Unsafe类

      • Java无法操作内存,Java可以调用C++使用native方法,C++可以调用内存,Unsafe就是Java的后门,可以通过这个类操作内存
      • 内存操作,效率很高
      • 自旋锁
  • ABA问题(狸猫换太子) 原子引用

19、原子引用

解决ABA问题 对应的思想—>乐观锁

AtomicStampedReference<Integer> atomicStampedReference = new AtomicStampedReference<>(初始值,版本号 );

20、各种锁的理解

  • 公平锁、非公平锁

    • 公平锁:非常公平,先来后到

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

      Lock lock1 = new ReentrantLock();//默认非公平锁Lock lock2 = new ReentrantLock(true);//设置公平锁
      
  • 可重入锁

    • 所有的锁都是可重入锁(递归锁)
      • 拿到外面的锁,就可以拿到里面的锁,自动获得
  • 自旋锁

    • spinlock
    • 不断尝试,直到解锁为止
  • 死锁

    • 死锁?
      • 两个或两个以上的进程在执行过程中,由于竞争资源或者由于彼此通信而造成的一种阻塞现象
      • 四要素
        • 互斥条件
        • 请求和保持条件
        • 不可剥夺条件
        • 环路等待条件
      • 解决死锁?
        • 破坏四要素
    • 死锁排查?
      • 查询堆栈信息
        • 使用jdk bin目录下,jps定位进程号 jps -l
        • 使用jdstack 进程号 找到死锁问题
      • 查看日志
  • 0
    点赞
  • 1
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值