新版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.rhkb.cn/news/264346.html

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

相关文章

阿里开源低代码引擎 - Low-Code Engine

阿里开源低代码引擎 - Low-Code Engine 本文主要介绍如何在Windows运行/开发阿里开源低代码引擎 - Low-Code Engine 详细文档参见【 阿里开源低代码引擎 - Low-Code Engine 官方文档】 目录 阿里开源低代码引擎 - Low-Code Engine一、环境准备1、使用 WSL 在 Windows 上安装 L…

8.网络游戏逆向分析与漏洞攻防-游戏网络架构逆向分析-游戏底层功能对接类GameProc的实现

内容参考于&#xff1a;易道云信息技术研究院VIP课 上一个内容&#xff1a;通过逆向分析确定游戏明文接收数据过程 码云地址&#xff08;master 分支&#xff09;&#xff1a;https://gitee.com/dye_your_fingers/titan 码云版本号&#xff1a;bcf7559184863febdcad819e48aa…

小红书关键词爬虫

标题 1 统计要收集的关键词,制作一个文件夹2 爬取每一页的内容3 爬取标题和内容4 如果内容可以被查看,爬取评论内容5 将结果进行汇总,并且每个帖子保存为一个json文件,具体内容6 总结1 统计要收集的关键词,制作一个文件夹 例如,我要收集旅游相关的,就收集: 旅游、旅行…

工厂方法模式Factory Method

1.模式定义 定义一个用于创建对象的接口&#xff0c;让子类决定实例化哪一个类。Factory Method 使得一个类的实例化延迟到子类 2.使用场景 1.当你不知道改使用对象的确切类型的时候 2.当你希望为库或框架提供扩展其内部组件的方法时 主要优点&#xff1a; 1.将具体产品和创建…

树的基本概念和结构

目录 树的概念和结构 树的相关概念 树的特点 树的表示 树的基本应用 树的概念和结构 树是一种非线性的数据结构&#xff0c;它是由n&#xff08;n>0&#xff09;个有限结点组成一个具有层次关系的集合 &#x1f4cc; 把它叫做树是因为它看起来像一棵倒挂的树&#x…

BUGKU-WEB 文件包含

题目描述 题目截图如下&#xff1a; 进入场景看看&#xff1a; 解题思路 你说啥我就干啥&#xff1a;点击一下试试你会想到PHP伪协议这方面去嘛&#xff0c;你有这方面的知识储备吗&#xff1f; 相关工具 解题步骤 查看源码 看到了一点提示信息&#xff1a; ./index.…

C/C++文件操作

一、文本文件操作 1、写文件操作 代码 #include<fstream> #include<iostream>int main() {ofstream outfile("Student.txt", ios::out);if (!outfile) {cout << "文件写入失败" << endl;exit(0); //程序终止}cout << &qu…

1995-2021年全国30省能源消费总量(万吨标煤)

1995-2021年全国30省能源消费总量&#xff08;万吨标煤&#xff09; 1、时间&#xff1a;1995-2021年 2、范围&#xff1a;30省市不含西藏 3、来源&#xff1a;能源统计年鉴 各省年鉴 3、指标: 能源消费总量 4、单位&#xff1a;万吨标煤 5、缺失情况&#xff1a;新疆202…

JSON(javaScript Object Notation,Js对象标记)—我耀学IT

Json是一种轻量级的数据交换格式&#xff0c;目前使用非常广泛&#xff0c;是一种轻量级的数据交换格式。易于人阅读和编写&#xff0c;可以在多种语言之间进行数据交换 。同时也易于机器解析和生成 1.1json的值: 值可以是对象、数组、数字、字符串或者三个字面值(false、nul…

3分钟看懂设计模式01:策略模式

一、什么是策略模式 定义一些列算法类&#xff0c;将每一个算法封装起来&#xff0c;并让它们可以互相替换。 策略模式让算法独立于使用它的客户而变化&#xff0c;是一种对象行为型模式。 以上是策略模式的一般定义&#xff0c;属于是课本内容。 在没有真正理解策略模式之…

《数据治理简易速速上手小册》第3章 数据质量管理(2024 最新版)

文章目录 3.1 数据质量的定义和标准3.1.1 基础知识3.1.2 重点案例&#xff1a;电商平台的数据清洗3.1.3 拓展案例 1&#xff1a;医疗保健机构的数据整合3.1.4 拓展案例 2&#xff1a;金融服务公司的交易数据监控 3.2 数据质量控制的方法与工具3.2.1 基础知识3.2.2 重点案例&…

OSCP靶场--Nickel

OSCP靶场–Nickel 考点(1.POST方法请求信息 2.ftp&#xff0c;ssh密码复用 3.pdf文件密码爆破) 1.nmap扫描 ┌──(root㉿kali)-[~/Desktop] └─# nmap 192.168.237.99 -sV -sC -p- --min-rate 5000 Starting Nmap 7.92 ( https://nmap.org ) at 2024-02-22 04:06 EST Nm…

vue2和vue3 setup beforecreate create生命周期时间比较

创建一个vue程序&#xff0c;vue3可以兼容Vue2的写法&#xff0c;很流畅完全没问题 写了一个vue3组件 <template><div></div> </template><script lang"ts"> import {onMounted} from vue export default{data(){return {}},beforeCr…

FPGA之进位逻辑

进位逻辑&#xff08;Carry Logic&#xff09;Slice 中除了LUT&#xff0c;寄存器&#xff0c;触发器&#xff0c;锁存器外&#xff0c;还提供了专用的快速超前进位逻辑&#xff0c;可以在slice 中执行快速算术加法和减法。CLB 中的专用进位逻辑提高了算术功能&#xff08;如加…

开源的表单设计器拥有什么显著特点?

开源的表单设计器的特点是什么&#xff1f;广州流辰信息是专业研发低代码技术平台的服务商&#xff0c;可以为企业提供系统开发、数据治理、数据分析各环节技术和方案支撑。为了帮助大家了解开源的表单设计器的相关优势特点&#xff0c;小编将为大家做一个详细介绍。 什么是开源…

3分钟快速实现串口PLC远程下载程序操作说明

3分钟快速实现串口PLC远程下载程序操作说明 搜索蓝蜂物联网官网&#xff0c;即可免费领取样机使用&#xff01;&#xff01;先到先得&#xff01;&#xff01;&#xff01; 一. 适用产品型号 其余型号网关此功能正在开发中&#xff0c;敬请期待。 二. 远程下载功能使用流程 …

服务端测试开发必备技能:Mock测试

什么是mock测试 Mock 测试就是在测试活动中&#xff0c;对于某些不容易构造或者不容易获取的数据/场景&#xff0c;用一个Mock对象来创建以便测试的测试方法。 Mock测试常见场景 无法控制第三方系统接口的返回&#xff0c;返回的数据不满足要求依赖的接口还未开发完成&#…

积分商城管理系统的设计与实现

积分商城管理系统的设计与实现 获取源码——》公主号&#xff1a;计算机专业毕设大全

【笔记】【电子科大 离散数学】 2.命题

文章目录 数理逻辑定义 命题定义不是命题的例子 原子命题和复合命题定义约定 命题联结词否定联结词定义例子真值表 合取联结词定义例子真值表 析取联结词定义例子 蕴含联结词定义例子真值表 等价联结词定义例子真值表 命题符号化及其应用速查表格优先级复合命题符号化布尔检索演…

Sora - 探索AI视频模型的无限可能

文章目录 每日一句正能量前言技术解析应用场景未来展望伦理与创意用户体验与互动后记 每日一句正能量 . 一个人&#xff0c;如果没有经受过投资失败的痛楚&#xff0c;又怎么会看到绝望之后的海阔天空。很多时候&#xff0c;经历了人生中最艰难的事&#xff0c;反而锻造了最坚强…