新版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 {@Overridepublic 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{@Overridepublic 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> {@Overridepublic 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 包装mycallableFutureTask<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{@Overridepublic 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 ;     // 线程执行的退出标记@Overridepublic 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 退出// 更改标记为truet1.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 ;     // 线程执行的退出标记@Overridepublic 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之后改为trueSystem.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) {// 同步块 Amethod2();}
}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) {// 同步块 Am2();}
}public static void m2() {synchronized (obj) {// 同步块 Bm3();}
}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

在这里插入图片描述

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

如若内容造成侵权/违法违规/事实不符,请联系多彩编程网进行投诉反馈email:809451989@qq.com,一经查实,立即删除!

相关文章

ES6 | (二)ES6 新特性(下) | 尚硅谷Web前端ES6教程

文章目录 &#x1f4da;迭代器&#x1f407;定义&#x1f407;工作原理&#x1f407;自定义遍历数据 &#x1f4da;生成器函数&#x1f407;声明和调用&#x1f407;生成器函数的参数传递&#x1f407;生成器函数案例 &#x1f4da;Promise&#x1f4da;Set&#x1f407;Set的定…

fastApi笔记05-路径参数和数值校验

使用Path可以对路径参数声明与Query相同类型的校验和元数据 from typing import Annotatedfrom fastapi import FastAPI, Path, Queryapp FastAPI()app.get("/items/{item_id}") async def read_items(item_id: Annotated[int, Path(title"The ID of the item …

微服务篇之限流

一、为什么要限流 1. 并发的确大&#xff08;突发流量&#xff09;。 2. 防止用户恶意刷接口。 二、限流的实现方式 1. Tomcat限流 可以设置最大连接数&#xff0c;但是每一个微服务都有一个tomcat&#xff0c;实现起来非常麻烦。 2. Nginx限流 &#xff08;1&#xff09;控…

假如C++进入Linux内核,那么需要做哪些改造?

假如C进入Linux内核&#xff0c;那么需要做哪些改造&#xff1f; 在开始前我有一些资料&#xff0c;是我根据网友给的问题精心整理了一份「c的资料从专业入门到高级教程」&#xff0c; 点个关注在评论区回复“888”之后私信回复“888”&#xff0c;全部无偿共享给大家&#xf…

ChatGPT安卓版正式发布,附安装包,但有款手机无法使用

ChatGPT安卓版如约而至&#xff0c;OpenAI正式宣布该应用已在谷歌应用商店上架&#xff0c;用户可以免费下载&#xff0c;对话不限次数。 但是安卓版ChatGPT目前仅在美国、印度、孟加拉国和巴西提供下载&#xff0c;下周将会推广至更多国家。 网页端下载链接&#xff1a; http…

五、矩阵的运算

1、矩阵的加减: 前提:两个矩阵必须是同形矩阵。 矩阵加减具有交换律,矩阵矩阵相乘没有交换律。 计算结果:元素级运算。 2、矩阵的数乘: 计算结果:元素级运算。这里要区别与行列式的数乘。 3、矩阵与向量的乘法: 前提:矩阵的列数等于向量的行数。 计算方式:左列…

【Linux基础】vim、常用指令、组管理和组权限

Linux基础 1、目录结构2、vi和vim3、常用指令运行级别找回密码帮助指令时间日期指令搜索查找文件目录操作磁盘管理指令压缩和解压缩 4、组管理和组权限用户操作指令权限 1、目录结构 Linux的文件系统是采用级层式的树状目录结构&#xff0c;在此结构中的最上层是根目录“/”&a…

C++之Easyx——图形库的基本功能(2):来点色彩

一、setbkcolor 函数定义 void EGEAPI setbkcolor(color_t color, PIMAGE pimg NULL); // 设置当前绘图背景色&#xff08;设置并做背景色像素替换&#xff09; 使用说明 void EGEAPI setbkcolor(颜色RGB, PIMAGE pimg NULL); // 设置当前绘图背景色&#xff08;…

备战蓝桥杯---动态规划(应用3之空间优化)

话不多说&#xff0c;直接看题&#xff1a; 我们不妨把问题抽象一下&#xff1a; 首先&#xff0c;我们由裴蜀定理知道如果两个数互质&#xff0c;那么axbyc一定有整数解&#xff08;只要c为1的倍数也就是整数&#xff09;&#xff0c;因此问题就转换为求选一些数使他们gcd1&a…

适用于 Linux、Windows 和 macOS 的免费 ONLYOFFICE 桌面应用程序

前言&#xff1a; 最近也是发现了一款特别好用的免费ONLYOFFICE 桌面应用程序忍不住分享给大家&#xff0c;这款编辑器能够打开、阅读和编辑多种文件类型&#xff0c;包括.docx文档、.pptx幻灯片和.xlsx表格等开放XML格式的Office文档。此外&#xff0c;ONLYOFFICE桌面编辑器还…

收入统计-嵌入式高级软件音频工程师

加我微信hezkz17&#xff0c;可申请进入数字音频系统研究开发交流答疑群&#xff0c;加群附加赠送 蓝牙耳机音频&#xff0c;DSP音频开发资料 1 固定工资收入 2 科技创业收入 3 总收入36K

为什么在MOS管开关电路设计中使用三极管容易烧坏?

MOS管作为一种常用的开关元件&#xff0c;具有低导通电阻、高开关速度和低功耗等优点&#xff0c;因此在许多电子设备中广泛应用。然而&#xff0c;在一些特殊情况下&#xff0c;我们需要在MOS管控制电路中加入三极管来实现一些特殊功能。然而&#xff0c;不同于MOS管&#xff…

【咕咕送书 | 第七期】世界顶级名校计算机专业,都在用哪些书当教材?

&#x1f3ac; 鸽芷咕&#xff1a;个人主页 &#x1f525; 个人专栏:《linux深造日志》《粉丝福利》 ⛺️生活的理想&#xff0c;就是为了理想的生活! ⛳️ 写在前面参与规则 ✅参与方式&#xff1a;关注博主、点赞、收藏、评论&#xff0c;任意评论&#xff08;每人最多评论…

航空航天5G智能工厂数字孪生可视化平台,推进航空航天数字化转型

航空航天5G智能工厂数字孪生可视化平台&#xff0c;推进航空航天数字化转型。随着科技的不断发展&#xff0c;数字化转型已经成为各行各业关注的焦点。航空航天业作为高端制造业的代表&#xff0c;也在积极探索数字化转型之路。为了更好地推进航空航天数字化转型&#xff0c;一…

SOLIDWORKS Electrical如何设置并编辑报表

在电气设计工作中因生产需要&#xff0c;很多企业都会要求电气工程师在图纸中插入设备清单报表。比如在设计机柜布局的时候&#xff0c;在相关设计图纸中插入报表清单可以清楚的帮助了解接线、装配、调试的电气物料内容及对应图纸中的明细。 SOLIDWORKS electrical中就可以自动…

PHP实现分离金额和其他内容便于统计计算

得到的结果可以粘贴到excel计算 <?php if($_GET["x"] "cha"){ $tips isset($_POST[tips]) ? $_POST[tips] : ; $pattern /(\d\.\d|\d)/; $result preg_replace($pattern, "\t\${1}\t", $tips); echo "<h2><strong>数…

第六篇【传奇开心果系列】Python文本和语音相互转换库技术点案例示例:深度解读Kaldi库个性化定制语音搜索引擎

传奇开心果短博文系列 系列短博文目录Python文本和语音相互转换库技术点案例示例系列 短博文目录前言一、雏形示例代码二、扩展思路介绍三、数据准备示例代码四、特征提取示例代码五、声学模型训练示例代码六、语言模型训练示例代码七、解码示例代码八、评估和调优示例代码九、…

MLflow【部署 01】MLflow官网Quick Start实操(一篇学会部署使用MLflow)

一篇学会部署使用MLflow 1.版本及环境2.官方步骤Step-1 Get MLflowStep-2 Start a Tracking ServerStep 3 - Train a model and prepare metadata for loggingStep 4 - Log the model and its metadata to MLflowStep 5 - Load the model as a Python Function (pyfunc) and us…

Autosar-Mcal配置详解-MCU

3.6.1创建、配置RAM 1)创建RAM配置 2)配置RAM 以F1KM R7F7016533ABG为例,它的local RAM有512K, global RAM 192K,Retention RAM 64K. Local RAM: local RAM就是程序平常使用的RAM,在DeepStop模式下内容会丢失。 Global RAM:主要用于DMA的源地址和目的地址使用,在Dee…

Web应用程序防火墙(WAF)与传统防火墙的区别

由于WEB应用防火墙&#xff08;WAF&#xff09;的名字中有“防火墙”三个字&#xff0c;因此很多人都会将它与传统防火墙混淆。实际上&#xff0c;二者之间的有着很大的差别。传统防火墙专注在网络层面&#xff0c;提供IP、端口防护。而WAF是专门为保护基于Web的应用程序而设计…