Java高并发核心编程—内置锁原理篇

news2025/5/31 0:04:27

注:本笔记是阅读《Java高并发核心编程卷2》整理的笔记!

导致并发修改的原因

例如Java中的i++等指令并非是原子操作,而是三条指令的集合:“内存取值”、“寄存器增加1”、“存值到内存” 。 因此,如果是多线程并发使用CPU,当某个线程正在计算时被抢夺了CPU然后恰好被另外一个线程修改了变量将发生覆盖。根本原因是CPU上下文切换导致指令交错执行!

getstatic i // 获取静态变量i的值
iconst_1 // 准备常量1
iadd // 自增
putstatic i // 将修改后的值存入静态变量i

基本概念

临界资源:一次仅允许一个进程使用的资源成为临界资源,一旦临界区资源被占用,想使用该资源的其他线程则必须等待。

临界区:访问临界资源的代码块。线程进入临界区代码段之前,必须在进入区申请资源,申请成功之后进行临界区代码段,执行完成之后释放资源。临界区代码段的进入和退出具体如图2-1所示。

[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-heLSQGWF-1684810591619)(C:\Users\cheney\AppData\Roaming\Typora\typora-user-images\image-20230522201604320.png)]

竞态条件:多个线程没有互斥访问临界区,并发在临界区内执行,由于代码的执行序列不同而导致结果无法预测,称之为发生了竞态条件。

为了避免竞态条件的问题,我们必须保证临界区代码段操作必须具备排他性 。一个线程进入Critical Section执行时,其他线程不能进入临界区代码段执行。

synchronized 关键字

每个Java对象都隐含有一把锁,这里称为Java内置锁|对象锁 。使用synchronized(syncObject)调用相当于获取syncObject的内置锁,所以可以使用内置锁对临界区代码段进行排他性保护。

方法声明synchronized
static int amount = 0;
public synchronized void selfPlus()
{
	amount++;
}

在方法声明中设置synchronized同步关键字,保证了其方法的代码执行流程是排他性的。任何时间只允许一条线程进入同步方法(临界区代码段),如果其他线程都需要执行同一个方法,那么只能等待和排队。 使用this对象锁作为进入临界区的同步锁 。也就是说方法上声明synchronized 锁是当前对象。

synchronized 同步块

为了提升吞吐量,可以将synchronized关键字放在函数体内,同步一个代码块。 synchronized同步块的写法是:

synchronized(syncObject) //同步块而不是方法
{
	//临界区代码段的代码块
}

synchronized同步块后边的括号中是一个syncObject对象,代表着进入临界区代码段需要获取syncObject对象的监视锁,或者说将syncObject对象监视锁作为临界区代码段的同步锁。由于每一个Java对象都有一把监视锁( Monitor),因此任何Java对象都能作为synchronized的同步锁。 synchronized代码块比synchronized方法更加细粒度地控制了多条线程的同步访问 。同步锁需要手动给定 syncObject 。如果某个synchronized方法是static(静态)方法,synchronized的同步锁并不是普通Object对象的监视锁,而是类所对应的Class对象的监视锁。

通过synchronized关键字所抢占的同步锁,什么时候释放呢?一种场景是synchronized块(代码块或者方法)正确执行完毕,监视锁自动释放;另一种场景是程序出现异常,非正常退出synchronized块,监视锁也会自动释放。所以,使用synchronized块时不必担心监视锁的释放问题。

消费者生产者问题

生产者―消费者问题关键是:

1)保证生产者不会在缓冲区满时加入数据,消费者也不会在缓冲区中为空时消耗数据。

2)保证在生产者加入过程、消费者消耗过程中,不会产生错误的数据和行为。

这里需要保证两个地方的互斥访问,一个是缓冲区,一个是记录缓存区的变量count。但是粗暴的使用对象锁作为同步锁,这样一来,所有的生产、消费动作在执行过程中都需要抢占同一个同步锁,最终的结果是所有的生产、消费动作都被串行化了导致效率低下。如何开发出并行化程度更高的生产者-消费者模式实现版本 ?后续就是围绕这个问题来讲解的,先从基础知识开始讲的。

Java 对象结构与内置锁

Java内置锁的很多重要信息都存放在对象结构中 ,不同的JVM的对象结构的实现不一样,这里以HotSpot JVM为例。 Java对象( Object实例)结构包括三部分:对象头、对象体和对齐字节。 对象头包括三个字段,第一个字段叫作Mark Word(标记字),用于存储自身运行时的数据,例如GC标志位、哈希码、锁状态等信息。

Mark Word(标记字)字段主要用来表示对象的线程锁状态,另外还可以用来配合GC、存放该对象的hashCode。 Mark Word的位长度为JVM的一个Word大小,也就是说32位JVM的Mark Word为32位, 64位JVM的Mark Word为64位。

Java内置锁的状态总共有4种,级别由低到高依次为:无锁、偏向锁、轻量级锁和重量级锁。其实在JDK 1.6之前, Java内置锁还是一个重量级锁,是一个效率比较低下的锁,在JDK 1.6之后, JVM为了提高锁的获取与释放效率,对synchronized的实现进行了优化,引入了偏向锁、轻量级锁的实现,从此以后Java内置锁的状态就有了4种(无锁、偏向锁、轻量级锁和重量级锁),并且4种状态会随着竞争的情况逐渐升级,而且是不可逆的过程,即不可降级,也就是说只能进行锁升级(从低级别到高级别)。

[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-aCDbvEUj-1684810591620)(C:\Users\cheney\AppData\Roaming\Typora\typora-user-images\image-20230522213119357.png)]

  • 4位的Java对象分代年龄。在GC中,如果对象在Survivor区复制一次,年龄增加1。当对象达到设定的阈值时,将会晋升到老年代。默认情况下,并行GC的年龄阈值为15,并发GC的年龄阈值为6。由于age只有4位,因此最大值为15,这就是-XX:MaxTenuringThreshold选项最大值为15的原因。
  • identity_hashcode: 31位的对象标识HashCode(哈希码)采用延迟加载技术,当调用Object.hashCode()方法计算对象的HashCode后,其结果将被写到该对象头中。
  • thread: 54位的线程ID值为持有偏向锁的线程ID。
  • lock:锁状态标记位,占两个二进制位。
  • biased_lock:对象是否启用偏向锁标记,只占1个二进制位。为1时表示对象启用偏向锁,为0时表示对象没有偏向锁。
四种内置锁

在JDK 1.6版本之前,所有的Java内置锁都是重量级锁。重量级锁会造成CPU在用户态和核心态之间频繁切换,所以代价高、效率低。 JDK 1.6版本为了减少获得锁和释放锁所带来的性能消耗,引入了“偏向锁”和“轻量级锁”实现。所以,在JDK 1.6版本里内置锁一共有4种状态:无锁状态、偏向锁状态、轻量级锁状态和重量级锁状态,这些状态随着竞争情况逐渐升级。内置锁可以升级但不能降级,意味着偏向锁升级成轻量级锁后不能降级成偏向锁。这种能升级却不能降级的策略,其目的是为了提高获得锁和释放锁的效率。

  • 无锁状态

    Java对象未加锁状态就是无锁状态,例如对象刚刚创建,偏向锁标识位是0、锁状态01 。

  • 偏向锁状态

    偏向锁是指一段同步代码一直被同一个线程所访问,那么该线程会自动获取锁,降低获取锁的代价。如果内置锁处于偏向状态,当有一个线程来竞争锁时,先用偏向锁,表示内置锁偏爱这个线程,这个线程要执行该锁关联的同步代码时,不需要再做任何检查和切换。偏向锁在竞争不激烈的情况下效率非常高。偏向锁状态的Mark Word会记录内置锁自己偏爱的线程ID,如下,内置锁会将该线程当作自己的熟人。

    [外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-UI8ba3An-1684810591621)(C:\Users\cheney\AppData\Roaming\Typora\typora-user-images\image-20230522214223719.png)]

  • 轻量级锁状态

    有两个线程开始竞争这个锁对象时,情况发生变化了,不再是偏向(独占)锁了,锁会升级为轻量级锁,两个线程公平竞争,哪个线程先占有锁对象,锁对象的Mark Word就指向哪个线程的栈帧中的锁记录。 当锁处于偏向锁又被另一个线程所企图抢占时,偏向锁就会升级为轻量级锁。企图抢占的线程会通过自旋的形式尝试获取锁,不会阻塞抢锁线程,以便提高性能。

    [外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-HVtrzCLb-1684810591621)(C:\Users\cheney\AppData\Roaming\Typora\typora-user-images\image-20230522214423334.png)]

    自旋原理非常简单,如果持有锁的线程能在很短时间内释放锁资源,那么那些等待竞争锁的线程就不需要做内核态和用户态之间的切换进入阻塞挂起状态,它们只需要等一等(自旋),等持有锁的线程释放锁后即可立即获取锁,这样就避免用户线程和内核切换的消耗。但是,线程自旋是需要消耗 CPU的,如果一直获取不到锁,那线程也不能一直占用CPU自旋做无用功,所以需要设定一个自旋等待的最大时间。JDK 1.6之后引入了适应性自旋锁,适应性自旋锁意味着自旋的时间不是固定的,而是由前一次在同一个锁上的自旋时间以及锁的拥有者的状态来决定的。线程如果自旋成功了,下次自旋的次数就会更多,如果自旋失败了,自旋的次数就会减少。如果持有锁的线程执行的时间超过自旋等待的最大时间仍没有释放锁,就会导致其他争用锁的线程在最大等待时间内还是获取不到锁,自旋不会一直持续下去,这时争用线程会停止自旋进入阻塞状态,该锁膨胀为重量级锁。

  • 重量级锁状态

    重量级锁会让其他申请的线程之间进入阻塞,性能降低。重量级锁也就叫同步锁,这个锁对象Mark Word再次发生变化,会指向一个监视器对象,该监视器对象用集合的形式来登记和管理排队的线程。

偏向锁原理

偏向锁主要解决无竞争下的锁性能问题,所谓的偏向就是偏心,即锁会偏向于当前已经占有锁的线程。 在实际场景中,如果一个同步块(或方法)没有多个线程竞争,而且总是由同一个线程多次重入获取锁,如果每次还有阻塞线程,唤醒CPU从用户态转核心态,那么对于CPU是一种资源的浪费,为了解决这类问题,就引入了偏向锁的概念。

偏向锁的核心原理是:如果不存在线程竞争的一个线程获得了锁,那么锁就进入偏向状态,此时Mark Word的结构变为偏向锁结构, 然后线程的ID记录在锁对象的Mark Word中(使用CAS操作完成)。 以后该线程获取锁的时候判断一下线程ID和标志位,就可以直接进入同步块,连CAS操作都不需要,这样就省去了大量有关锁申请的操作,从而也就提升了程序的性能。 在大多数情况下,锁不仅不存在多线程竞争,而且总是由同一线程多次获得锁,因此,在大多数情况下偏向锁是能提升性能的。

如果是同一个线程多次获得锁,如果不是偏向锁将会导致无限制的获取锁、释放锁操作,这将导致无竞争情况下的系统底层的同步操作,性能很低。如果使用偏向锁,之前获得锁的线程再次获得锁时会判断偏向锁的线程ID是否指向自己。如果指向自己,拿锁成功。如果未指向当前线程,当前线程会采用CAS操作将Mark Word中线程ID设置为当前线程ID,如果CAS操作成功,那么获取偏向锁成功,去执行同步代码块,如果CAS操作失败,那么表示有竞争,抢锁线程被挂起,撤销占锁线程的偏向锁,然后将偏向锁膨胀为轻量级锁。

偏向锁的缺点:如果锁对象时常被多条线程竞争,偏向锁就是多余的,并且其撤销的过程会带来一些性能开销。

偏向锁的撤销

假如有多个线程来竞争偏向锁,此对象锁已经有所偏向,其他的线程发现偏向锁并不是偏向自己,就说明存在了竞争,尝试撤销偏向锁(很可能引入安全点),然后膨胀到轻量级锁。

以下两个操作导致撤销偏向锁:

1)多个线程竞争偏向锁。

2)调用偏向锁对象obj.的obj.hashCode()方法计算对象的哈希码之后,偏向锁将被撤销。因为对象头的Mark Word只有64位,然而54位被线程ID占用了,因此没有存放HashCode。所以只能撤销偏向锁,将Mark Word用于存放对象的哈希码。轻量级锁会在帧栈的Lock Record(锁记录)中记录哈希码,重量级锁会在监视器中记录哈希码,起到了对哈希码备份的作用。而偏向锁没有地方备份哈希码。

偏向锁的膨胀

如果偏向锁被占据,一旦有第二个线程争抢这个对象,因为偏向锁不会主动释放,所以第二个线程可以看到内置锁偏向状态,这时表明在这个对象锁上已经存在竞争了。 JVM检查原来持有该对象锁的占有线程是否依然存活,如果挂了,就可以将对象变为无锁状态,然后进行重新偏向,偏向为抢锁线程。如果JVM检查到原来的线程依然存活,就表明原来的线程还在使用偏执锁,发生锁竞争,撤销原来的偏向锁,将偏向锁膨胀(INFLATING)为轻量级锁。经验表明,其实大部分情况下进入一个同步代码块的线程都会是同一个线程。这也是为什么JDK会引入偏向锁出现的原因。所以,总体来说,使用偏向锁带来的好处还是大于偏向锁撤销和膨胀的所带来的代价。

全局安全点(非重点,记住结论即可)

JVM包含了一些虚拟机后台线程(包含VMThread、 GC线程、系统接收外部请求的线程等)以及用户定义线程(含线程池中的线程) 。VMThread线程的角色是一个超级线程,可以理解为JVM里面的线程母体或者所有线程的大总管。 VMThread是一个单例的对象(最原始的线程),所有其他的线程都由这个超级线程产生或触发。 VMThread线程负责完成一些基础性的VM工作,比如, VMThread线程可以协调其它线程达到全局安全点。 什么是全局安全点? JVM可以安全地进行一些全局性的操作,如GC、偏向锁解除等。在到达全局安全点后, JVM中的所有工作的用户线程都会被挂起,只有垃圾收集的native线程会持续不断地跑。也就是说,全局安全点会触发JVM的STW(Stop The World)停顿。所有的用户线程都会被暂停,没有任何响应,有点像卡死的感觉,才称为STW停顿。

有哪些场景需要让JVM进入到全局安全点呢?主要的场景如下:

  • 垃圾回收。
  • 偏向锁解除。
  • 由于代码优化所引起的指令重排。
  1. JVM设置一个global safe point标志位,各用户线程主动去检查这个标志位,发现全局安全点标志位为true时,就将自己挂起。
  2. JVM中所有的用户线程都到达安全点之后,此时所有的用户线程都已经挂起, JVM处于STW停顿状态, JVM也达到一个全局安全点。
  3. 可以简单地把用户线程的安全点理解为局部安全点,而把JVM中所有的用户线程都到达局部安全点之后, JVM所处的状态称为全局安全点。

**结论:**偏向锁的撤销操作需要依赖JVM的全局安全点,从而会带来STW停顿。如果偏向锁撤销操作发生频繁,会招来频繁的STW,从而导致严重的性能问题。所以,对于高并发应用来说,一般建议关闭偏向锁。具体的方式:可以在启动命令中加上以下JVM参数:-XX:-UseBiasedLocking关闭偏向锁之后, Java内置锁默认会进入轻量级锁状态。

轻量级锁的原理

引入轻量级锁的主要目的是在多线程竞争不激烈的情况下,通过CAS机制竞争锁减少重量级锁产生的性能损耗。重量级锁使用了操作系统底层的互斥锁(Mutex Lock),会导致线程在用户态和核心态之间频繁切换,从而带来较大的性能损耗。 轻量级锁是一种自旋锁,因为JVM本身就是一个应用,所以希望在应用层面上通过自旋解决线程同步问题。

  • 普通自旋锁:指当有线程来竞争锁时,抢锁线程会在原地循环等待,而不是被阻塞,直到那个占有锁的线程释放锁之后,这个抢锁线程才可以获得锁。 锁在原地循环等待的时候是会消耗CPU的, 适用于那些临界区代码耗时很短的场景,这样线程在原地等待很短的时间就能够获得锁了。 默认情况下,自旋的次数为10次 。用户可以通过-XX:PreBlockSpin选项来进行更改。
  • 自适应自旋锁 :自适应自旋解决的是“锁竞争时间不确定”的问题。 总的思想是:根据上一次自旋的时间与结果调整下一次自旋的时间。 谓自适应自旋锁,就是等待线程空循环的自旋次数并非是固定的,而是会动态地根据实际情况来改变自旋等待的次数,自旋次数由前一次在同一个锁上的自旋时间及锁的拥有者的状态来决定。轻量级锁也被称为非阻塞同步、乐观锁,因为这个过程并没有把线程阻塞挂起,而是让线程空循环等待 。自适应自旋锁的大概原理是:
    • 如果抢锁线程在同一个锁对象上之前成功获得过锁,那么JVM就会认为这次自旋也很有可能再次成功,因此允许自旋等待持续相对更长的时间。
    • 如果对于某个锁,抢锁线程在很少成功获得过,那么JVM将可能减少自旋时间甚至省略自旋过程,以避免浪费处理器资源。
轻量级锁的膨胀

轻量级锁的问题在哪里呢?虽然大部分临界区代码的执行时间都是很短的, 但是也会存在执行得很慢的临界区代码。临界区代码执行耗时较长,在其执行期间其他线程都在原地自旋等待,会空消耗CPU。因此,如果竞争这个同步锁的线程很多,就会有多个线程在原地等待继续空循环消耗CPU(空自旋),这会带来很大的性能损耗。 轻量级锁本意是为了减少多线程进入操作系统底层的互斥锁( Mutex Lock)的概率。所以,在争用激烈的场景下,轻量级锁会膨胀为基于操作系统内核互斥锁实现的重量级锁。

重量级锁原理

在JVM中,每个对象都关联一个监视器,这里的对象包含了Object实例和Class实例。监视器是一个同步工具,相当于一个许可证,拿到许可证的线程即可以进入临界区进行操作,没有拿到则需要阻塞等待。重量级锁通过监视器的方式保障了任何时间只允许一个线程通过受到监视器保护的临界区代码。 在Hotspot虚拟机中, 监视器是由C++类ObjectMonitor 实现的。竞争访问临界区时,所有请求锁的线程首先被放在这个竞争队列中。被阻塞的队列放入阻塞队列中。线程的阻塞或者唤醒都需要操作系统来帮忙,进程需要从用户态切换到内核态。 用户态是应用程序运行的空间,为了能访问到内核管理的资源(例如CPU、内存、 I/O),可以通过内核态所提供的访问接口实现,这些接口就叫系统调用。 由于JVM轻量级锁使用CAS进行自旋抢锁,这些CAS操作都处于用户态下,进程不存在用户态和内核态之间的运行切换,因此JVM轻量级锁开销较小。而JVM重量级锁使用了Linux内核态下的互斥锁(Mutex),这是重量级锁开销很大的原因。

synchronized的执行过程
  • 抢锁时先判断是否是偏向锁,如果是再判断Mark Word中线程ID是否是抢锁线程ID,如果是抢锁成功,开始执行临界区代码。

  • 如果如果Mark Word中线程ID并未指向抢锁线程,就通过CAS操作竞争锁。 如果竞争成功,就将Mark Word中线程ID设置为抢锁线程。如果CAS操作竞争失败,就说明发生了竞争,撤销偏向锁,进而升级为轻量级锁。

  • JVM使用CAS将锁对象的Mark Word替换为抢锁线程的锁记录指针,如果成功,抢锁线程就获得锁。如果替换失败,就表示其他线程竞争锁, JVM尝试使用CAS自旋替换抢锁线程的锁记录指针,如果自旋成功(抢锁成功),那么锁对象依然处于轻量级锁状态。如果JVM的CAS替换锁记录指针自旋失败,轻量级锁膨胀为重量级锁,后面等待锁的线程也要进入阻塞状态。

    总体来说,偏向锁是在没有发生锁争用的情况下使用;一旦有了第二个线程的争用锁,偏向锁就会升级为轻量级锁;如果锁争用很激烈,轻量级锁的CAS自旋到达阈值后(超过最大自旋数量),轻量级锁就会升级为重量级锁。

[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-BGXUNdx5-1684810591621)(C:\Users\cheney\AppData\Roaming\Typora\typora-user-images\image-20230523094758377.png)]

线程间的通信

线程是操作系统调度的最小单位,如果每个线程间都孤立地运行,就会造资源浪费。线程的通信可以被定义为:当多个线程共同操作共享的资源时,线程间通过某种方式互相告知自己的状态,以避免无效的资源争夺。 “等待-通知”通信方式是Java中使用普遍的线程间通信方式,其经典的案例就是“生产者-消费者”模式。 Java语言中“等待-通知”方式的线程间的通信使用对象的wait()、 notify()两类方法来实现。每个Java对象都有wait()、 notify()两类实例方法,属于Object对象的方法。

  • wait()方法,对象的wait()方法的主要作用是让当前线程阻塞并等待被唤醒。使用wait()方法时也一定需要放在同步块中,线程进入阻塞队列等待唤醒, wait(long timeout) 限时等待版本,指定的时间timeout用完,线程不再等待被唤醒。

  • notify()方法 ,唤醒在等待的线程。使用notify()方法时也需要放在同步块中。 notify()方法的主要作用如下: locko.notify()调用后,唤醒locko监视器等待集中的第一个等待线程;被唤醒的线程进入EntryList,其状态从WAITING等待状态变成BLOCKED。locko.notifyAll()被调用后,唤醒locko监视器等待集中的全部等待线程;所有被唤醒的线程进入EntryList,线程状态从WAITING等待状态变成BLOCKED。EntryList中的线程抢夺到监视器Owner权利之后,线程的状态从BLOCKED变成Runnable,具备重新执行的资格。

  • 需要在 synchronized 同步块的内部使用 wait 和 notify,在调用同步对象的wait()和notify()系列方法时,“当前线程”必须拥有该对象的同步锁,也就是说, wait()和notify()系列方法需要在同步块中使用,否则JVM会抛出非法监视器状态异常。

  • 使用wait()方法时使用while进行条件判断:如果是在某种条件下进行等待,对条件的判断不能使用if语句做一次性判断,而是使用while循环进行反复判断。只有这样才能在线程被唤醒后继续检查wait的条件,并在条件没有满足的情况下继续等待。

    public T fetch() throws Exception
    {
        while (amount <= 0){ // 不能改成 if ,如果if被唤醒之后不会再次判断amount
            synchronized (NOT_EMPTY)
            {
                Print.tcfo("队列已经空了! ");
                //等待未空通知
                NOT_EMPTY.wait();
            }
        }
        //省略其他
    }
    

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

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

相关文章

PHP操作宝塔面板Api,宝塔服务器搭建,API接口使用教程

最近两个月都在写Bty项目&#xff0c;所以收集了很多很多宝塔常用到的一些Api接口&#xff0c;官方文档虽然写了一点&#xff0c;但是始终是不怎么全的&#xff0c;下面我们来看看宝塔面板的接口如何抓取 接口抓取 1、登录宝塔面板 2、找到自己想要的功能 3、f12打开审查元素&a…

韵达转债上市价格预测

韵达转债 基本信息 转债名称&#xff1a;韵达转债&#xff0c;评级&#xff1a;AA&#xff0c;发行规模&#xff1a;24.5亿元。 正股名称&#xff1a;韵达股份&#xff0c;今日收盘价&#xff1a;12.23元&#xff0c;转股价格&#xff1a;12.15元。 当前转股价值 转债面值 / 转…

vue关于静态路由和动态路由:

这篇文章写得超详细&#xff01;&#xff01;&#xff01; &#x1f449;vue实现动态路由一步到位_vue动态路由怎么实现_ds_surk的博客-CSDN博客 目录 静态路由的配置&#xff1a; 步骤&#xff1a; 动态路由的配置&#xff1a; 步骤&#xff1a; 代码实现&#xff1a; …

网易云音乐开发--search模块基本功能实现(除历史记录模块)

search头部搭建 老样子搭建一个search搜索页面 还有一块没有实现&#xff0c;那就是让输入框默认的文本变换颜色 微信小程序: input输入框placeholder样式的修改_微信小程序placeholder样式_酷伊奥的博客-CSDN博客 百度搜索了一下&#xff0c;找到了这个大佬的解决方案。很nic…

ICV:中国的数字经济与5G市场研究报告

近日&#xff0c;专注于前沿科技领域的国际咨询机构ICV发布了《中国的数字经济与5G市场研究报告》。报告指出&#xff0c;随着5G商用的发展&#xff0c;5G对经济社会的影响逐步显现&#xff0c;其影响突出体现在对数字产业发展的带动上。随着5G应用的不断创新与扩散&#xff0c…

chrome插件打包之后,显示此扩展程序可能已损坏

每日鸡汤&#xff0c;每个你想要学习的瞬间都是未来的你向自己求救 问题是这样的&#xff0c;我们有一个chrome插件的项目&#xff0c;但是我也没有参与开发&#xff0c;可以说此前对chrome插件一窍不通。但是今天呢&#xff0c;有个bug&#xff0c;要我改&#xff0c;我就拉一…

基于Java+SpringBoot+Vue的校园交友网站的设计与实现

博主介绍&#xff1a; 大家好&#xff0c;我是一名在Java圈混迹十余年的程序员&#xff0c;精通Java编程语言&#xff0c;同时也熟练掌握微信小程序、Python和Android等技术&#xff0c;能够为大家提供全方位的技术支持和交流。 我擅长在JavaWeb、SSH、SSM、SpringBoot等框架下…

为什么x86架构一个字节是8个bit

探究计算机存储的历史&#xff1a;为什么x86架构下一个字节是8个bit 原文链接&#xff1a;Some possible reasons for 8-bit bytes About author I’m a software developer. I live in Montreal. I sometimes give talks. Most of my income comes from my programming zines…

【博览群书】《实战大数据》——属于我的第一本大数据图书

文章目录 前言简介目录其他 前言 Hello家人们&#xff0c;博主前不久参加了CSDN图书馆和机械工业出版社联合举办的图书类活动&#xff0c;很荣幸在活动中获得了属于自己的第一本大数据图书&#xff0c;《实战大数据—— 分布式大数据分析处理系统开发与应用》。作为大数据专业…

五、数据仓库详细介绍(建模)理论篇

1 前言 大家好&#xff0c;本篇文章是数仓详细介绍系列的第四篇。 第一篇是简单介绍&#xff0c;后三篇属于数仓设计部分&#xff1a; 数仓概述&#xff0c;这一篇我给大家简单介绍了数据仓库的基本概念和大致构建过程&#xff0c;没有过多深入主要是给大家有个基本的了解。 数…

数字孪生应用落地,“未来之城”或成智慧城市新形态

“最近&#xff0c;到北京大学人民医院西直门院区就诊的患者发现&#xff1a;动辄绵延数百米的“车龙”消失了&#xff0c;周边道路也变得畅通起来。高峰期排队进院花费的时间&#xff0c;从过去1个多小时减至现在的10分钟左右。 与之相隔不远的北京市西城区城市管理委员会办公…

Java程序设计入门教程--类的行为

类的成员方法是Java描述类对象行为的途径。成员方法的定义应包含两部分内容&#xff1a;方法声明和方法体。 方法定义常用的格式如下&#xff1a; [public/protected/private][static][final/abstract] returnType methodName([param List]) [throw…

ER图和数据库模型图怎么使用呢?

1. 简介 对于从事数据库结构设计相关人员而言&#xff0c;我们通常会在设计的不同阶段用到ER图和数据库模型图&#xff0c;用来描述数据之间的组成结构和数据间的关系&#xff0c;但是很多画图人员会把它们两者给搞混了&#xff0c;下面就来聊聊它们之间的区别。 1、ER图全称…

【TES600】基于XC7K325T与TMS320C6678的通用信号处理平台

板卡概述 TES600是一款基于FPGA&#xff0b;DSP协同处理架构的通用高性能实时信号处理平台&#xff0c;该平台采用1片TI的KeyStone系列多核浮点/定点DSP TMS320C6678作为主处理单元&#xff0c;采用1片Xilinx的Kintex-7系列FPGA XC7K325T作为协处理单元&#xff0c;具有1个FMC子…

XML配置方式SSM框架西蒙购物网

文章目录 一、网站功能需求二、网站设计思路&#xff08;一&#xff09;设计模式&#xff08;二&#xff09;网站前台&#xff08;三&#xff09;网站后台1、用户管理2、类别管理3、商品管理4、订单管理 &#xff08;四&#xff09;购物流程图 三、网站运行效果四、网站实现步骤…

大一新生如何自学JavaScript?

前言 针对于题主的情况&#xff0c;我特意做了一份Js方面的知识路线图以及一些知识点讲解的资源链接&#xff0c;希望对于还未学习Js或者已经学习了Js但没有但没有一个系统路线的小伙伴能有一些帮助~ 先放上路线图 img 部分重要知识点 基础性知识 声明变量 null 和 undefi…

二总线-MBus讲解

二总线的叫法演变是从多线到总线再到二总线这么一个过程&#xff0c;尤其在楼宇的消防领域&#xff0c;报警的设备总线基本已经是二总线了&#xff0c;其特点就是电源与通信一起传输&#xff0c;本质上是一个电力载波的思路。那么现在的powerbus二总线又是一个极端&#xff0c;…

chatgpt赋能Python-python_judge

Python Judge&#xff1a;一个高效的Python代码评测平台 如果你是一个Python程序员或是教师&#xff0c;你一定需要测试你的Python代码表现。Python Judge是一个专门为Python程序员设计的代码评测平台&#xff0c;它可以帮助你测试你的Python代码的运行时间、空间占用和准确性…

DDD在前端应用中的一些思考

作者&#xff1a;吴尔畅 DDD旨在解决业务逻辑的复杂性&#xff0c;而业务逻辑大部分场景下不存在于前端。但在一些复杂的应用中&#xff0c;前端可能需要处理一些业务逻辑&#xff0c;此时DDD的一些思想和方法可能有助于组织前端代码&#xff0c;使其更易于理解和维护。 一、什…

【STL模版库】list介绍及使用 {inserterase的迭代器失效问题,vector_sort VS list_sort,list的其他接口函数}

一、list的介绍 list是可以在常数时间内在任意位置进行插入和删除的序列式容器&#xff0c;并且该容器可以前后双向迭代。list的底层是双向带头循环链表结构&#xff0c;双向链表中每个元素存储在互不相关的独立节点中&#xff0c;在节点中通过指针指向其前一个元素和后一个元…