3. Java中的锁

文章目录

  • 乐观锁与悲观锁
    • 乐观锁(无锁编程,版本号机制)
    • 悲观锁
    • 两种锁的伪代码比较
  • 通过 8 种锁运行案例,了解锁
    • 锁相关的 8 种案例演示
      • 场景一
      • 场景二
      • 场景三
      • 场景四
      • 场景五
      • 场景六
      • 场景七
      • 场景八
    • synchronized 有三种应用方式
      • 8 种锁的案例实际体现在 3 个地方
    • 从字节码角度分析 synchronized 实现
      • `javap -c ****.class`文件反编译
      • synchronized 同步代码块
      • synchronized 普通同步方法
      • synchronized 静态同步方法
    • 对于 synchronized 的深入研究
      • 面试题:为什么任何一个对象都可以成为一个锁
      • 什么是管程 monitor
  • 公平锁与非公平锁
    • 为什么会有公平锁和非公平锁的设计?
    • 为什么默认使用非公平锁?
    • 什么时候用公平锁?什么时候用非公平锁?
  • 可重入锁,又叫,递归锁
    • 隐式锁(synchronized 默认是可重入锁)
      • 同步块
      • 同步方法
    • synchronized 的可重入原理(基于 objectMonitor.hpp)
    • 显示锁(Lock,ReentrantLock)
  • 死锁以及排查
    • 死锁是什么
    • 编写一个死锁 case
    • 死锁的故障排查
  • 小结
  • 见后续
    • 自旋锁 SpinLock
    • 无锁->独占锁->读写锁->邮戳锁
    • 无锁->偏向锁->轻量锁->重量锁

乐观锁与悲观锁

乐观锁(无锁编程,版本号机制)

  • 认为自己在使用数据时,不会有别的线程修改数据或资源,所以不会加锁 。
  • 在 Java 中通过使用无锁编程来实现,只在更新数据时去判断,之前是否存在其它线程更新此数据。
    • 如果这个数据没有被更新,当前线程将自己修改的数据成功写入
    • 如果这个线程数据已经被其他线程更新,则根据不同的实现方式执行不同的操作
      • 放弃修改,尝试抢锁等
  • 判断规则
    • 版本号机制 Version
    • 最常采用的是 CAS 算法,Java 原子类的递增操作就通过 CAS 自旋实现的
  • 适合读操作多的场景,不加锁的特点能够使其读操作的性能大幅提升
  • 乐观锁直接去操作同步资源,是一种无锁算法
  • 乐观锁的两种实现方式
    • 采用 Version 版本号机制
    • CAS(Compare-and-Swap,比较替换算法) 实现

悲观锁

  • 认为自己在使用数据时,必然有别的线程来修改数据,因此在获取到数据的时候,进行操作之前,会先加锁,保证数据不被别的线程所修改
  • synchronized 关键字与 Lock 锁的实现类均为悲观锁
  • 适合写操作多的场景,先加锁可以保证写操作时数据正确,
  • 显示锁定之后再进行同步资源

两种锁的伪代码比较

  • 悲观锁
 //悲观锁基于synchronized关键字
    public synchronized void m1(){
        //code logic segment....
    }
    
    //悲观锁基于Lock对象实现
    ReentrantLock reentrantLock = new ReentrantLock();
    public void m2(){
        reentrantLock.lock();
        try {
            //code logic segment....
        } finally {
            reentrantLock.unlock();
        }
    }
  • 乐观锁
        //乐观锁的调用方式,保证多个线程使用的是同一个AtomicInteger
        AtomicInteger atomicInteger = new AtomicInteger();
        atomicInteger.incrementAndGet();

通过 8 种锁运行案例,了解锁

锁相关的 8 种案例演示

场景一

a,b 两个线程分别使用同步监视器修饰
a 线程启动,0.2 秒后 b 线程启动
可以预见,先执行 a 后执行 b

  • 代码
class Phone {//资源类

    public synchronized void sendEmail() {
        System.out.println(Thread.currentThread().getName() + "-----sendEmail");
    }

    public synchronized void sendSMS() {
        System.out.println(Thread.currentThread().getName() + "-----sendSMS");
    }
}

...
public static void main(String[] args) {//一切程序的入口
        Phone phone = new Phone();

        new Thread(() -> {
            phone.sendEmail();
        }, "a").start();

        //暂停毫秒,保证a线程先启动
        try {
            TimeUnit.MILLISECONDS.sleep(200);
        } catch (InterruptedException e) {
            e.printStackTrace();
        }

        new Thread(() -> {
            phone.sendSMS();
        }, "b").start();
    }

  • 效果

image.png

场景二

在场景一的资源类中,sendEmail方法中加入暂停3秒钟
由于 synchronized 是悲观锁,并且 sleep 不回释放锁,因此 a 线程先执行,并且执行 sleep 时程序也会阻塞,当 a 线程执行完毕时,b 线程才会执行
可以预见结果和场景一致

  • 代码
    public  synchronized void sendEmail() {
        try {
            TimeUnit.SECONDS.sleep(3);
        } catch (InterruptedException e) {
            e.printStackTrace();
        }
        System.out.println(Thread.currentThread().getName() + "-----sendEmail");
    }
  • 效果

image.png

一个对象里面如果有多个synchronized方法,某一个时刻内,只要一个线程去调用其中的一个synchronized方法了,
其它的线程都只能等待,换句话说,某一个时刻内,只能有唯一的一个线程去访问这些synchronized方法
锁的是当前对象this,被锁定后,其它的线程都不能进入到当前对象的其它的synchronized方法

场景三

添加一个普通的hello方法,先打印邮件还是hello?
普通方法线程共享

class Phone {//资源类

    ....
    public void hello() {
        System.out.println("-------hello");
    }
    ....
}

//main方法
 public static void main(String[] args) {//一切程序的入口
        Phone phone = new Phone();

        new Thread(() -> {
            phone.sendEmail();
        }, "a").start();

        //暂停毫秒,保证a线程先启动
        try {
            TimeUnit.MILLISECONDS.sleep(200);
        } catch (InterruptedException e) {
            e.printStackTrace();
        }

        new Thread(() -> {
            phone.hello();
        }, "b").start();
    }
  • 效果

image.png

场景四

有两部手机,请问先打印邮件还是短信
两次方法的调用 synchronized 锁住的对象不同

  • 代码
//资源类不变
 public static void main(String[] args) {//一切程序的入口
        Phone phone = new Phone();
        Phone phone2 = new Phone();

        new Thread(() -> {
            phone.sendEmail();
        }, "a").start();

        //暂停毫秒,保证a线程先启动
        try {
            TimeUnit.MILLISECONDS.sleep(200);
        } catch (InterruptedException e) {
            e.printStackTrace();
        }

        new Thread(() -> {
           phone2.sendSMS();
        }, "b").start();
    }
  • 效果

image.png

场景五

在场景 一的情况下将两个方法均添加 static 修饰,测试代码同场景一
静态同步方法(类锁)

  • 代码
    public static synchronized void sendEmail() {
        try {
            TimeUnit.SECONDS.sleep(3);
        } catch (InterruptedException e) {
            e.printStackTrace();
        }
        System.out.println(Thread.currentThread().getName() + "-----sendEmail");
    }

    public static synchronized void sendSMS() {
        System.out.println(Thread.currentThread().getName() + "-----sendSMS");
    }
  • 效果

image.png

场景六

在场景五的情况下,添加一部手机,用 phone2 调用 sentSMS

  • 代码
//资源类同场景五
//测试代码
 public static void main(String[] args) {//一切程序的入口
        Phone phone = new Phone();
        Phone phone2 = new Phone();

        new Thread(() -> {
            phone.sendEmail();
        }, "a").start();

        //暂停毫秒,保证a线程先启动
        try {
            TimeUnit.MILLISECONDS.sleep(200);
        } catch (InterruptedException e) {
            e.printStackTrace();
        }

        new Thread(() -> {
            phone2.sendSMS();
        }, "b").start();
    }
  • 效果

image.png

对于普通同步方法,锁的是当前实例对象,通常指this,具体的一部部手机,所有的普通同步方法用的都是同一把锁——>实例对象本身(方法调用者),
对于静态同步方法,锁的是当前类的Class对象,如Phone.class唯一的一个模板(调用者所属的类型)
对于同步方法块,锁的是 synchronized 括号内的对象

场景七

有1个静态同步方法,有1个普通同步方法,有1部手机,请问先打印邮件还是短信

    //将短信方法还原为普通同步方法
    public static synchronized void sendSMS() {
        System.out.println(Thread.currentThread().getName() + "-----sendSMS");
    }
  • 测试代码同场景一
    public static void main(String[] args) {//一切程序的入口
        Phone phone = new Phone();

        new Thread(() -> {
            phone.sendEmail();
        }, "a").start();

        //暂停毫秒,保证a线程先启动
        try {
            TimeUnit.MILLISECONDS.sleep(200);
        } catch (InterruptedException e) {
            e.printStackTrace();
        }

        new Thread(() -> {
           phone.sendSMS();
        }, "b").start();
    }
  • 效果

image.png

类锁与对象锁不是同一个,各种执行,a 线程睡眠 3 秒

场景八

有1个静态同步方法,有1个普通同步方法,有2部手机,请问先打印邮件还是短信

//资源类同场景七
//测试代码
 public static void main(String[] args) {//一切程序的入口
        Phone phone = new Phone();
        Phone phone2 = new Phone();

        new Thread(() -> {
            phone.sendEmail();
        }, "a").start();

        //暂停毫秒,保证a线程先启动
        try {
            TimeUnit.MILLISECONDS.sleep(200);
        } catch (InterruptedException e) {
            e.printStackTrace();
        }

        new Thread(() -> {
            phone2.sendSMS();
        }, "b").start();
    }
  • 效果

image.png

当一个线程试图访问同步代码时它首先必须得到锁,正常退出或抛出异常时必须释放锁。
所有的普通同步方法用的都是同一把锁——实例对象本身,就是new出来的具体实例对象本身,本类this
也就是说如果一个实例对象的普通同步方法获取锁后,该实例对象的其他普通同步方法必须等待获取锁的方法释放锁后才能获取锁。
所有的静态同步方法用的也是同一把锁——类对象本身,就是我们说过的唯一模板Class
具体实例对象this和唯一模板Class,这两把锁是两个不同的对象,所以静态同步方法与普通同步方法之间是不会有竞态条件的
但是一旦一个静态同步方法获取锁后,其他的静态同步方法都必须等待该方法释放锁后才能获取锁。

synchronized 有三种应用方式

8 种锁的案例实际体现在 3 个地方

  • 作用于实例方法,为当前方法调用者加锁,进入同步代码前需要获得当前实例的锁
  • 作用于代码块,synchronized(obj){},obj 为加锁对象
  • 作用于静态方法,当前类加锁,进入同步代码块之前需要获取类对象的锁

从字节码角度分析 synchronized 实现

javap -c ****.class文件反编译

  • -c 作用: 对代码进行反编译
  • -v (verbose) 作用 输出附加信息(行号,本地变量表,反编译等详细信息)

synchronized 同步代码块

  • 编写测试代码->运行产生 class 文件->进入 class 类路径–>执行 javap -c
Object object = new Object();

    public void m1() {
        synchronized (object) {
            System.out.println("----hello synchronized code block");
        }
    }
  • 反编译结果
    • 一般情况下,一个 enter 对应 2 个 exit

image.png

  • 极端情况==>手动抛出一个异常
Object object = new Object();

public void m1() {
    synchronized (object) {
        System.out.println("----hello synchronized code block");
        throw new RuntimeException("-----exp");
    }
}

image.png

  • synchronized 同步代码块的实现
    • 进入锁使用 monitorenter 指令
    • 退出锁使用 monitorexit 指令

synchronized 普通同步方法

    public synchronized void m2() {
        System.out.println("----hello synchronized m2");
    }
  • 使用javap -v .\LockSyncDemo.class进行编译

image.png

  • 调用指令时检查方法的 ACC_SYNCHRONIZED 访问标志位是否被设置
    • 若被设置了,则线程会将先持有 monitor 锁,然后再执行方法
    • 最后在方法完成时(无论正常完成还是非正常完成)均会释放 monitor

synchronized 静态同步方法

public static synchronized void m3() {
    System.out.println("----hello static synchronized m3");
}
  • 执行反编译

image.png

  • ACC_STATIC, ACC_SYNCHRONIZED 访问标志位区分该方法是否为静态同步方法

对于 synchronized 的深入研究

面试题:为什么任何一个对象都可以成为一个锁

什么是管程 monitor

  • HotSport 虚拟机中,monitor 采用 ObjectMonitor 实现
  • C++源码执行过程

  • Object 底层实现基于 ObjectMonitor.cpp,所有类默认继承自 Object 类,因此每个对象天生就带着一个 monitor 对象
  • 每一个锁住的对象均会与 monitor 进行关联
  • objectMonitor.hpp 中的源码片段
// initialize the monitor, exception the semaphore, all other fields
// are simple integers or pointers
ObjectMonitor() {
    _header       = NULL;
    _count        = 0;			//记录该线程获取锁的次数
    _waiters      = 0,
    recursions   = 0;			//锁的重入次数
    _object       = NULL;
    _owner        = NULL;	    //指向持有ObjectMonitor对象的线程
    _WaitSet      = NULL;		//存放的处于wait状态的线程队列
    _WaitSetLock  = 0 ;			
    _Responsible  = NULL ;
    _succ         = NULL ;
    _cxq          = NULL ;
    FreeNext      = NULL ;
    _EntryList    = NULL ;		//存放处于等待锁block状态的线程队列
    _SpinFreq     = 0 ;
    _SpinClock    = 0 ;
    OwnerIsThread = 0 ;
    _previous_owner_tid = 0;
}

公平锁与非公平锁

公平锁是指多线程按照申请锁的顺序来获取锁,先来先得
Lock lock = new ReentrantLock(true); //true表示先来先得


非公平锁是指,获取锁的顺序不是按照申请锁的顺序
存在后申请的线程比先申请的线程优先获取锁
在高并发环境下,存在优先级翻转或者锁饥饿的状态
锁饥饿 : 某个线程长时间得不到锁
Lock lock = new ReentrantLock(false); //false 表示非公平锁,并发抢锁
Lock lock = new ReentrantLock(); //默认非公平锁

为什么会有公平锁和非公平锁的设计?

  • 恢复挂起的线程到真正锁的获取还是有时间差的
  • 对 CPU 而言时间差较为明细,非公平锁能够更充分利用 CPU 的时间片,减少 CPU 空闲时间

为什么默认使用非公平锁?

  • 使用多线程的一个考量点就是线程切换的开销
    • 采用非公平锁时,当一个线程请求锁获取同步状态,然后释放同步状态,则刚释放锁的线程在此时再次获得同步状态的概率就变得非常大,因此减少了线程的开销

什么时候用公平锁?什么时候用非公平锁?

  • 为了提高系统的吞吐量,提升性能,减少不必要的时间开销,应选择非公平锁
  • 公平锁需要结合具体场景进行讨论

可重入锁,又叫,递归锁

同一个线程在外层方法获取锁的时候,再进入该线程都内层方法会自动获取锁(前提,锁的对象是同一个),不会因为之前已经获取过还没释放而阻塞

  • Java 中 synchronized 和 ReentrantLock 都是可重入锁
  • 可重入锁可以一定程度上避免死锁
  • 可重入锁,即可多次进入同步域==>同步代码块,同步方法,lock()与 unlock()包裹的区域

隐式锁(synchronized 默认是可重入锁)

  • 在一个 synchronized 修饰的方法或代码块内部调用本类的其它 synchronized 修饰的方法或代码块时,永远可以得到锁

同步块

    private static void reEntryM1() {
        final Object object = new Object();

        new Thread(() -> {
            synchronized (object) {
                System.out.println(Thread.currentThread().getName() + "\t ----外层调用");
                synchronized (object) {
                    System.out.println(Thread.currentThread().getName() + "\t ----中层调用");
                    synchronized (object) {
                        System.out.println(Thread.currentThread().getName() + "\t ----内层调用");
                    }
                }
            }
        }, "t1").start();
    }
  • 在 main 方法中进行调用,测试结果

image.png

同步方法

     public synchronized void m1() {
        //指的是可重复可递归调用的锁,在外层使用锁之后,在内层仍然可以使用,并且不发生死锁,这样的锁就叫做可重入锁。
        System.out.println(Thread.currentThread().getName()+"\t ----come in");
        m2();
        System.out.println(Thread.currentThread().getName()+"\t ----end m1");
    }
    public synchronized void m2(){
        System.out.println(Thread.currentThread().getName()+"\t ----come in");
        m3();
    }
    public synchronized void m3(){
        System.out.println(Thread.currentThread().getName()+"\t ----come in");
    }


    //main方法调用

    public static void main(String[] args){
        ReEntryLockDemo reEntryLockDemo = new ReEntryLockDemo();

        new Thread(() -> {
            reEntryLockDemo.m1();
        }, "t1").start();
    }
  • 效果

image.png

synchronized 的可重入原理(基于 objectMonitor.hpp)

  • 每个锁对象都拥有一个锁计数器和一个指向持有锁的线程指针
    • 当执行 monitorenter 时,如果目标锁的计数器为零,那么没有被其他线程所持有,JVM 会将该锁对象持有的线程设置为当前线程,并将计数器加 1
    • 在目标锁对象的计数器不为零的情况下,如果锁的持有线程是但前线程,那么 JVM 可以将其计数器加 1,否则进入等待,直至持有线程释放该锁
    • 当执行 monitorexit 时,JVM 将锁对象的计数器减 1,计数器为 0 表示锁已被释放

显示锁(Lock,ReentrantLock)

    new Thread(() -> {
            lock.lock();
            try
            {
                System.out.println(Thread.currentThread().getName()+"\t ----come in外层调用");
                lock.lock();
                try
                {
                    System.out.println(Thread.currentThread().getName()+"\t ----come in内层调用");
                }finally {
                    lock.unlock();
                }

            }finally {
                // 由于加锁次数和释放次数不一样,第二个线程始终无法获取到锁,导致一直在等待。
                lock.unlock();// 正常情况,加锁几次就要解锁几次
            }
        },"t1").start();

        new Thread(() -> {
            lock.lock();
            try
            {
                System.out.println(Thread.currentThread().getName()+"\t ----come in外层调用");
            }finally {
                lock.unlock();
            }
        },"t2").start();
  • 效果

image.png

  • lock()与 unlock()未一一匹对

image.png

  • 效果

image.png

  • t1 外层未释放锁,t2 陷入持续等待…

死锁以及排查

死锁是什么

参考往期文章

编写一个死锁 case

 public static void main(String[] args) {
        final Object objectA = new Object();
        final Object objectB = new Object();

        new Thread(() -> {
            synchronized (objectA) {
                System.out.println(Thread.currentThread().getName() + "\t 自己持有A锁,希望获得B锁");
                try {
                    TimeUnit.SECONDS.sleep(1);
                } catch (InterruptedException e) {
                    e.printStackTrace();
                }
                synchronized (objectB) {
                    System.out.println(Thread.currentThread().getName() + "\t 成功获得B锁");
                }
            }
        }, "A").start();

        new Thread(() -> {
            synchronized (objectB) {
                System.out.println(Thread.currentThread().getName() + "\t 自己持有B锁,希望获得A锁");
                try {
                    TimeUnit.SECONDS.sleep(1);
                } catch (InterruptedException e) {
                    e.printStackTrace();
                }
                synchronized (objectA) {
                    System.out.println(Thread.currentThread().getName() + "\t 成功获得A锁");
                }
            }
        }, "B").start();
    }

死锁的故障排查

  • jps -l
  • jstack pid

image.png
image.png

  • jconsole

image.png

小结


见后续

自旋锁 SpinLock

无锁->独占锁->读写锁->邮戳锁

无锁->偏向锁->轻量锁->重量锁


在这里插入图片描述

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

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

相关文章

亿道推出重磅加固平板!为行业发展注入新动力

随着科技生产力的不断发展,各行各业都得到质的飞跃。产品的迭代速度也大大加快,作为全球领先的加固行移动终端一站式提供商,亿道信息跟紧时代潮流,推出EM-I10J、EM-I20J两款均衡型加固平板,为行业发展注入新动力。 接地…

.[hudsonL@cock.li].mkp勒索加密数据库完美恢复---惜分飞

有朋友oracle数据库所在机器被加密,扩展名为:.[hudsonLcock.li].mkp,数据文件类似: 通过专业工具分析,确认这次运气非常好,每个文件就加密破坏前面31个block 通过研发的Oracle数据文件勒索恢复工具进行恢复 顺利数据库并且导出数据 mkp勒索病毒预…

【大厂AI课学习笔记NO.55】2.3深度学习开发任务实例(8)模型训练

作者简介:giszz,腾讯云人工智能从业者TCA认证,信息系统项目管理师。 博客地址:https://giszz.blog.csdn.net 声明:本学习笔记来自腾讯云人工智能课程,叠加作者查阅的背景资料、延伸阅读信息,及学…

CDQ分治详解,一维、二维、三维偏序

文章目录 零、偏序关系一、一维偏序二、二维偏序三、三维偏序(CDQ)3.1CDQ分治3.2CDQ分治解决三维偏序的流程 四、OJ练习4.1三维偏序模板题4.1.1原题链接4.1.2AC代码 4.2老C的任务4.2.1原题链接4.2.2解题思路4.2.3AC代码 4.3动态逆序对4.3.1原题链接4.3.2解题思路4.3.3AC代码 零…

C# 学习第二弹

一、变量 存储区(内存)中的一个存储单元 (一)变量的声明和初始化 1、声明变量——根据类型分配空间 ①声明变量的方式 —变量类型 变量名 数值; —变量类型 变量名; 变量名 数值; —变…

使用R语言进行主成分和因子分析

一、数据描述 数据来源2013年各地区水泥制造业规模以上企业的各主要经济指标,原始数据来源于2014年(《中国水泥统计年鉴》),试对用主成分和因子进行经济效益评价。 地区,企业个数(亿元),流动资产合计&…

python Matplotlib Tkinter-->最终框架一

3D雷达上位机实例(能够通过点击柱状图来展示3D雷达数据)2024.2.26 环境 python:python-3.12.0-amd64 包: matplotlib 3.8.2 pillow 10.1.0 import matplotlib.pyplot as plt from matplotlib.backends.backend_tkagg import FigureCanvasTkAgg, NavigationToolbar2Tk impor…

基于Springboot + Vue 母婴商城系统

末尾获取源码作者介绍:大家好,我是墨韵,本人4年开发经验,专注定制项目开发 更多项目:CSDN主页YAML墨韵 学如逆水行舟,不进则退。学习如赶路,不能慢一步。 目录 一、项目简介 二、开发技术与环…

基于Java SSM框架实现驾校预约管理系统项目【项目源码+论文说明】计算机毕业设计

基于java的SSM框架实现驾校预约管理系统演示 摘要 随着科学技术的飞速发展,各行各业都在努力与现代先进技术接轨,通过科技手段提高自身的优势,驾校预约管理系统当然也不能排除在外,随着网络市场的不断成熟,带动了驾校…

【BUG 记录】史诗级 BUG - MYSQL 删库删表却没有备份如何恢复数据

【BUG 记录】史诗级 BUG - MYSQL 删库删表却没有备份如何恢复数据 1. 问题描述2. 解决方案(binlog)2.1 构造测试环境2.2 查看 MySQL 环境是否开启 binlog2.3 查看所有的 binlog 日志记录2.4 查看当前正在使用的是哪一个 binlog 文件2.5 查看此时的 binlo…

设计并实现一个并发安全的LRU(Least Recently Used,最近最少使用)缓存结构

文章目录 前言实战演示写在最后 前言 相信很多人都使用过LinkedHashMap,LinkedHashMap中的removeEldestEntry可以删除老旧的元素,我们可以以此来实现一个LRU缓存结构,并结合java中JUC包中的各种多线程锁机制来保证多线程安全。 以下是我遇见…

C# OpenCvSharp DNN Yolov8-OBB 旋转目标检测

目录 效果 模型信息 项目 代码 下载 C# OpenCvSharp DNN Yolov8-OBB 旋转目标检测 效果 模型信息 Model Properties ------------------------- date:2024-02-26T08:38:44.171849 description:Ultralytics YOLOv8s-obb model trained on runs/DOT…

Windows常用协议

LLMNR 1. LLMNR 简介 链路本地多播名称解析(LLMNR)是一个基于域名系统(DNS)数据包格式的协议,可用于解析局域网中本地链路上的主机名称。它可以很好地支持IPv4和IPv6,是仅次于DNS 解析的名称解析协议。 2.LLMNR 解析过程 当本地hosts 和 DNS解析 当本地hosts 和 …

Linux浅学笔记04

目录 Linux实用操作 Linux系统下载软件 yum命令 apt systemctl命令 ln命令 日期和时区 IP地址 主机名 网络传输-下载和网络请求 ping命令 wget命令 curl命令 网络传输-端口 进程 ps 命令 关闭进程命令: 主机状态监控命令 磁盘信息监控&#xff1a…

2018-02-14 新闻内容爬虫【上学时做论文自己爬新闻数据,原谅我自己懒发的图片】

2018-02-14新闻内容爬虫【上学时做论文自己爬新闻数据,原谅我自己懒发的图片】资源-CSDN文库https://download.csdn.net/download/liuzhuchen/88878591爬虫过的站点: 1QQ新闻 1,准备爬取滚动新闻页面 2 通过F12 开发工具查找发现&#xff…

Qt项目:网络1

文章目录 项目:网路项目1:主机信息查询1.1 QHostInfo类和QNetworkInterface类1.2 主机信息查询项目实现 项目2:基于HTTP的网络应用程序2.1 项目中用到的函数详解2.2 主要源码 项目:网路 项目1:主机信息查询 使用QHostI…

SIMON 32/64加密电路的实现(System Verilog)

关于SIMON加密电路的原理,参考之前发布的博文【SIMON加密算法的原理】 1.总览与电路介绍 1.1 电路总体结构图 1.2 模式配置介绍 SIMON加密算法的分组长度、密钥长度以及必要的参数配置如下图: 本次需要实现的是SIMON 32/64,即分组长度2n3…

【数据结构】B树,B+树,B*树

文章目录 一、B树1.B树的定义2.B树的插入3.B树的中序遍历 二、B树和B*树1.B树的定义2.B树的插入3.B*树的定义4.B树系列总结 三、B树与B树的应用 一、B树 1.B树的定义 1. 在内存中搜索效率高的数据结构有AVL树,红黑树,哈希表等,但这是在内存…

Stable Diffusion 绘画入门教程(webui)-ControlNet(Shuffle)

Shuffle(随机洗牌),这个预处理器会把参考图的颜色打乱搅拌到一起,然后重新组合的方式重新生成一张图,可以想象出来这是一个整体风格控制的处理器。 那么问题来了,官方为啥会设计个这样的处理器呢,主要是给懒人用的&am…

Atcoder ABC341 E - Alternating String

Alternating String(交替字符串) 时间限制:3s 内存限制:1024MB 【原题地址】 所有图片源自Atcoder,题目译文源自脚本Atcoder Better! 点击此处跳转至原题 【问题描述】 【输入格式】 每个查询 q u e r y i query…