新版Java面试专题视频教程——多线程篇①

新版Java面试专题视频教程——多线程篇①

    • = = = = = = = = = = = = = = = = Java多线程相关面试题 = = = = = = = = = = = = = = = =
      • 0. 问题汇总
        • 0.1 线程的基础知识
        • 0.2 线程中并发安全
      • 1.线程的基础知识
        • 1.1 线程和进程的区别?
        • 1.2 并行和并发有什么区别?
        • 1.3 创建线程的四种方式
        • 1.4 runnable 和 callable 有什么区别
        • 1.5 线程的 run()和 start()有什么区别?
        • 1.6 线程包括哪些状态,状态之间是如何变化的
        • 1.7 新建 T1、T2、T3 三个线程,如何保证它们按顺序执行?
        • 1.8 notify()和 notifyAll()有什么区别?
        • 1.9 在 java 中 wait 和 sleep 方法的不同?
        • 1.10 如何停止一个正在运行的线程?
      • 2.线程中并发锁
        • 2.1 讲一下synchronized关键字的底层原理?
          • 2.1.1 基本使用
          • 2.1.2 Monitor
        • 2.2 synchronized关键字的底层原理-进阶
          • 2.2.1 对象的内存结构
          • 2.2.2 MarkWord
          • 2.2.3 再说Monitor重量级锁
          • 2.2.4 轻量级锁
          • 2.2.5 偏向锁
          • 2.2.6 Monitor实现的锁属于重量级锁,你了解过锁升级吗?——参考回答
        • 2.3你谈谈 JMM(Java 内存模型)
        • 2.4 CAS 你知道吗?
          • 2.4.1 概述及基本工作流程
          • 2.4.2 CAS 底层实现
          • 2.4.3 乐观锁和悲观锁的区别
        • 2.5 请谈谈你对 volatile 关键字的理解
          • 2.5.1 保证线程间的可见性
          • 2.5.2 禁止进行指令重排序
        • 2.6 什么是AQS?
          • 2.6.1 概述
          • 2.6.2 基本工作机制
        • 2.5 ReentrantLock的实现原理
          • 2.5.1 概述
          • 2.5.2 实现原理
        • 2.6 synchronized和Lock有什么区别 ?
        • 2.7 死锁产生的条件是什么?
        • 2.8 如何进行死锁诊断?
        • 2.10 ConcurrentHashMap
        • 2.11 导致并发程序出现问题的根本原因是什么

在这里插入图片描述

在这里插入图片描述

= = = = = = = = = = = = = = = = Java多线程相关面试题 = = = = = = = = = = = = = = = =

0. 问题汇总

0.1 线程的基础知识

线程与进程的区别
并行与并发的区别
线程创建的方式有哪些
runnable和callable有什么区别
线程包括哪些状态
状态之间是如何变化的
在java中wait和sleep方法的不同
新建三个线程,如何保证它们按顺序执行
notify和notifyAll有什么区别
线程的run()和start()有什么区别
如何停止一个正在运行的线程

0.2 线程中并发安全

synchronized关键字的底层原理
你谈谈JMM (Java 内存模型)
CAS你知道吗
什么是AQS
ReentrantLock的实现原理
synchronized和Lock有什么区别
死锁产生的条件是什么
如何进行死锁诊断
请谈谈你对volatile的理解
聊一下ConcurrentHashMap
导致并发程序出现问题的根本原因是什么

在这里插入图片描述

1.线程的基础知识

1.1 线程和进程的区别?

难易程度:☆☆
出现频率:☆☆☆

程序由指令和数据组成,但这些指令要运行,数据要读写,就必须将指令加载至CPU,数据加载至内存。在指令运行过程中还需要用到磁盘、网络等设备。进程就是用来加载指令、管理内存、管理IO的。

当一个程序被运行,从磁盘加载这个程序的代码至内存,这时就开启了一个进程。

在这里插入图片描述

一个进程之内可以分为一到多个线程。

一个线程就是一个指令流,将指令流中的一条条指令以一定的顺序交给 CPU 执行

Java 中,线程作为最小调度单位,进程作为资源分配的最小单位。

在 windows 中进程是不活动的,只是作为线程的容器

在这里插入图片描述

二者对比

  • 进程是正在运行程序的实例,进程中包含了线程,每个线程执行不同的任务

  • 不同的进程使用不同的内存空间,在当前进程下的所有线程可以共享内存空间

  • 线程更轻量,线程上下文切换成本一般上要比进程上下文切换低(上下文切换指的是从一个线程切换到另一个线程)

在这里插入图片描述

1.2 并行和并发有什么区别?

难易程度:☆ ☆
出现频率:☆ ☆

在这里插入图片描述

多核CPU

在这里插入图片描述

并行和并发有什么区别?

  • 并发(concurrent) 是同一时间应对(dealing with)多件事情的能力
  • 并行(parallel) 是同一时间动手做(doing) 多件事情的能力

举例:
家庭主妇做饭、打扫卫生、给孩子喂奶,她 一个人轮流交替做这多件事,这时就是并发
家庭主妇雇了个保姆,她们一起这些事,这时既有并发,也有并行(这时会产生竞争,例如(并发)锅只有一口,一个人用锅时,另一个人就得等待)
雇了3个保姆,一个专做饭、一个专打扫卫生、一个专喂奶,互不干扰,这时是并行

在这里插入图片描述

1.3 创建线程的四种方式

难易程度:☆ ☆
出现频率:☆ ☆ ☆ ☆

参考回答:

共有四种方式可以创建线程,分别是:

继承Thread类、实现runnable接口、实现Callable接口、线程池创建线程(项目中常用)

详细创建方式参考下面代码:

继承Thread类


public class MyThread extends Thread {

    @Override
    public void run() {
        System.out.println("MyThread...run...");
    }
    public static void main(String[] args) {

        // 创建MyThread对象
        MyThread t1 = new MyThread() ;
        MyThread t2 = new MyThread() ;

        // 调用start方法启动线程
        t1.start();
        t2.start();
    }  
}

实现runnable接口


public class MyRunnable implements Runnable{

    @Override
    public void run() {
        System.out.println("MyRunnable...run...");
    }
    public static void main(String[] args) {
        // 创建MyRunnable对象
        MyRunnable mr = new MyRunnable() ;

        // 创建Thread对象
        Thread t1 = new Thread(mr) ;
        Thread t2 = new Thread(mr) ;

        // 调用start方法启动线程
        t1.start();
        t2.start();
    }
}

实现Callable接口 适用于:当前线程执行完 需要返回结


public class MyCallable implements Callable<String> {

    @Override
    public String call() throws Exception {//返回值与泛型相同
        System.out.println("MyCallable...call...");
        return "OK";
    }

    public static void main(String[] args) throws ExecutionException, InterruptedException {

        // 创建MyCallable对象
        MyCallable mc = new MyCallable() ;

        // 创建 FutureTask 包装mycallable
        FutureTask<String> ft = new FutureTask<String>(mc) ;

        // 创建Thread对象
        Thread t1 = new Thread(ft) ;
        Thread t2 = new Thread(ft) ;

        // 调用start方法启动线程
        t1.start();

        // 调用ft的get方法 就能获取执行结果
        String result = ft.get();

        // 输出
        System.out.println(result);
    }
}

线程池创建线程

public class MyExecutors implements Runnable{
    @Override
    public void run() {
        System.out.println("MyRunnable...run...");
    }
    public static void main(String[] args) {
        // 创建线程池对象
        ExecutorService threadPool = Executors.newFixedThreadPool(3);
        threadPool.submit(new MyExecutors()) ;//提交任务

        // 关闭线程池
        threadPool.shutdown();
    }
}

在这里插入图片描述

1.4 runnable 和 callable 有什么区别

难易程度:☆☆
出现频率:☆☆☆

参考回答:

  1. Runnable 接口run方法没有返回值;Callable接口call方法有返回值,是个泛型,和Future、FutureTask配合可以用来获取异步执行的结果

  2. Callable接口支持返回执行结果,需要调用FutureTask.get()得到,此方法会阻塞主进程的继续往下执行,如果不调用不会阻塞。

  3. Callable接口的call()方法允许throw抛出异常;而Runnable接口的run()方法的异常只能在内部消化(try catch),不能继续上抛

在这里插入图片描述

1.5 线程的 run()和 start()有什么区别?

难易程度:☆☆
出现频率:☆☆

start(): 用来启动线程,通过该线程调用run方法执行run方法中所定义的逻辑代码。start方法只能被调用一次

run(): 封装了要被线程执行的代码,普通方法当然 可以被调用多次

在这里插入图片描述

1.6 线程包括哪些状态,状态之间是如何变化的

难易程度:☆☆☆
出现频率:☆☆☆☆

线程的状态可以参考JDK中的Thread类中的枚举State

在这里插入图片描述


public enum State {
    /**
     * 尚未启动的线程的线程状态
     */
    NEW,

    /**
     * 可运行线程的线程状态。处于可运行状态的线程正在 Java 虚拟机中执行,
     * 但它可能正在等待来自操作系统的其他资源,例如处理器。
     */
    RUNNABLE,

    /**
     * 线程阻塞等待监视器锁的线程状态。
     * 处于阻塞状态的线程正在等待监视器锁进入同步块/方法或在调
     * 用Object.wait后重新进入同步块/方法。
     */
    BLOCKED,

    /**
     * 等待线程的线程状态。由于调用以下方法之一,线程处于等待状态:
* Object.wait没有超时
     * 没有超时的Thread.join
     * LockSupport.park
     * 处于等待状态的线程正在等待另一个线程执行特定操作。
     * 例如,一个对对象调用Object.wait()的线程正在等待
     * 另一个线程对该对象调用Object.notify()			
    * 或Object.notifyAll() 。已调用Thread.join()的线程正在等待指定线程终止。
     */
    WAITING,

    /**
     * 具有指定等待时间的等待线程的线程状态。
     * 由于以指定的正等待时间调用以下方法之一,线程处于定时等待状态:
* Thread.sleep
* Object.wait超时
* Thread.join超时
* LockSupport.parkNanos
* LockSupport.parkUntil
     * </ul>
     */
    TIMED_WAITING,

    /**
     * 已终止线程的线程状态。线程已完成执行
     */
    TERMINATED;
}

状态之间是如何变化的

分别是

  • 新建

    • 当一个线程对象被创建,但还未调用 start 方法时处于新建状态

    • 此时未与操作系统底层线程关联

    • 在这里插入图片描述

  • 可运行

    • 调用了 start 方法,就会由新建进入可运行

    • 此时与底层线程关联,由操作系统调度执行

  • 终结

    • 线程内代码已经执行完毕,由可运行进入终结

    • 此时会取消与底层线程关联

  • 阻塞

    • 当获取锁失败后,由可运行进入 Monitor 的阻塞队列阻塞,此时不占用 cpu 时间

    • 当持锁线程释放锁时,会按照一定规则唤醒阻塞队列中的阻塞线程,唤醒后的线程进入可运行状态

    • 在这里插入图片描述

  • 等待

    • 当获取锁成功后,但由于条件不满足,调用了 wait() 方法,此时从可运行状态释放锁进入 Monitor 等待集合等待,同样不占用 cpu 时间

    • 当其它持锁线程调用 notify() 或 notifyAll() 方法,会按照一定规则唤醒等待集合中的等待线程,恢复为可运行状态

在这里插入图片描述

  • 有时限等待

  • 当获取锁成功后,但由于条件不满足,调用了 wait(long) 方法,此时从可运行状态释放锁进入 Monitor 等待集合进行有时限等待,同样不占用 cpu 时间

  • 当其它持锁线程调用 notify() 或 notifyAll() 方法,会按照一定规则唤醒等待集合中的有时限等待线程,恢复为可运行状态,并重新去竞争锁

  • 如果等待超时,也会从有时限等待状态恢复为可运行状态,并重新去竞争锁

  • 还有一种情况是调用 sleep(long) 方法也会从可运行状态进入有时限等待状态,

但与 Monitor 无关,不需要主动唤醒,超时时间到自然恢复为可运行状态

在这里插入图片描述

在这里插入图片描述

在这里插入图片描述

1.7 新建 T1、T2、T3 三个线程,如何保证它们按顺序执行?

难易程度:☆☆
出现频率:☆☆☆

在多线程中有多种方法让线程按特定顺序执行,你可以用线程类的join()方法在一个线程中启动另一个线程,另外一个线程完成该线程继续执行。

在这里插入图片描述

代码举例:

为了确保三个线程的顺序你应该先启动最后一个(T3调用T2,T2调用T1),这样T1就会先完成而T3最后完成

public class JoinTest {
    public static void main(String[] args) {
        // 创建线程对象
        Thread t1 = new Thread(() -> {
            System.out.println("t1");
        }) ;
        Thread t2 = new Thread(() -> {
            try {
                t1.join();   // 加入线程t1,只有t1线程执行完毕以后,再次执行该线程
            } catch (InterruptedException e) {
                e.printStackTrace();
            }
            System.out.println("t2");
        }) ;

        Thread t3 = new Thread(() -> {
            try {
                t2.join();   // 加入线程t2,只有t2线程执行完毕以后,再次执行该线程
            } catch (InterruptedException e) {
                e.printStackTrace();
            }
            System.out.println("t3");
        }) ;

        // 启动线程
        t1.start();
        t2.start();
        t3.start();
    }
}

在这里插入图片描述

1.8 notify()和 notifyAll()有什么区别?

难易程度:☆☆
出现频率:☆☆

notifyAll:唤醒所有wait的线程

notify:只 随机唤醒一个wait 线程

package com.itheima.basic;
public class WaitNotify {

    static boolean flag = false;
    static Object lock = new Object();

    public static void main(String[] args) {

        Thread t1 = new Thread(() -> {
            synchronized (lock){
                while (!flag){
                    System.out.println(Thread.currentThread().getName()+"...wating...");
                    try {
                        lock.wait();
                    } catch (InterruptedException e) {
                        e.printStackTrace();
                    }
                }
                System.out.println(Thread.currentThread().getName()+"...flag is true");
            }
        });

        Thread t2 = new Thread(() -> {
            synchronized (lock){
                while (!flag){
                    System.out.println(Thread.currentThread().getName()+"...wating...");
                    try {
                        lock.wait();
                    } catch (InterruptedException e) {
                        e.printStackTrace();
                    }
                }
                System.out.println(Thread.currentThread().getName()+"...flag is true");
            }
        });

        Thread t3 = new Thread(() -> {
            synchronized (lock) {
                System.out.println(Thread.currentThread().getName() + " hold lock");
                lock.notifyAll();
                flag = true;
                try {
                    Thread.sleep(2000);
                } catch (InterruptedException e) {
                    e.printStackTrace();
                }
            }
        });
        t1.start();
        t2.start();
        t3.start();
    }
}

在这里插入图片描述

1.9 在 java 中 wait 和 sleep 方法的不同?

难易程度:☆☆☆
出现频率:☆☆☆

参考回答:

共同点

  • wait() ,wait(long) 和 sleep(long) 的效果都是让当前线程暂时放弃 CPU 的使用权,进入阻塞状态

不同点

  • 方法归属不同
    • sleep(long) 是 Thread 的静态方法
    • 而 wait(),wait(long) 都是 Object 的成员方法,每个对象都有
  • 醒来时机不同
    • 执行 sleep(long) 和 wait(long) 的线程都会在等待相应毫秒后醒来
    • wait(long) 和 wait() 还可以被 notify 唤醒,wait() 如果不唤醒就一直等下去
    • 它们都可以被打断唤醒
  • 锁特性不同(重点)
    • wait 方法的调用必须先获取 wait 对象的锁,而 sleep 则无此限制
    • wait 方法执行后会释放对象锁,允许其它线程获得该对象锁(我放弃 cpu,但你们还可以用)
    • 而 sleep 如果在 synchronized 代码块中执行,并不会释放对象锁(我放弃 cpu,你们也用不了)

代码示例:

public class WaitSleepCase {
    static final Object LOCK = new Object();
    public static void main(String[] args) throws InterruptedException {
        sleeping();
    }
    private static void illegalWait() throws InterruptedException {
        LOCK.wait();//没和synchronized配合使用 直接报错
    }
    private static void illegalWait() throws InterruptedException {
        synchronized(LOCK){
            LOCK.wait();
        }
    }
//wait 方法执行后会释放对象锁,允许其它线程获得该对象锁(我放弃 cpu,但你们还可以用)
    private static void waiting() throws InterruptedException {
        Thread t1 = new Thread(() -> {
            synchronized (LOCK) {
                try {
                    get("t").debug("waiting...");
                    LOCK.wait(5000L);
                        //5秒之后 从阻塞跳出 可运行状态
                } catch (InterruptedException e) {
                    get("t").debug("interrupted...");
                    e.printStackTrace();
                }
            }
        }, "t1");
        t1.start();

        Thread.sleep(100);
        synchronized (LOCK) {
            main.debug("other...");
        }
    }
//而 sleep 如果在 synchronized 代码块中执行,并不会释放对象锁(我放弃 cpu,你们也用不了)
    private static void sleeping() throws InterruptedException {
        Thread t1 = new Thread(() -> {
            synchronized (LOCK) {
                try {
                    get("t").debug("sleeping...");
                    Thread.sleep(5000L);
                } catch (InterruptedException e) {
                    get("t").debug("interrupted...");
                    e.printStackTrace();
                }
            }
        }, "t1");
        t1.start();

        Thread.sleep(100);
        synchronized (LOCK) {
            main.debug("other...");
        }
    }
}

在这里插入图片描述

1.10 如何停止一个正在运行的线程?

难易程度:☆☆
出现频率:☆☆

参考回答:

有三种方式可以停止线程

  • 使用退出标志,使线程正常退出,也就是当run方法完成后线程终止
  • 使用stop方法强行终止(不推荐,方法已作废)
  • 使用interrupt方法中断线程

代码参考如下:

使用退出标志,使线程正常退出


public class MyInterrupt1 extends Thread {
    volatile boolean flag = false ;     // 线程执行的退出标记

    @Override
    public void run() {
        while(!flag) {
            System.out.println("MyThread...run...");
            try {
                Thread.sleep(3000);
            } catch (InterruptedException e) {
                e.printStackTrace();
            }
        }
    }
    
    public static void main(String[] args) throws InterruptedException {
        // 创建MyThread对象
        MyInterrupt1 t1 = new MyInterrupt1() ;
        t1.start();

        // 主线程休眠6秒
        Thread.sleep(6000);
            //6秒之后 正好打印两次 改flag 退出
        // 更改标记为true
        t1.flag = true ;
    }
}

在这段代码中,主线程创建了一个名为 t1MyInterrupt1 线程,并启动了它。然后主线程休眠了6秒钟。在这6秒钟内,MyInterrupt1 线程会一直循环执行打印语句 System.out.println("MyThread...run...");,每次循环之间休眠3秒钟。

当主线程休眠6秒钟后,主线程将 t1 线程的 flag 属性设置为 true,这样在 MyInterrupt1 线程的下一次循环迭代时,由于 flag 被设置为 true,循环条件 while(!flag) 将不再满足,MyInterrupt1 线程退出循环并终止。

因此,在主线程休眠结束之后,会再打印一次 "MyThread...run...",然后 MyInterrupt1 线程结束。这就解释了为什么会打印两次 "MyThread...run..."

使用stop方法强行终止(不推荐)


public class MyInterrupt2 extends Thread {

    volatile boolean flag = false ;     // 线程执行的退出标记

    @Override
    public void run() {
        while(!flag) {
            System.out.println("MyThread...run...");
            try {
                Thread.sleep(3000);
            } catch (InterruptedException e) {
                e.printStackTrace();
            }
        }
    }

    public static void main(String[] args) throws InterruptedException {

        // 创建MyThread对象
        MyInterrupt2 t1 = new MyInterrupt2() ;
        t1.start();

        // 主线程休眠2秒
        Thread.sleep(6000);

        // 调用stop方法
        t1.stop();
    }
}

使用interrupt方法中断线程


package com.itheima.basic;

public class MyInterrupt3 {

    public static void main(String[] args) throws InterruptedException {

        //1.打断阻塞的线程
        /*Thread t1 = new Thread(()->{
            System.out.println("t1 正在运行...");
            try {
                Thread.sleep(5000);
            } catch (InterruptedException e) {
                e.printStackTrace();
            }
        }, "t1");
        t1.start();
        Thread.sleep(500);
        t1.interrupt();
        System.out.println(t1.isInterrupted());*/


        //2.打断正常的线程
        Thread t2 = new Thread(()->{
            while(true) {
                Thread current = Thread.currentThread();
                boolean interrupted = current.isInterrupted();
                if(interrupted) {//默认为false 调用interrupt之后改为true
                    System.out.println("打断状态:"+interrupted);
                    break;
                }
            }
        }, "t2");
        t2.start();
        Thread.sleep(500);
        t2.interrupt();

    }
}

在这里插入图片描述

2.线程中并发锁

2.1 讲一下synchronized关键字的底层原理?

难易程度:☆☆☆☆☆
出现频率:☆☆☆

2.1.1 基本使用

如下抢票的代码,如果不加锁,就会出现超卖或者一张票卖给多个人

Synchronized【对象锁】采用互斥的方式让同一时刻至多只有一个线程能持有【对象锁】,其它线程再想获取这个【对象锁】时就会阻塞住


public class TicketDemo {
    static Object lock = new Object();
    int ticketNum = 10;

    public synchronized void getTicket() {
        synchronized (this) {
            if (ticketNum <= 0) {
                return;
            }
            System.out.println(Thread.currentThread().getName() +
            							  "抢到一张票,剩余:" + ticketNum);
            // 非原子性操作
            ticketNum--;
        }
    }

    public static void main(String[] args) {
        TicketDemo ticketDemo = new TicketDemo();
        for (int i = 0; i < 20; i++) {
            new Thread(() -> {
                ticketDemo.getTicket();
            }).start();
        }
    }
}

在这里插入图片描述

2.1.2 Monitor

Monitor 被翻译为监视器,是由jvm提供,c++语言实现

在代码中想要体现monitor需要借助javap命令查看clsss的字节码,比如以下代码:

public class SyncTest {
    static final Object lock = new Object();
    static int counter = 0;
    public static void main(String[] args) {
        synchronized (lock) {
            counter++;
        }
    }
}

找到这个类的class文件,在class文件目录下执行

javap -v SyncTest.class,反编译效果如下:

在这里插入图片描述

monitorenter 上锁开始的地方
monitorexit 解锁的地方
其中被monitorenter和monitorexit 包围住的指令就是上锁的代码
有两个monitorexit的原因, 第二个monitorexit是为了防止锁住的代码抛异常后不能及时释放锁

在使用了synchornized代码块时需要指定一个对象,所以synchornized也被称为对象锁

monitor主要就是跟这个对象产生关联,如下图

在这里插入图片描述

Monitor内部具体的存储结构:

  • Owner:存储当前获取锁的线程的,只能有一个线程可以获取

  • EntryList:关联没有抢到锁(Owner为null)的线程,处于Blocked状态的线程

  • WaitSet:关联调用了wait方法的线程,处于Waiting状态的线程

具体的流程:

  • 代码进入synchorized代码块,先让lock(对象锁)关联monitor,然后判断Owner是否有线程持有

  • 如果没有线程持有(Owner为null),则让当前线程持有,表示该线程获取锁成功

  • 如果有线程持有,则让当前线程进入entryList进行阻塞,如果Owner持有的线程已经释放了锁,在EntryList中的线程去竞争锁的持有权(非公平)

  • 如果代码块中调用了wait()方法,则会进去WaitSet中进行等待

参考回答:

  • Synchronized【对象锁】采用互斥的方式让同一时刻至多只有一个线程能持有【对象锁】

  • 它的底层由monitor实现的,monitor是jvm级别的对象( C++实现),线程获得锁需要使用对象(锁)关联monitor

  • 在monitor内部有三个属性,分别是owner、entrylist、waitset

  • 其中owner是关联的获得锁的线程,并且只能关联一个线程;entrylist关联的是处于阻塞状态的线程;waitset关联的是处于Waiting状态的线程

在这里插入图片描述

2.2 synchronized关键字的底层原理-进阶

Monitor实现的锁属于重量级锁(性能低),你了解过锁升级吗?

  • Monitor实现的锁属于重量级锁,里面涉及到了用户态和内核态的切换、进程的上下文切换,成本较高,性能比较低。
  • 在JDK 1.6引入了两种新型锁机制:偏向锁和轻量级锁,它们的引入是为了解决在没有多线程竞争或基本没有竞争的场景下因使用传统锁机制带来的性能开销问题。
2.2.1 对象的内存结构

HotSpot虚拟机中,对象在内存中存储的布局可分为3块区域:对象头(Header)、实例数据(Instance Data)和对齐填充

在这里插入图片描述

我们需要重点分析MarkWord对象头
在这里插入图片描述

2.2.2 MarkWord

在这里插入图片描述

hashcode:25位的 对象标识Hash码
age:对象分 代回收年龄占4位
biased_lock:偏向 锁标识,占1位 ,0表示没有开始偏向锁,1表示开启了偏向锁
thread: 持有偏向锁的线程ID,占23位
epoch: 偏向时间戳,占2位
ptr_to_lock_record: 轻量级锁状态下,指向 栈中锁记录的指针,占30位
ptr_to_heavyweight_monitor: 重量级锁状态下,指向对 象监视器Monitor的指针,占30位

我们可以通过lock的标识,来判断是哪一种锁的等级

  • 后三位是001表示无锁

  • 后三位是101表示偏向锁

  • 后两位是00表示轻量级锁

  • 后两位是10表示重量级锁

在这里插入图片描述

2.2.3 再说Monitor重量级锁

每个 Java 对象都可以关联一个 Monitor 对象,如果使用 synchronized 给对象上锁(重量级)之后,该 对象头的Mark Word 中就被设置 指向 Monitor 对象的指针

简单说就是:每个对象的对象头都可以设置monitor的指针,让对象与monitor产生关联
在这里插入图片描述

2.2.4 轻量级锁

在很多的情况下,在Java程序运行时,同步块中的代码(同一线程持有锁)都是不存在竞争的,不同的线程交替的执行同步块中的代码。这种情况下,用重量级锁是没必要的。因此JVM引入了轻量级锁的概念。


static final Object obj = new Object();

public static void method1() {
    synchronized (obj) {
        // 同步块 A
        method2();
    }
}

public static void method2() {
    synchronized (obj) {
        // 同步块 B
    }
}

加锁的流程

1.在线程执行时线程栈中创建一个Lock Record锁记录,将其obj字段指向锁对象

2.通过CAS指令将Lock Record的地址存储在对象头的****mark word(hashcode)中(数据进行交换),如果对象处于无锁状态则修改成功,代表该线程获得了轻量级锁。

3.如果是当前线程已经持有该锁了,代表这是一次锁重入(比如method1调用2)。

设置Lock Record第一部分为null,起到了一个重入计数器的作用。

4.如果CAS修改失败,说明发生了竞争,需要膨胀为重量级锁。

解锁过程

1.遍历线程栈,找到所有obj字段等于当前锁对象的Lock Record

2.如果Lock Record的Mark Word为null,代表这是一次重入,要减一,将obj设置为null后continue

3.如果Lock Record的 Mark Word不为null****(无重入),则利用CAS指令将对象头的mark word恢复成为无锁状态。如果失败则膨胀为重量级锁。


在这里插入图片描述

2.2.5 偏向锁

轻量级锁在没有竞争时(就自己这个线程),每次重入仍然需要执行 CAS 操作。

Java 6 中引入了偏向锁来做进一步优化:只有第一次使用 CAS 将线程 ID 设置到对象的 Mark Word 头,之后发现这个线程 ID 是自己的就表示没有竞争,不用重新 CAS。

以后只要不发生竞争,这个对象就归该线程所有


static final Object obj = new Object();

public static void m1() {
    synchronized (obj) {
        // 同步块 A
        m2();
    }
}

public static void m2() {
    synchronized (obj) {
        // 同步块 B
        m3();
    }
}

public static void m3() {
    synchronized (obj) {
    }
}

加锁的流程

1.在线程栈中创建一个Lock Record,将其obj字段指向锁对象

2.通过CAS指令将Lock Record的线程id存储在对象头的mark word中,同时也设置偏向锁的标识为101,如果对象处于无锁状态则修改成功,代表该线程获得了偏向锁。

3.如果是当前线程已经持有该锁了,代表这是一次锁重入。设置Lock Record第一部分为null,起到了一个重入计数器的作用。与轻量级锁不同的时,这里不会再次进行cas操作,只是判断对象头中的线程id是否是自己,因为缺少了cas操作,性能相对轻量级锁更好一些

解锁流程参考轻量级锁

在这里插入图片描述

2.2.6 Monitor实现的锁属于重量级锁,你了解过锁升级吗?——参考回答

Java中的synchronized有偏向锁、轻量级锁、重量级锁三种形式,

分别对应了锁只被一个线程持有、不同线程交替持有锁、多线程竞争锁三种情况。

X描述
重量级锁底层使用的Monitor实现,里面涉及到了用户态和内核态的切换、进程的上下文切换,成本较高,性能比较低。
轻量级锁线程加锁的时间是错开的(也就是没有竞争),可以使用轻量级锁来优化。轻量级修改了对象头的锁标志,相对重量级锁性能提升很多。每次修改都是CAS操作,保证原子性
偏向锁一段很长的时间内都只被一个线程使用锁,可以使用了偏向锁,在第一次获得锁时,会有一个CAS操作,之后该线程再获取锁,只需要判断mark word中是否是自己的线程id即可,而不是开销相对较大的CAS命令

一旦锁发生了竞争,都会升级为重量级锁

在这里插入图片描述

2.3你谈谈 JMM(Java 内存模型)

难易程度:☆☆☆
出现频率:☆☆☆

JMM(Java Memory Model)Java内存模型,是java虚拟机规范中所定义的一种内存模型

Java内存模型(Java Memory Model)描述了Java程序中各种变量(线程共享变量)的访问规则,以及在JVM中将变量存储到内存和从内存中读取变量这样的底层细节。

每个线程创建时,都会分配 工作内存(存储线程内的私有数据);

每个线程只能访问自己的工作内存;无法互相访问,

多个线程想要同步,只能通过主内存同步线程间的数据;

在这里插入图片描述

特点:

  1. 所有的共享变量都存储于主内存(计算机的RAM)这里所说的变量指的是实例变量和类变量。不包含局部变量,因为局部变量是线程私有的,因此不存在竞争问题。

  2. 每一个线程还存在自己的工作内存,线程的工作内存,保留了被线程使用的变量的工作副本。

  3. 线程对变量的所有的操作(读,写)都必须在工作内存中完成,而不能直接读写主内存中的变量,不同线程之间也不能直接访问对方工作内存中的变量,线程间变量的值的传递需要通过主内存完成。

在这里插入图片描述

2.4 CAS 你知道吗?

难易程度:☆☆☆
出现频率:☆☆

2.4.1 概述及基本工作流程

CAS的全称是: Compare And Swap(比较再交换),它体现的一种乐观锁的思想,在无锁情况下保证线程操作共享数据的原子性

在JUC( java.util.concurrent )包下实现的很多类都用到了CAS操作

  • AbstractQueuedSynchronizer(AQS框架)

  • AtomicXXX类

例子:

我们还是基于刚才学习过的JMM内存模型进行说明

  • 线程1与线程2都从主内存中获取变量int a = 100,同时读到各个线程的工作内存中

在这里插入图片描述

一个 当前内存值V旧的预期值A、即 将更新的值B当且仅当旧的预期值 A和内存值V相同时,将内 存值修改为B并返回true, 否则什么都不做,并返回false。
如果CAS操作失败,通过 自旋的方式等待并再次尝试,直到成功

  • 线程1操作:V:int a = 100,A:int a = 100,B:修改后的值:int a = 101 (a++)

  • 线程1拿A的值与主内存V的值进行比较,判断是否相等

  • 如果相等,则把B的值101更新到主内存中

在这里插入图片描述

  • 线程2操作:V:int a = 101,A:int a = 100,B:修改后的值:int a = 99(a–)

  • 线程2拿A的值与主内存V的值进行比较,判断是否相等(目前不相等,因为线程1已更新V的值99)

  • 不相等,则线程2更新失败

在这里插入图片描述

  • 自旋锁****操作

  • 因为没有加锁,所以线程不会陷入阻塞,效率较高

  • 如果竞争激烈,重试频繁发生,效率会受影响

在这里插入图片描述

自旋:需要不断尝试获取共享内存V中最新的值,然后再在新的值的基础上进行更新操作,如果失败就继续尝试获取新的值,直到更新成功

在这里插入图片描述

2.4.2 CAS 底层实现

CAS 底层依赖于一个 Unsafe 类来直接调用操作系统底层的 CAS 指令

都是native修饰的方法,由系统提供的接口执行,并非java代码实现,一般的思路也都是自旋锁实现

在这里插入图片描述

在java中比较常见使用有很多,

比如ReentrantLock和Atomic开头的线程安全类,都调用了Unsafe中的方法

  • ReentrantLock中的一段CAS代码

在这里插入图片描述

在这里插入图片描述

在这里插入图片描述

2.4.3 乐观锁和悲观锁的区别
  • CAS 是基于乐观锁的思想:最乐观的估计,

不怕别的线程来修改共享变量,就算改了也没关系,我吃亏点再重试(自旋)呗。

  • synchronized 是基于悲观锁的思想:最悲观的估计,

得防着其它线程来修改共享变量,我上了锁你们都别想改,我改完了解开锁,你们才有机会。

在这里插入图片描述

2.5 请谈谈你对 volatile 关键字的理解

难易程度:☆☆☆
出现频率:☆☆☆

一旦一个共享变量(类的成员变量、类的静态成员变量)被volatile修饰之后,那么就具备了两层语义:

在这里插入图片描述

2.5.1 保证线程间的可见性

保证了不同线程对这个变量进行操作时的可见性,即一个线程修改了某个变量的值,防止编译器优化发生,这新值对其他线程来说是立即可见的,volatile关键字会强制将修改的值立即写入主存。

一个典型的例子:永不停止的循环


package com.itheima.basic;
// 可见性例子
// -Xint
public class ForeverLoop {
    static boolean stop = false;

    public static void main(String[] args) {
        new Thread(() -> {
            try {
                Thread.sleep(100);
            } catch (InterruptedException e) {
                e.printStackTrace();
            }
            stop = true;
            System.out.println("modify stop to true...");
        }).start();
        foo();
    }

    static void foo() {
        int i = 0;
        while (!stop) {
            i++;
        }
        System.out.println("stopped... c:"+ i);
    }
}

在这里插入图片描述

当执行上述代码的时候,发现foo()方法中的循环是结束不了的,也就说读取不到共享变量的值结束循环

主要是因为在JVM虚拟机中有一个JIT(即时编辑器)给代码做了优化

上述代码
while (!stop) { i++; }
在很短的时间内,这个代码执行的 次数太多了,当达到了一个阈值,JIT就会优化此代码,如下:
while (true) { i++; }
当把 代码优化成这样子以后,及时stop变量改变为了false也依然停止不了循环

解决方案:

第一:

在程序运行的时候加入vm参数-Xint表示禁用即时编辑器,不推荐,得不偿失(其他程序还要使用

在这里插入图片描述

在这里插入图片描述

第二:

修饰stop变量的时候加上volatile,表示当前代码禁用了即时编辑器,问题就可以解决,代码如下:

static volatile boolean stop = false;

在这里插入图片描述

2.5.2 禁止进行指令重排序

用 volatile 修饰共享变量会在读、写共享变量时加入不同的屏障阻止其他读写操作越过屏障,从而达到阻止重排序的效果

在这里插入图片描述

注解 @Actor 保证方法内的代码在同一个线程下执行

引入多线程测试工具进行测试大量线程 出现上述四种可能结果

import org. openjdk.jcstress…
在这里插入图片描述
在这里插入图片描述

在这里插入图片描述

解决方案

在变量上添加volatile,禁止指令重排序,则可以解决问题

在这里插入图片描述

屏障添加的示意图

在这里插入图片描述

  • 操作加的屏障是阻止上方其它写操作越过屏障排到volatile变量写之下

  • 操作加的屏障是阻止下方其它操作越过屏障排到volatile变量读之上

其他补充

我们上面的解决方案是把volatile加在了int y这个变量上,我们能不能把它加在int x这个变量上呢?

下面代码使用volatile修饰了x变量

屏障添加的示意图

在这里插入图片描述

这样显然是不行的,主要是因为下面两个原则:

  • 写操作加的屏障是阻止上方其它写操作越过屏障排到volatile变量写之下

  • 读操作加的屏障是阻止下方其它读操作越过屏障排到volatile变量读之上

  • 两个都加volatile 行不行? 行但是 阻止指令重排序 性能会降低

所以,现在我们就可以总结一个volatile使用的小妙招:

  • 写变量让volatile修饰的变量的在代码最后位置

  • 变量让volatile修饰的变量的在代码最开始位置

在这里插入图片描述

2.6 什么是AQS?

难易程度:☆☆☆
出现频率:☆☆☆

2.6.1 概述

全称是 AbstractQueuedSynchronizer,

阻塞式锁和相关的同步器工具的框架,它是构建锁或者其他同步组件的基础框架

简单说,JUC提供的锁机制

AQS与Synchronized的区别

synchronizedAQS
关键字,c++ 语言实现java 语言实现
悲观锁,自动释放锁悲观锁,手动开启和关闭
锁竞争激烈都是重量级锁,性能差锁竞争激烈的情况下,提供了多种解决方案

AQS常见的实现类

  • ReentrantLock 阻塞式锁

  • Semaphore 信号量

  • CountDownLatch 倒计时锁

在这里插入图片描述

2.6.2 基本工作机制
  • 在AQS中维护了一个使用了volatile修饰的state属性来表示资源的状态,0表示无锁,1表示有锁

  • 提供了基于 FIFO 的等待队列,类似于 Monitor 的 EntryList

  • 条件变量来实现等待、唤醒机制,支持多个条件变量,类似于 Monitor 的 WaitSet

在这里插入图片描述

线程0来了以后,去尝试 修改state属性,如果发现state属性是0,就修改state状态为1,表示线程0 抢锁成功
线程1和线程2也会先尝试修改state属性,发现state的值 已经是1了,有其他线程持有锁,它们都会到FIFO队列中进行等待,
FIFO是一个 先进先出双向队列, head属性表示头结点,tail表示尾结点
0线程结束,把state改成0,唤醒head

如果多个线程共同去抢这个资源是如何保证原子性的呢?

在这里插入图片描述

在去修改state状态的时候,使用的cas自旋锁来保证原子性,确保只能有一个线程修改成功,修改失败的线程将会进入FIFO队列中等待

AQS是公平锁吗,还是非公平锁?

  • 新的线程与队列中的线程共同来抢资源,是非公平

  • 新的线程到队列中最后等待,只让队列中的head线程获取锁,是公平锁

比较典型的AQS实现类ReentrantLock,
默认就是非公平锁,新的线程与队列中的线程共同来抢资源

在这里插入图片描述

2.5 ReentrantLock的实现原理

难易程度:☆☆☆☆
出现频率:☆☆☆

2.5.1 概述

ReentrantLock翻译过来是可重入锁,相对于synchronized它具备以下特点:

  • 可中断,synchronized不能中断

  • 可以设置超时时间

  • 可以设置公平/非公平 锁,synchronized只有非公平

  • 支持多个条件变量

  • 与synchronized一样,都支持重入

不释放锁会死锁


在这里插入图片描述

2.5.2 实现原理

ReentrantLock主要利用 CAS+AQS队列(底层完全是AQS) 来实现。

它支持公平锁和非公平锁,两者的实现类似

构造方法接受一个可选的公平参数(默认非公平锁),当设置为true时,表示公平锁,否则为非公平锁。公平锁的效率往往没有非公平锁的效率高,在许多线程访问的情况下,公平锁表现出较低的吞吐量

查看ReentrantLock源码中的构造方法:

在这里插入图片描述

提供了两个构造方法,不带参数的默认为非公平

如果使用带参数的构造函数,并且传的值为true,则是公平

其中NonfairSync和FairSync这两个类父类都是Sync

而Sync的父类是AQS,所以可以得出ReentrantLock底层主要实现就是基于AQS来实现的

在这里插入图片描述

工作流程

在这里插入图片描述

  • 线程来抢锁后使用cas的方式修改state状态,修改状态成功为1,则让exclusiveOwnerThread属性指向当前线程,获取锁成功

  • 假如修改状态失败,则会进入双向队列中等待,head指向双向队列头部,tail指向双向队列尾部

  • 当exclusiveOwnerThread为null的时候,则会唤醒在双向队列中等待的线程

  • 公平锁则体现在按照先后顺序获取锁,非公平体现在不在排队的线程****也可以抢

在这里插入图片描述

在这里插入图片描述

2.6 synchronized和Lock有什么区别 ?

难易程度:☆☆☆☆
出现频率:☆☆☆☆

参考回答

  • 语法层面
  • synchronized 是关键字,源码在 jvm 中,用 c++ 语言实现
  • Lock 是接口,源码由 jdk 提供,用 java 语言实现
  • 使用 synchronized 时,退出同步代码块锁会自动释放, 而使用 Lock 时,需要手动调用 unlock 方法释放锁
  • 功能层面
  • 二者均属于悲观锁、都具备基本的互斥、同步、锁重入功能
  • Lock 提供了许多 synchronized 不具备的功能,例如获取等待状态、公平锁、可打断、可超时、多条件变量
  • Lock 有适合不同场景的实现,如 ReentrantLock, ReentrantReadWriteLock
  • 性能层面
  • 没有竞争时,synchronized 做了很多优化,如偏向锁、轻量级锁,性能不赖
  • 竞争激烈时,Lock 的实现通常会提供更好的性能

在这里插入图片描述

2.7 死锁产生的条件是什么?

难易程度:☆☆☆☆
出现频率:☆☆☆

死锁:一个线程需要同时获取多把锁,这时就容易发生死锁

例如:
t1 线程获得A对象锁,接下来想获取B对象的锁
t2 线程获得B对象锁,接下来想获取A对象的锁

代码如下:


package com.itheima.basic;
import static java.lang.Thread.sleep;
public class Deadlock {

    public static void main(String[] args) {
        Object A = new Object();
        Object B = new Object();
        Thread t1 = new Thread(() -> {
            synchronized (A) {
                System.out.println("lock A");
                try {
                    sleep(1000);
                } catch (InterruptedException e) {
                    throw new RuntimeException(e);
                }
                synchronized (B) {
                    System.out.println("lock B");
                    System.out.println("操作...");
                }
            }
        }, "t1");

        Thread t2 = new Thread(() -> {
            synchronized (B) {
                System.out.println("lock B");
                try {
                    sleep(500);
                } catch (InterruptedException e) {
                    throw new RuntimeException(e);
                }
                synchronized (A) {
                    System.out.println("lock A");
                    System.out.println("操作...");
                }
            }
        }, "t2");
        t1.start();
        t2.start();
    }
}

控制台输出结果

在这里插入图片描述

此时程序并没有结束,这种现象就是死锁现象…线程t1持有A的锁等待获取B锁,线程t2持有B的锁等待获取A的锁。
在这里插入图片描述

2.8 如何进行死锁诊断?

难易程度:☆☆☆
出现频率:☆☆☆

当程序出现了死锁现象,我们可以使用jdk自带的工具:jps和 jstack

  • jps:输出JVM中运行的进程状态信息
  • jstack:查看java进程内线程的堆栈信息

步骤如下:

第一:查看运行的线程 Terminal输入jps

在这里插入图片描述

第二:使用jstack查看线程运行的情况,下图是截图的关键信息

运行命令:jstack -l 46032

其他解决工具,可视化工具

  1. jconsole

用于对jvm的内存,线程,类的监控,是一个基于 jmx 的 GUI 性能监控工具

打开方式:javajdk 安装目录 bin目录下 直接启动 jconsole.exe 就行

  1. VisualVM:故障处理工具

能够监控线程,内存情况,查看方法的CPU时间和内存中的对象,已被GC的对象,反向查看分配的堆栈

打开方式:java 安装目录 bin目录下 直接启动 jvisualvm.exe就行


在这里插入图片描述

2.10 ConcurrentHashMap

难易程度:☆☆☆
出现频率:☆☆☆☆

ConcurrentHashMap 是一种线程安全的高效Map集合

底层数据结构:

  • JDK1.7底层采用分段的数组+链表实现

  • JDK1.8 采用的数据结构跟HashMap1.8的结构一样,数组+链表/红黑二叉树

(1) JDK1.7中concurrentHashMap

数据结构

在这里插入图片描述

提供了一个 segment数组,在初始化ConcurrentHashMap 的时候可以 指定数组的长度,默认是16,一旦 初始化之后中间不可扩容
在每个segment中都可以 挂一个HashEntry数组,数组里面可以存储具体的元素,HashEntry数组是可以扩容的
在HashEntry 存储的数组中存储的元素,如果发生冲突,则可以挂单向链表

存储流程

在这里插入图片描述

  • 先去计算key的hash值,然后确定segment数组下标

  • 再通过hash值确定hashEntry数组中的下标存储数据

  • 在进行操作数据的之前,会先判断当前segment对应下标位置是否有线程进行操作,为了线程安全使用的是ReentrantLock进行加锁,如果获取锁是被会使用cas自旋锁进行尝试

(2) JDK1.8中concurrentHashMap

在JDK1.8中,放弃了Segment臃肿的设计,数据结构跟HashMap的数据结构是一样的:数组+红黑树+链表

采用 CAS + Synchronized来保证并发安全进行实现

  • CAS控制数组节点的添加

  • synchronized只锁定当前链表或红黑二叉树的首节点,只要hash不冲突,就不会产生并发的问题 , 效率得到提升

在这里插入图片描述

在这里插入图片描述

2.11 导致并发程序出现问题的根本原因是什么

难易程度:☆☆☆
出现频率:☆☆☆

Java并发编程三大特性

  • 原子性

  • 可见性

  • 有序性

(1)原子性

一个线程在CPU中操作不可暂停,也不可中断,要不执行完成,要不不执行

比如,如下代码能保证原子性吗?

在这里插入图片描述

以上代码会出现超卖或者是一张票卖给同一个人,执行并不是原子性的

解决方案:

1.synchronized:同步加锁

2.JUC里面的lock:加锁

(2)内存可见性

内存可见性:让一个线程对共享变量的修改对另一个线程可见

比如,以下代码不能保证内存可见性

在这里插入图片描述

解决方案:

  • synchronized

  • volatile(推荐)

  • LOCK

(3)有序性

指令重排:处理器为了提高程序运行效率,可能会对输入代码进行优化,它不保证程序中各个语句的执行先后顺序同代码中的顺序一致,但是它会保证程序最终执行结果和代码顺序执行的结果是一致的

还是之前的例子,如下代码:

在这里插入图片描述

解决方案:

  • volatile

在这里插入图片描述

本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若转载,请注明出处:/a/400984.html

如若内容造成侵权/违法违规/事实不符,请联系我们进行投诉反馈qq邮箱809451989@qq.com,一经查实,立即删除!

相关文章

Global Gamers Challenge | 与 Flutter 一起保护地球

作者 / Kelvin Boateng 我们知道 Flutter 开发者热爱挑战&#xff0c;因此我们很高兴地宣布&#xff0c;新一轮的 Flutter 挑战赛来了&#xff01; 挑战https://flutter.cn/events/puzzle-hack Global Gamers Challenge 是一项为期 8 周的比赛&#xff0c;参赛者需要设计、构建…

基于CNN-GRU-Attention的时间序列回归预测matlab仿真

目录 1.算法运行效果图预览 2.算法运行软件版本 3.部分核心程序 4.算法理论概述 4.1 CNN&#xff08;卷积神经网络&#xff09;部分 4.2 GRU&#xff08;门控循环单元&#xff09;部分 4.3 Attention机制部分 5.算法完整程序工程 1.算法运行效果图预览 2.算法运行软件版…

详解 IT/OT 融合的五层架构(从PLC/SCADA到MES/ERP)

作为一个电气自动化的从业者&#xff0c;有必要搞懂下面术语的意思。 IT&#xff1a;Information Technology的缩写&#xff0c;指信息技术&#xff1b; OT&#xff1a;Operational Technology的缩写&#xff0c;指操作层面的技术&#xff0c;比如运营技术&#xff1b;CT&…

智慧城市环卫车辆监控管理方案

二&#xff0e;方案设计 智慧城市环卫系统主要包括以下几个方面&#xff1a; 1、通过 RFID 实时自动采集功能&#xff0c;自动统计了解各处垃圾桶每天清理情况&#xff1b; 2、GPS 与 DTU 透传相结合&#xff0c;实时掌握保洁及垃圾车辆的工作状态&#xff0c; 行驶路线以及任…

Vue路由缓存问题

路由缓存问题的产生 VueRouter允许用户在页面中创建多个视图&#xff08;多级路由&#xff09;&#xff0c;并根据路由参数来动态的切换视图。使用带参数的路由时&#xff0c;相同的组件实例将被重复使用。因为两个路由都渲染同一个组件&#xff0c;比起销毁再创建&#xff0c;…

OpenAI Sora 技术报告: Video generation models as world simulators

Paper name OpenAI Sora 技术报告&#xff1a;Video generation models as world simulators Paper Reading Note 官网&#xff1a;https://openai.com/sora 技术报告&#xff1a;https://openai.com/research/video-generation-models-as-world-simulators TL;DR 2024 Op…

高维数据的中介效应【中介分析】《R包:HIMA》

允许基于高级中介筛选和惩罚回归技术来估计和测试高维中介效应 Hima包浏览 高维中介示意图 图1. 在暴露和结果之间有高维中介的情况 本包的作用 在确定独立筛选和极小极大凹惩罚技术的基础上&#xff0c;采用联合显著性检验方法对调解效果进行检验。使用蒙特卡罗模拟研究来展…

鸿蒙应用/元服务开发实战-Serverless云存储没法创建处理方式

新账户&#xff0c;Serverless云存储没法创建 &#xff0c;没法进行下一步。 解决方式 请按照这个方式修改一下就能正常创建了&#xff0c;浏览器中打开控制台输入 window.top.cfpConfig.cloudStorageSwitch‘off’ 后再创建桶

二叉树基础知识总结

目录 二叉树基础知识 概念 : 根节点的五个形态 : 特殊的二叉树 满二叉树 : 完全二叉树 : 二叉搜索树 : 平衡二叉搜索树 : 二叉树的性质 : 二叉树的存储结构 二叉树的顺序存储结构 二叉树的链式存储结构 二叉树的遍历方式 : 基础概念 前中后遍历 层序遍历 :…

堆的结构实现与应用

目录 前言: 1.认识堆 a.如何认识堆&#xff1f; b.大根堆与小根堆 c.堆应用的简单认识 2.堆的结构与要实现的功能 3.向上调整算法 4.向下调整算法 5.向堆插入数据并建堆 6.堆的大小 7.堆的判空 8.取堆顶数据 9.删除堆顶数据 10.向上调整时间复杂度 11.向下调整时…

(十二)【Jmeter】线程(Threads(Users))之tearDown 线程组

简述 操作路径如下: 作用:在正式测试结束后执行清理操作,如关闭连接、释放资源等。配置:设置清理操作的采样器、执行顺序等参数。使用场景:确保在测试结束后应用程序恢复到正常状态,避免资源泄漏或对其他测试的影响。优点:提供清理操作,确保测试环境的整洁和可重复性…

【前端】夯实基础 css/html/js 50个练手项目(持续更新)

文章目录 前言Day 1 expanding-cardsDay 2 progress-steps 前言 发现一个没有用前端框架的练手项目&#xff0c;很适合我这种纯后端开发夯实基础&#xff0c;内含50个mini project&#xff0c;学习一下&#xff0c;做做笔记。 项目地址&#xff1a;https://github.com/bradtr…

集合可视化:rainbow box与欧拉图

论文&#xff1a;A new diagram for amino acids: User study comparing rainbow boxes to Venn/Euler diagram 最近偶然看到了这篇论文&#xff0c;觉得很有意思&#xff0c;针对的任务是集合数据的可视化。 我们用示例来说明&#xff0c;比如图二的欧拉图&#xff0c;展示的…

爬取链家二手房房价数据存入mongodb并进行分析

实验目的 1.使用python将爬虫数据存入mongodb&#xff1b; 2.使用python读取mongodb数据并进行可视化分析。 实验原理 MongoDB是文档数据库&#xff0c;采用BSON的结构来存储数据。在文档中可嵌套其他文档类型&#xff0c;使得MongoDB具有很强的数据描述能力。本节案例使用的…

【深度学习】微调Qwen1.8B

1.前言 使用地址数据微调Qwen1.8B。Qwen提供了预构建的Docker镜像&#xff0c;在使用时获取镜像只需安装驱动、下载模型文件即可启动Demo、部署OpenAI API以及进行微调。 github地址&#xff1a;GitHub - QwenLM/Qwen: The official repo of Qwen (通义千问) chat & pretr…

计算机设计大赛 深度学习卫星遥感图像检测与识别 -opencv python 目标检测

文章目录 0 前言1 课题背景2 实现效果3 Yolov5算法4 数据处理和训练5 最后 0 前言 &#x1f525; 优质竞赛项目系列&#xff0c;今天要分享的是 &#x1f6a9; **深度学习卫星遥感图像检测与识别 ** 该项目较为新颖&#xff0c;适合作为竞赛课题方向&#xff0c;学长非常推荐…

ELK Stack 日志平台搭建

前言 最近在折腾 ELK 日志平台&#xff0c;它是 Elastic 公司推出的一整套日志收集、分析和展示的解决方案。 专门实操了一波&#xff0c;这玩意看起来简单&#xff0c;但是里面的流程步骤还是很多的&#xff0c;而且遇到了很多坑。在此记录和总结下。 本文亮点&#xff1a;…

PEARL: 一个轻量的计算短文本相似度的表示模型

| &#x1f4bb; [code] | &#x1f4be; [data] | &#x1f917; PEARL-small | &#x1f917; PEARL-base | 论文 如何计算短文本相似度是一个重要的任务&#xff0c;它发生在各种场景中&#xff1a; 字符串匹配&#xff08;string matching&#xff09;。我们计算两个字符…

Stable Diffusion 模型下载:A-Zovya RPG Artist Tools(RPG 大师工具箱)

本文收录于《AI绘画从入门到精通》专栏&#xff0c;专栏总目录&#xff1a;点这里。 文章目录 模型介绍生成案例案例一案例二案例三案例四案例五案例六案例七案例八 下载地址 模型介绍 A-Zovya RPG Artist Tools 模型是一个针对 RPG 训练的一个模型&#xff0c;可以生成一些 R…

win32 汇编读文件

做了2个小程序&#xff0c;没有读成功&#xff1b;文件打开了&#xff1b; .386.model flat, stdcalloption casemap :noneinclude windows.inc include user32.inc includelib user32.lib include kernel32.inc includelib kernel32.lib include Comdlg32.inc includelib …