3. Java中的锁

news2024/9/28 6:30:05

文章目录

  • 乐观锁与悲观锁
    • 乐观锁(无锁编程,版本号机制)
    • 悲观锁
    • 两种锁的伪代码比较
  • 通过 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

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

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


在这里插入图片描述

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

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

相关文章

ARM 版银河麒麟桌面系统下 Qt 开发环境搭建指南

目录 前言安装Linux ARM 版 QtCreator配置 Qt Creator配置构建套件 第一个麒麟 Qt 应用程序小结 前言 在上一篇文章信创ARM架构QT应用开发环境搭建中建议大家使用 Ubuntu X86 系统作为信创 ARM 架构 QT 应用的开发环境,里面使用了交叉编译的方式。这对于自己的 Qt …

AI与大数据:智慧城市安全的护航者与变革引擎

一、引言 在数字化浪潮的席卷下,智慧城市正成为现代城市发展的新方向。作为城市的神经系统,AI与大数据的融合与应用为城市的安全与应急响应带来了革命性的变革。它们如同城市的“智慧之眼”和“聪明之脑”,不仅为城市管理者提供了强大的决策…

【算法与数据结构】684、685、LeetCode冗余连接I II

文章目录 一、684、冗余连接 I二、685、冗余连接 II三、完整代码 所有的LeetCode题解索引,可以看这篇文章——【算法和数据结构】LeetCode题解。 一、684、冗余连接 I 思路分析:题目给出一个无向有环图,要求去掉一个边以后构成一个树&#xf…

PyQt6的开发流程(密码生成小程序为例)

PyQt6的开发流程(密码生成小程序为例) 文章目录 PyQt6的开发流程(密码生成小程序为例)一、流程介绍与概览1. 界面与逻辑分离的开发流程2. PyQt6的开发流程 二、打开 designer.exe 创建文件三、用QT设计师绘制界面保存成ui1. QT常用…

急中生智:献血200cc没事,为啥出血200cc就可能噶?

点击文末“阅读原文”即可参与节目互动 剪辑、音频 / 卷圈 运营 / SandLiu 卷圈 监制 / 姝琦 封面 / 姝琦Midjourney 产品统筹 / bobo 场地支持 / 声湃轩北京录音间 外伤出血更常见,但同样可能危及生命。 众所周知,出血是一种常见的外伤和急症&…

家装服务管理:Java技术的创新应用

✍✍计算机毕业编程指导师 ⭐⭐个人介绍:自己非常喜欢研究技术问题!专业做Java、Python、微信小程序、安卓、大数据、爬虫、Golang、大屏等实战项目。 ⛽⛽实战项目:有源码或者技术上的问题欢迎在评论区一起讨论交流! ⚡⚡ Java、…

ONLYOFFICE 桌面编辑器 v8.0 更新内容详细攻略

文章目录 引言PDF 表单RTL 支持电子表格中的新增功能Moodle 集成用密码保护 PDF 文件从“开始”菜单快速创建文档本地界面主题下载安装桌面编辑工具总结 引言 官网链接: ONLYOFFICE 官方网址 ONLYOFFICE 桌面编辑器是一款免费的文档处理软件,适用于 Li…

APP被针对攻击了,要怎么解决

随着APP行业的兴起,游戏公司异军突起,不管是在控证还是攻击方面都是属于最复杂的一个场面,游戏APP逐渐成为DDOS流量攻击的“重灾区”。没有提前做好了解就盲目进军游戏APP行业,一旦被攻击就会让公司束手无策。那么,刚上…

基于springboot实现的海鲜销售系统

一、系统架构 前端:html | bootstrap | vue | js | css 后端:springboot | springdata-jpa 环境:jdk1.8 | mysql | maven | redis 二、代码及数据库 三、功能介绍 01. web端-注册 02. web端-登录 03. web端-首页 04. web端-…

k8s节点负载使用情况分析命令kubectl describe node [node-name]

1.到任意安装了kubectl节点命令的节点上执行kubectl describe node [node-name] 上面的Requests最小分配 Limits最大分配是所有pod之和,最小分配之和不能超过服务器实际参数,否则新的pod会因为资源不够起不来,最大分配是预设之和&#xff0…

移动端学习:如何把exe转换成apk

exe转换成apk是怎么实现的呢?-电脑端-一门科技将exe文件转换成apk文件是一个比较常见的需求,尤其是对于一些开发者和用户来说。但是,这个过程并不是简单的复制和粘贴。在本文中,我们将介绍exe转换成apk的原理和详细介绍。首先,我们需要了解什么https://www.yimenapp.net/k…

数据安全-动态加密(不同敏感字段使用不同的加密算法-MySQL、Oracle版本)

动态数据加密 动态加密(也称实时加密,透明加密等,其英文名为encrypt on-the-fly),是指数据在使用过程中自动对数据进行加密或解密操作,无需用户的干预,合法用户在使用加密的文件前,…

服务器权限:Error: EACCES: permission denied, open‘/Cardiac/uniquC.csv

背景: 我想在服务器上传一个文件uniquC.csv,但是服务器说我没有权限 解决方案: 1. 查看目前是否存在对文件夹的权限 ls -ld /Cardiac/ # your fold path 此时,我发现 这也意味着root也没有赋予写的权限。 2. 拿到root权限 …

Python爬虫-模拟Github登录并获取个人信息

爬虫系列:http://t.csdnimg.cn/WfCSx 前言 很多情况下,页面的某些信息需要登录才可以查看。对于爬虫来说,需要爬取的信息如果需要登录才可以看到的话,那么我们就需要做一些模拟登录的事情。 在前面我们了解了会话和 Cookies 的…

人脸2D和3D道具SDK解决方案提供商

人脸识别和增强现实技术成为了许多企业和开发者关注的焦点,为了满足市场对高质量、易于集成的人脸识别SDK的需求,美摄科技推出了一系列领先的人脸2D/3D道具SDK解决方案。 一、产品特点 高精度识别:美摄科技的人脸识别技术采用深度学习算法&…

【博士每天一篇文献-综述】A Modified Echo State Network Model Using Non-Random Topology

阅读时间:2023-11-23 1 介绍 年份:2023 作者: Arroyo, Diana Carolina Roca,数学与计算机科学研究所(ICMC)圣保罗大学 (USP) 期刊: 博士论文 引用量:0 这篇论文是一篇博士论文&am…

PostgreSQL 与MySQL 对比使用

一、前言 博主的系统既有 用到MySQL 也有用到PostgreSQL ,之所以用到这两种数据库,主要是现在都是国产替代,虽然说这两款数据库也不是国产的,但是相对开源,oracle是不让用了。所以现在使用比较多的就是这两个关系型数据…

geotools解析shp 提示 opengis.*.SimpleFeatureType‘ 不在其界限内

问题:( geotools.version:31-SNAPSHOT) 解析shp文件时提示类型SimpleFeatureType不在其界限内 解决: 在引用处将org.opengis.feature.simple.SimpleFeatureType 改为 org.geotools.api.feature.simple.SimpleFeatureType

Web JavaScript

目录 1 前言2 原生js常见用法2.1 弹窗操作2.2 for循环操作2.3 打印日志操作2.4 获取页面值操作2.5 判空操作2.6 修改页面内容操作2.7 网页版计算器制作 3 外部js常见用法4 总结 1 前言 JavaScript 是一种脚本,一门编程语言,它可以在网页上实现复杂的功能…

jvm面试题目补充

jdk&jre Java程序设计语言、Java虚拟机、Java API类库这三部分统称为JDK(Java Development Kit)。 把Java API类库中的Java SE API子集 [1] 和Java虚拟机这两部分统称为JRE(Java Runtime Environment),JRE是支持…