volatile详解、原理

文章目录

  • 一、Volatile的定义和作用
    • 1.1 Volatile简介
    • 1.2 Volatile作用
  • 二、并发编程中的三个问题:可见性、原子性、有序性
  • 二、Java内存模型(JMM)
  • 三、volatile变量的特性
    • 3.1 线程可见性
    • 3.2 禁止重排序
      • 禁止重排序原理
      • 禁止重排序举例
    • 3.3 volatile特性
  • 四、volatile原理
  • 五、适用场景、不适用场景
    • 5.1 volatile适用场景
    • 5.2 volatile不适用的场景
      • 5.2.1 volatile不适用 复合操作。
      • 5.2.2 如何解决
  • 六、Volatile与Synchronized比较

一、Volatile的定义和作用

1.1 Volatile简介

Java允许线程访问共享变量。为了确保共享变量能被一致、可靠地更新,线程必须确保 它是排他性地使用此共享变量,通常都是获得对这些共享变量强制排他性的同步锁。Java编程语言提供了另一种机制,volatile域变量,对于某些场景的使用 这会更加的方便。可以把变量声明为volatile,以让Java内存模型来保证所有线程都能看到这个变量的同一个值。

volatile是Java提供的一种轻量级的同步机制。Java语言包含两种内在的同步机制:同步块(或同步方法) 和 volatile变量,相比于synchronized(synchronized通常被称为重量级锁),volatile更轻量级,因为它不会引起线程上下文的切换和调度。但是volatile变量的同步性较差(有时它更简单并且开销更低),而且其使用也更容易出错。

注:Volatile只能修饰变量。

1.2 Volatile作用

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

  • 保证线程间的可见性:当一个线程对共享变量进行了修改,另外的线程可以立即看到修改后的最新值。CPU都是有行缓存的,volatile能让行缓存无效,因此能读到内存中最新的值。

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

二、并发编程中的三个问题:可见性、原子性、有序性

还记得并发编程中的可见性、原子性、有序性吗?如果忘记可以到这里重温复习【并发编程】1 synchronized底层实现原理、Java内存模型JMM;可重入、不可中断、monitor、CAS、乐观锁和悲观锁;对象内存结构、Mark Word、synchronized锁升级

  • 可见性:当一个线程对共享变量进行了修改,另外的线程并没有立即看到修改后的最新值。 ——> 解决:当多个线程访问同一个变量时,一个线程修改了该变量的值,其他线程能够立即看到修改后的最新值
  • 原子性:当一个线程对共享变量操作到一半时,另外的线程也有可能来操作共享变量,干扰了前一个线程的操作。 ——> 解决:在一次或多次操作中,要么所有的操作都执行并且不会受其他因素干扰而中断,要么所有的操作都不执行
  • 有序性:程序代码在执行过程中的先后顺序,由于Java在编译期以及运行期的优化,导致了代码的执行顺序未必就是开发者编写代码时的顺序。 ——> 解决:程序执行的顺序按照代码的先后顺序执行

二、Java内存模型(JMM)

和Java内存结构不同,Java内存模型是一套规范、是标准化的,屏蔽掉了底层不同计算机的区别(Java内存模型对内存的划分对硬件内存并没有任何影响,因为JMM只是一种抽象的概念,是一组规则)。

JMM描述了Java程序中各种变量(线程共享变量)的访问规则,以及在JVM中将变量存储到内存和从内存中读取变量这样的底层细节,Java内存模型是对共享数据的可见性、有序性、和原子性的规则和保障。细节如下。

  • 主内存:主内存是所有线程都共享的,都能访问的。所有的共享变量都存储于主内存。
  • 工作内存:每一个线程有自己的工作内存,工作内存只存储该线程对共享变量的副本。线程对变量的所有的操作(读,取)都必须在工作内存中完成,而不能直接读写主内存中的变量,不同线程之间也不能直接访问对方工作内存中的变量。

在这里插入图片描述

对于普通的共享变量x,线程1将其修改为某个值 发生在线程1的工作内存中,此时还未同步到主内存中去;而线程2已经缓存了该变量的旧值,所以就导致了共享变量值的不一致。解决这种共享变量在多线程中的不可见问题,较粗暴的方式自然就是加锁,但此处使用synchronized或Lock这些方式 太重量级了,比较合理的方式是 volatile。

注意:JMM是抽象的内存模型,所谓的主内存、工作内存都是抽象概念,不一定就真实地对应cpu缓存、物理内存。

具体细节见【并发编程】1 synchronized底层实现原理、Java内存模型JMM;可重入、不可中断、monitor、CAS、乐观锁和悲观锁;对象内存结构、Mark Word、synchronized锁升级
在这里插入图片描述

三、volatile变量的特性

3.1 线程可见性

用 volatile 修饰共享变量,当一个线程对共享变量进行了修改,另外的线程可以立即看到修改后的最新值。CPU都是有行缓存的,volatile能让行缓存无效,因此能读到内存中最新的值。

  • 当某个线程写volatile变量、将修改的值同步回主内存时,JMM会把该线程工作内存中的变量强制刷新到主内存中去
  • JMM会把其他工作内存的值全部设置为失效,线程会重新读取共享内存的值

详解

当一个线程把主内存中的共享变量读取到自己的本地内存中,然后做了更新。在还没有把共享变量刷新的主内存的时候,另外一个线程是看不到的。如何把修改后的值刷新到主内存中?

现代的处理器使用写缓冲区临时保存向内存写入的数据。写缓冲区可以保证指令流水线持续运行,它可以避免由于处理器停顿下来等向内存写入数据而产生的延迟。同时,通过以批处理的方式刷新写缓冲区,以及合并写缓冲区中对同一内存地址的多次写,较少对内存总线的占用。但是什么时候写入到内存是不知道的。

在这里插入图片描述

所以就引入了volatile,volatile是如何保证可见性的呢?

  • 将当前处理器缓存行的数据写回到系统内存。
  • 这个写回内存的操作会使其他cpu里缓存了该内存地址的数据无效。
  • 如果声明了volatile的变量进行写操作,JVM就会向处理器发送一条Lock前缀的指令,将这个变量所在缓存行的数据写回到系统内存。但是,就算写回到内存,如果其他处理器缓存的还是旧的,再执行操作就会有问题。所以,在多处理器下,为了保证各个处理器的缓存是一致的,就会实现缓存一致性协议,每个处理器通过嗅探在总线传播的数据来检查自己缓存的值是不是过期了,当处理器发现自己缓存行对应的内存地址被修改,就会将当前处理器的缓存行设置成无效状态,当处理器对这个数据进行修改操作的时候,会重新从系统内存中把数据读到处理器缓存里。

3.2 禁止重排序

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

首先看下不加volatile的情况

1)修改pom.xml文件,添加依赖

<dependency>
    <groupId>org.openjdk.jcstress</groupId>
    <artifactId>jcstress-core</artifactId>
    <version>0.14</version>
</dependency>

2)编写代码

//为什么使用此工具,直接运行代码不行吗?  只有使用大量的线程去执行,才能测试出指令重排序的效果
@JCStressTest     //用并发压缩工具jcstress 测试类方法
@Outcome(id = {"0, 0", "1, 1", "0, 1"}, expect = Expect.ACCEPTABLE, desc = "ok")    //@Outcome 对输出结果进行处理, "0, 0", "1, 1", "0, 1"  打印ok
@Outcome(id = "1, 0", expect = Expect.ACCEPTABLE_INTERESTING, desc = "danger")
@State
public class TestOrdering {
    int x;
    int y;

    @Actor
    public void actor1() {   //写
        x = 1;
        y = 1;
    }

    @Actor
    public void actor2(II_Result r) {   //读
        r.r1 = y;
        r.r2 = x;
    }
}

mvn clean install,java -jar xxx\target\jcstress.jar

3)查看项目对应路径results目录下的html文件,用浏览器打开,观察运行结果

在这里插入图片描述

在这里插入图片描述

根据运行结果,出现了”1,0“这种情况,说明actor1()方法先给y赋值、再给x赋值,确实发生指令重排序(指令重排序是为了实现指令流水线,属于计算机组成原理的内容)

如何解决:在变量上添加volatile,禁止指令重排序,则可以解决问题(volatile原理就是加了一些屏障,使屏障后的代码一定不会比屏障前的代码先执行,从而实现有序性)

  • 给 y 加上 volatile,不会出现”1,0“,解决了指令重排序的问题。
//省略部分代码
public class TestOrdering {
    int x;
    volatile int y;
    
    //其他不变,省略
}

在这里插入图片描述

volatile的底层原理是,给共享变量加上不同的屏障,保证指令不会发生重排序

  • 把volatile加在x上面,无法禁止指令重排序
//省略部分代码
public class TestOrdering {
    int x;
    volatile int y;
    
    //其他不变,省略
}

在这里插入图片描述

写操作加的屏障是阻止上方其它写操作越过屏障排到volatile变量写之下,但无法阻止下方的指令往上走,y=1可以往上走、r.r1=y可以往下走。

  • 可以给x、y都加上volatile,也能解决问题,但性能不高,不推荐

在这里插入图片描述

在这里插入图片描述

禁止重排序原理

在执行程序时,为了提高性能,编译器和处理器常常会对指令做重排序。重排序分为如下三种:

在这里插入图片描述

1属于编译器重排序,2和3属于处理器重排序。这些重排序可能会导致多线程程序出现内存可见性问题。

当变量声明为volatile时,Java编译器在生成指令序列时,会插入内存屏障指令,通过内存屏障指令来禁止重排序。

JMM内存屏障插入策略如下:

  • 在每个volatile写操作的前面插入一个StoreStore屏障,后面插入一个StoreLoad屏障。
  • 在每个volatile读操作后面插入一个LoadLoad,LoadStore屏障。

Volatile写插入内存屏障后生成指令序列示意图:

在这里插入图片描述

Volatile读插入内存屏障后生成指令序列示意图:

在这里插入图片描述

通过上面这些我们可以得出如下结论:编译器不会对volatile读与volatile读后面的任意内存操作重排序;编译器不会对volatile写与volatile写前面的任意内存操作重排序。

禁止重排序举例

从一个最经典的例子来分析重排序问题,以单例模式的双重检查锁实现(线程安全)为例

public class Singleton {
	public volatile static Singleton uniqueInstance;
    /**
    * 构造函数私有,禁止外部实例化
    */
    private Singleton() {}
    
    public static Singleton getUniqueInstance() {
        if (uniqueInstance == null) {
            synchronized (Singleton.class) {
                if (uniqueInstance == null) {
                    uniqueInstance = new Singleton();
                }
            }
        }
        return uniqueInstance;
    }
}

为什么使用volatile关键字修饰uniqueInstance实例变量?

因为uniqueInstance = new Singleton()这段代码执行时分为三步:

  1. 为uniqueInstance分配内存空间
  2. 初始化uniqueInstance
  3. 将uniqueInstance指向分配的内存地址

正常的执行顺序当然是 1–>2–>3,但由于JVM具有指令重排的特性,执行顺序可能变成 1–>3–>2。单线程环境时,指令重排没有什么问题;多线程环境下,会导致有些线程可能会获取到还没初始化的实例。如线程A只执行了1和3,此时线程B来调用getUniqueInstance(),发现 uniqueInstance 不为空,便获取 uniqueInstance 实例,但是其实此时的 uniqueInstance 还没有初始化。

解决方法就是 加一个 volatile 关键字修饰 uniqueInstancevolatile 会禁止 JVM 的指令重排,就可以保证多线程环境下的安全运行。

3.3 volatile特性

  • volatile仅能使用在变量级别
  • volatile仅能实现变量的修改可见性,不能保证原子性,volatile + cas 就实现了原子性,如atomic包下面的类。
  • volatile不会造成线程的阻塞
  • volatile标记的变量不会被编译器优化

volatile可以保证可见性、有序性,无法保证原子性,所以是线程不安全的(它对任意单个变量的读/写具有原子性,但是类似于i++这种复合操作不具有原子性,因为本质上i++是读、写两次操作)。要保证多线程的原子性, 可以通过AtomicInteger或者Synchronized来实现,本质上就是CAS操作(见下文5.2)

【synchronized可以保证可见性、原子性、有序性,因为锁的互斥执行的特性可以确保对整个临界区代码执行具有原子性】

java.util.concurrent.*里面的高级线程安全数据结构像ConcurrentHashMap以及java.util.concurrent.atomic.*等的实现都用到了volatile。可以多看看这些类的实现,以加深对volatile的理解和运用。

四、volatile原理

volatile可以保证线程可见性且提供了一定的有序性,但是无法保证原子性。在JVM底层volatile是采用"内存屏障"来实现的。观察加入volatile关键字和没有加入volatile关键字时所生成的汇编代码发现,加入volatile关键字时,会多出一个lock前缀指令,lock前缀指令实际上相当于一个内存屏障(也成内存栅栏),内存屏障会提供3个功能:

(1)它确保指令重排序时不会把其后面的指令排到内存屏障之前的位置,也不会把前面的指令排到内存屏障的后面;即在执行到内存屏障这句指令时,在它前面的操作已经全部完成;

(2)它会强制将对缓存的修改操作立即写入主存

(3)如果是写操作,它会导致其他CPU中对应的缓存行无效。

计算机科学里面,为了解决复杂性,都会分层。正如一个名人所说:“计算机的任何问题都可以通过增加一个虚拟层来解决”(“All problems in computer science can be solved by another level of indirection”)。volatile虚拟机层引入的,解决语言层面的问题,那么它的实现,必然是靠下一层的支持,也就是需要汇编或者说处理器指令的支持来实现,volatile是靠内存屏障和**MESI**(缓存一致性协议)来达成的它的作用的。

内存屏障(Memory Barriers)是处理器提供的一组内存操作指令,它的作用是限制内存操作的顺序,也就是说内存屏障像一个栅栏一样,它前面的指令要在它后面的指令之前完成;还能强制把缓存写入到主存;再有的就是触发缓存一致性,就是当有写变量时,会把其他CPU核心的缓存变为无效。

五、适用场景、不适用场景

5.1 volatile适用场景

对变量可见性有要求、对读取顺序没要求的情况下。如 多线程情况下的标志位

  • 读操作,多于写操作
  • 写操作,不依赖于变量的当前值,即纯赋值操作
  • 只需要读取的值,不需要等待某一特定的值

5.2 volatile不适用的场景

5.2.1 volatile不适用 复合操作。

例如 number++ 不是一个原子性操作,由读取、加、赋值3步组成,下列程序可能返回多种结果,2677、4202、4722、4910、5000

public class Test02Atomicity {
    private volatile static int number = 0;

    public static void main(String[] args) throws InterruptedException {
        Runnable increment = () -> {
            for (int i = 0; i < 1000; i++) {
                number++;
            }
        };

        List<Thread> list = new ArrayList<>();
        for (int i = 0; i < 5; i++) {
            Thread t = new Thread(increment);
            t.start();
            list.add(t);
        }

        for (Thread thread : list) {
            //让主线程等待自己创建的线程执行完毕,5个线程执行完 再取number的值
            thread.join();
        }

        System.out.println("number=" + number);  //值有多种可能,2677、4202、4722、4910、5000


    }
}

5.2.2 如何解决

1)采用synchronized

public class Test02Atomicity {
    private static int number = 0;
    private static Object obj = new Object();
    public static void main(String[] args) throws InterruptedException {
        Runnable increment = () -> {
            for (int i = 0; i < 1000; i++) {
                synchronized (obj) {  
                    number++; //synchronized保证 number++ 为原子操作。线程获取不到锁 就会等待
                }
            }
        };

        List<Thread> list = new ArrayList<>();
        for (int i = 0; i < 5; i++) {
            Thread t = new Thread(increment);
            t.start();
            list.add(t);
        }

        for (Thread thread : list) {
            //让主线程等待自己创建的线程执行完毕
            thread.join();
        }

        System.out.println("number=" + number);  //5000

    }
}

2)采用Lock

public class Test02Atomicity {
    private static int number = 0;
    private static Lock lock = new ReentrantLock();

    public static void main(String[] args) throws InterruptedException {
        Runnable increment = () -> {
            for (int i = 0; i < 1000; i++) {

                try {
                    lock.lock();
                    number++;
                } finally {
                    lock.unlock();
                }

            }
        };

        List<Thread> list = new ArrayList<>();
        //使用5个线程来进行
        for (int i = 0; i < 5; i++) {
            Thread t = new Thread(increment);
            t.start();
            list.add(t);
        }
        for (Thread thread : list) {
            //让主线程等待自己创建的线程执行完毕,5个线程执行完 再取number的值
            thread.join();
        }

        System.out.println("number=" + number);  //5000

    }
}

3)采用java并发包中的原子操作类,原子操作类是通过CAS的方式来保证其原子性的

public class Test02Atomicity {
    private static AtomicInteger atomicInteger = new AtomicInteger();

    public static void main(String[] args) throws InterruptedException {
        Runnable increment = () -> {
            for (int i = 0; i < 1000; i++) {
                atomicInteger.incrementAndGet();   //AtomicInteger的incrementAndGet()可保证变量赋值的原子性
            }
        };

        List<Thread> list = new ArrayList<>();
        for (int i = 0; i < 5; i++) {
            Thread t = new Thread(increment);
            t.start();
            list.add(t);
        }

        for (Thread thread : list) {
            //让主线程等待自己创建的线程执行完毕
            thread.join();
        }

        System.out.println("atomicInteger=" + atomicInteger.get());  //5000

    }
}

六、Volatile与Synchronized比较

  • Volatile是轻量级的synchronized,因为它不会引起上下文的切换和调度,因此Volatile性能更好。
  • Volatile只能修饰变量(类的成员变量、类的静态成员变量),synchronized可以修饰方法,静态方法,代码块。
  • Volatile对任意单个变量的读/写具有原子性,但是类似于i++这种复合操作不具有原子性。而锁的互斥执行的特性可以确保对整个临界区代码执行具有原子性。
  • 多线程访问volatile不会发生阻塞,而synchronized会发生阻塞。
  • volatile是变量在多线程之间的可见性,synchronize是多线程之间访问资源的同步性。

参考 https://zhuanlan.zhihu.com/p/633426082、https://blog.csdn.net/u012723673/article/details/80682208、https://blog.csdn.net/xinghui_liu/article/details/124379221

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

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

相关文章

知乎知+广告推广该如何做?怎么收费?

知乎作为一个汇聚高质量用户群体的知识分享平台&#xff0c;成为了众多品牌和产品推广的优选之地。特别是知乎的“知”广告推广服务&#xff0c;以其精准定向、内容原生的特点&#xff0c;深受广告主青睐。 一、知乎知广告推广基础 1. 什么是知乎知&#xff1f; 知是知乎官方…

Java入门基础学习笔记11——关键字和标识符

1、关键字 关键字是java中已经被赋予特定意义的&#xff0c;有特殊作用的一些单词&#xff0c;不可以把这些单词作为标识符来使用。 注意&#xff1a;关键字是java用了的&#xff0c;我们就不能用来作为&#xff1a;类名、变量名、否则会报错。 标识符&#xff1a; 标识符就是…

RAG应用中的路由模式

依据的用户查询意图在 RAG 应用程序使用“路由控制模式”可以帮助我们创建更强大的 RAG 应用程序。我们通常希望用户能够访问的数据可以来自各种来源,如报告、文档、图片、数据库和第三方系统。 对于基于业务的 RAG 应用程序,我们可能还希望用户能够与其它业务系统进行交互,…

Linux 中 alarm 函数详解

目录 简介函数原型函数参数返回值使用示例设置 3 秒闹钟修改闹钟与取消闹钟设置 1 秒周期定时器 更多内容 简介 alarm 函数的功能是设置一个闹钟&#xff08;定时器&#xff09;&#xff0c;当闹钟时间到时&#xff0c;内核会向当前进程发送一个 SIGALRM 信号。 打开 Linux 终…

汇昌联信电商:拼多多新手怎么做店铺的免费流量会慢慢起来?

在拼多多上开店&#xff0c;新手们往往面临着如何吸引免费流量的挑战。毕竟&#xff0c;流量是店铺生存和发展的血脉&#xff0c;没有流量&#xff0c;就没有销量&#xff0c;店铺也就失去了生命力。那么&#xff0c;作为拼多多新手&#xff0c;如何做才能让店铺的免费流量慢慢…

Python Socket

一、服务端 from socket import *def print_hi(name):print(fHi, {name})# 允许所有ip连接IP 0.0.0.0# 端口PORT 8003# 定义一次从socket缓冲区读入512个字节数据BUFFER_LEN 512# 实例化socket对象 listenSocket 用来监听的socketlistenSocket socket(AF_INET, SOCK_STREA…

有哪些网络兼职适合大学生参与?揭秘几个简单又实用的兼职机会

有哪些网络兼职适合大学生参与&#xff1f;揭秘几个简单又实用的兼职机会 对于大学生而言&#xff0c;除了专注于学业&#xff0c;利用空余时间参与一些网络兼职&#xff0c;不仅能锻炼个人技能&#xff0c;还能为未来的职业生涯积累宝贵的经验。想象一下&#xff0c;步入社会…

基于SSM的“基于协同过滤的在线通用旅游平台网站”的设计与实现(源码+数据库+文档)

基于SSM的“基于协同过滤的在线通用旅游平台网站”的设计与实现&#xff08;源码数据库文档) 开发语言&#xff1a;Java 数据库&#xff1a;MySQL 技术&#xff1a;SSM 工具&#xff1a;IDEA/Ecilpse、Navicat、Maven 系统展示 系统主界面 景点信息界面 后台界面 部分源码…

SSH 免密登录,设置好仍然需要密码登录解决方法

说明&#xff1a; ssh秘钥登录设置好了&#xff0c;但是登录的时候依然需要提供密码 查看系统安全日志&#xff0c;定位问题 sudo cat /var/log/auth.log或者 sudo cat /var/log/secure找到下面的信息 Authentication refused: bad ownership or modes...&#xff08;网上的…

STM: SpatioTemporal and Motion Encoding for Action Recognition 论文阅读

STM: SpatioTemporal and Motion Encoding for Action Recognition 论文阅读 Abstract1. Introduction2. Related Works3. Approach3.1. Channel-wise SpatioTemporal Module3.2. Channel-wise Motion Module3.3. STM Network 4. Experiments5. Conclusion 文章信息&#xff1a…

3.使用uView让tabbar更优雅

文章目录 1. 使用uView让tabbar更优雅1.1. 怎么才优雅&#xff1f;1.2. uView的tabbar合适吗&#xff1f;1.3. 引入项目过程1.3.1. 修改pages.json1.3.2. 把demo里面的pages先拷贝过来1.3.3. 引入tabbar的图片1.3.4. 运行 1.4. 我们自己的项目适配 1. 使用uView让tabbar更优雅 …

深入浅出:ConcurrentLinkedQueue源码分析与实战

哈喽&#xff0c;各位小伙伴们&#xff0c;你们好呀&#xff0c;我是喵手。运营社区&#xff1a;C站/掘金/腾讯云&#xff1b;欢迎大家常来逛逛 今天我要给大家分享一些自己日常学习到的一些知识点&#xff0c;并以文字的形式跟大家一起交流&#xff0c;互相学习&#xff0c;一…

【适用全主题】WordPress原创插件:弹窗通知插件 支持内容自定义

内容目录 一、详细介绍二、效果展示1.部分代码2.效果图展示 三、学习资料下载 一、详细介绍 适用于所有WordPress主题的弹窗插件 一款WordPress原创插件&#xff1a;弹窗通知插件 支持内容自定义 二、效果展示 1.部分代码 代码如下&#xff08;示例&#xff09;&#xff1…

4.uniapp+vue3项目使用vuex

文章目录 1. uniappvue3项目使用vuex1.1. main.js引入store1.2. 创建store/index.js1.3. 项目中引用1.4. 开始解决实际问题1.5. vuex和storage的区别 1. uniappvue3项目使用vuex 这篇文章&#xff0c;既是使用的教程&#xff0c;也是用来解决一个实际问题&#xff1a;uView自定…

Python爬虫入门:网络世界的宝藏猎人

今天阿佑将带你踏上Python的肩膀&#xff0c;成为一名网络世界的宝藏猎人&#xff01; 文章目录 1. 引言1.1 简述Python在爬虫领域的地位1.2 阐明学习网络基础对爬虫的重要性 2. 背景介绍2.1 Python语言的流行与适用场景2.2 网络通信基础概念及其在数据抓取中的角色 3. Python基…

Java面试八股之String s = “String“;和String s = new String(“String“);有什么区别

Java中String s "String";和String s new String("String");有什么区别 字符串字面量&#xff08;"String"&#xff09;&#xff1a; 常量池&#xff1a;使用字面量方式创建字符串时&#xff0c;Java虚拟机&#xff08;JVM&#xff09;会在运…

WWW服务器搭建(1)——HTTP协议原理篇

目录 一、WWW的相关概念 1.1 WWW的定义 1.2 超文本标记语言HTML 1.3 统一资源定位符URL 1.4 超文本传输协议HTTP 二、HTTP协议工作过程 2.1 DNS解析 2.2 TCP连接过程 2.3 HTTP 请求与响应 2.4 TCP连接断开 三、HTTP请求报文格式 3.1 请求行 3.2 请求头 3.3 空行 …

全国防灾减灾日主题活动投稿我可算找对了投稿方法

作为一名社区公众人员,我深知对外信息宣传的重要性。特别是在全国防灾减灾日这样的特殊时刻,我们不仅要向居民普及防灾减灾知识,还要通过媒体将社区的活动和成果展示给更多人。然而,在投稿的过程中,我最初却遭遇了诸多挑战。 起初,我采用传统的邮箱投稿方式,将精心撰写的稿件发…

【JavaWeb】网上蛋糕商城后台-客户管理

概念 上文中已讲解和实现了后台管理系统中的订单管理功能&#xff0c;本文讲解客户信息管理功能。 客户信息列表 在后台管理系统的head.jsp头部页面中点击“客户管理”向服务器发送请求 在servlet包中创建AdminUserListServlet类接收浏览器的请求 package servlet;import m…

特征提取与深度神经网络(二)

关键点/角点检测 2011论文-ORB关键点检测&#xff0c;比SIFT与SURF速度更快。 ORB算法可以看出两个部分组成&#xff1a;快速关键点定位BRIEF描述子生成 Fast关键点检测&#xff1a; 选择当前像素点P&#xff0c;阈值T&#xff0c;周围16个像素点&#xff0c;超过连续N12个像素…