并发编程

news2024/11/23 12:11:12

什么是并发编程

并行:在同一个时间节点上,多个线程同时执行(是真正意义上的同时执行)

并发:一个时间段内,多个线程依次执行。

并发编程:在例如买票、抢购、秒杀等等场景下,有大量的请求访问同一个资源。会出现线程安全的问题,所以需要通过编程来解决多个线程依次访问资源,称为并发编程。

并发编程的根本原因:

  1. 多核cpu的出现,真正意义上可以做到并行执行
  2. java内存模型(JMM)

java内存模型,规范了Java虚拟机与计算机内存是如何协同工作的。

将内存分为主内存和工作内存。两个线程同时操作,会导致出错,本质原因在于内存模型设计。

共享数据存储在主内存中,每个线程都有各自的工作内存。操作共享数据时,会将主内存中的数据复制一份到工作内存中操作,操作完成后,再写回到主内存中。

但是一旦两个线程同时进行操作,读取共享数据,两个线程各自在工作内存中修改后,同时又写到主内存,这样就会与预期的结果不同。(AB两个线程同时操作变量n)

一、并发编程核心问题

由于java内存模型的设计,多线程操作一些共享的数据时,出现以下3个问题:

(1)不可见性:A线程在工作内存中操作共享数据时,B线程不知道A线程已经修改了数据。

(2)无序性:为了优化性能,有时候会改变程序中语句的先后顺序,以提高速度。

int a = 10;

io.read();//从其他地方读数据

int b = 5;

int c=a+b;

但是为了优化,第2行需要从其他地方读数据 需要时间;系统可能将3行代码乱序执行,例如 1、3、2的顺序执行。

有时,看似没有关系的代码乱序执行,可能会对后面的代码产生影响。

(3)非原子性

一个或多个操作在CPU执行的过程中不被中断的特性,我们称为原子性。 原子性是拒绝多线程交叉操作的,同一时刻只能有一个线程来对它进行操作

高级语言里一条语句往往需要多条CPU指令完成。如 count++,至少需要三条CPU指令。

  • 首先,需要把变量 count 从主内存加载到工作内存;
  • 之后,在工作内存执行 +1 操作;
  • 最后,将结果写入主内存;

解决办法

  1. 让不可见变为可见
  2. 让无序变为有序
  3. 非原子执行变为原子(加锁),由于线程切换执行导致

缓存(工作内存) 带来了不可见性;指令重排优化带来了无序性;线程切换带来了非原子性。

volatile可以解决前两个问题,加锁可以解决所有问题。

二、volatile关键字

volatile修饰的共享变量(类的成员变量、类的静态成员变量),被一个线程修改后,可以同步更新到其他线程,让其他线程中立即可见。volatile修饰的共享变量,指令是有顺序的。

但是volatile不能解决原子性问题,原子性问题由于线程切换执行导致。

volatile底层实现原理:

使用内存屏障(指令)进行控制。

  • 有序性实现:volatile修饰的变量,在操作前添加内存屏障,来禁止指令重排序。
  • 可见性实现:volatile修饰的变量添加内存屏障之外,还通过缓存一致性协议(MESI)将数据写回到主内存,其他工作内存嗅探后,如果自己工作内存中的数据过期,重新从主内存读取最新的数据。

三、如何保证原子性

同一时刻只有一个线程执行,称之为互斥。如果我们能够保证对共享变量的修改是互斥的,那么就能保证原子性了。

1、锁

只有通过加锁的方式,让线程互斥执行,来保证一次只有一个线程对共享资源进行访问

synchronized:关键字;修饰代码块、方法;自动获取锁,自动释放锁

ReentrantLock:类;只能对某段代码修饰;需要手动加锁,手动释放锁

2、原子变量

在java中还提供一些原子类,在低并发情况下使用,是一种无锁实现。

JUC(java.util.concurrent包)中,里面的locks包和atomic包,它们可以解决原子性问题。

1.原子类原理(AtomicInteger 为例)

原子类的原子性是通过volatile+CAS实现原子操作的。

低并发情况下:使用原子类 AtomicInteger,底层有一个变量通过volatile关键字修饰的,结合CAS机制实现。

2.CAS(重点)

采用CAS机制(Compare-And-Swap比较并交换),是一种无锁实现,在低并发情况下使用。CAS是乐观锁的方式,采用的是自旋的思想。

采用自旋思想:

(1)第一次从内存中读到内存值V

(2)对数据进行修改,将改变后的值写入到内存时,需要重新读取内存中最新的值,作为预期值A

(3)在写入前比较预期值与内存值,看是否一致:

  • 如果一致,说明其他线程没有修改内存中的值,将更新后的值,写入到内存;
  • 如果不一致,说明其他线程修改了主内存中的值,就需要重新计算变量值,反复这一过程。--->自旋

优点:

  • 不加锁,所有的线程都可以对共享数据操作;
  • 适合低并发使用,因为所有线程不会进入阻塞状态

缺点:

  • 大并发时,不停自旋判断,导致cpu占用率高
3.ABA问题

ABA问题,即线程1读取到内存值,线程2将内存值由A改为了B,再由B改为了A。当线程1去判断时,预期值与内存值相同,无法分辨内存值是否发生过变化。

通过设置版本号,每次操作改变版本号来避免ABA问题。如原先的内存值为(A,1),线程修改为(B,2),再修改为(A,3)。此时另一个线程使用预期值(A,1)与内存值(A,3)进行比较,只需要比较版本号1和3,即可发现该主内存中的数据被更新过了。

四、java中的锁

一些锁的名称指的是锁的特性、设计、状态,并不是都是锁。

1、乐观锁/悲观锁

乐观锁:没有加锁,不加锁的方式是没有问题的。例如CAS机制

悲观锁:必须加锁。悲观的认为,不加锁的并发操作一定会出问题。

2、可重入锁

synchronized和ReentrantLock是可重入锁,可以避免死锁。

A方法和B方法是两个同步方法,在同一个类中,用同一把锁,先进入到同步方法A中,锁被使用,在方法A调用方法B依然可以进入到方法B。(此时方法A还没有释放锁)

如果不是可重入锁的话,方法B不会被当前线程执行。

3、读写锁

ReentrantReadWriteLock,里面有一个读锁和写锁。

  • 读读不互斥:只有读没有写,可以多个线程同时读
  • 读写互斥:一旦有写操作,读写不同同时进行。
  • 写写互斥:多个写互斥
4、分段锁

不是锁,是一种锁实现思想:用于将数据分段,并在每个分段上都会单独加锁,以提高并发效率。

举例:Hashtable是将整合hash表格锁住了,一次只能有一个线程操作并发量低,效率低。

ConcurrentHashMap将每个哈希位置当做一个锁,可以有多个线程对map进行操作,一次只能有一个线程操作一个位置.

5、自旋锁

不是锁。是自己重试,当线程抢锁失败后,重试几次,如果抢到锁了就继续,如果抢不到就阻塞线程。

6、共享锁/独占锁

共享锁:一个锁可被多个线程共享,例如读写锁中的 读锁。

独占锁:一次只能有一个线程操作。例如:Synchronized、ReentrantLock,读写锁中的 写锁。

7、公平锁/非公平锁

公平锁:按照请求的顺序执行(排队,先来来执行)。

非公平锁:不按照请求顺序执行,谁先抢到谁先执行。

synchronized是一种非公平锁。ReentrantLock默认是非公平锁,但是底层可以通过AQS来实现线程调度,使其变成公平锁。

五、synchronized锁

1、锁的状态

synchronized锁的底层实现中,提供4种锁的状态,又来区别对待。(锁的状态在同步锁对象的对象头中,有一个区域叫Mark Word中存储)

  1. 无锁状态:没有线程进入。
  2. 偏向锁:始终只有一个线程访问同步代码快,记录线程的编号,快速的获取锁。
  3. 轻量级锁:当锁状态为偏向锁时,还有其他线程访问,此时升级为轻量级锁。特点:当一个线程获取锁之后,其他线程不会阻塞,会通过自旋方式获取锁,提高效率。
  4. 重量级锁:当锁的状态为轻量级锁时,线程自旋达到一定的次数,还没有获取到锁,就会进入到阻塞状态,锁状态升级为重量级锁,等待操作系统调度。

2、对象结构

在Hotspot虚拟机中,对象在内存中分为三块区域:对象头、实例数据和对齐填充;synchronized使用的锁对象是存储在对象头里。

对象头中有一块为Mark Word,用于存储对象自身的运行时数据,如哈希码(HashCode)、GC 分代年龄、锁状态标志、线程持有的锁、偏向线程ID等等。

32位操作系统Mark Word为32bit,64 位操作系统Mark Word为64bit。下面就是对象头的一些信息:

3、synchronized锁实现

synchronized锁是依赖底层编译后的指令,添加锁的监视器实现,需要我们提供一个同步对象,来记录是否加锁、以及锁的状态。

六、AQS

全称为(AbstractQueuedSynchronizer),这个类在java.util.concurrent.locks包下面。抽象同步队列,是java代码实现线程同步非常重要的一个底层实现类。

思路:

  • 在类中定义了一个state变量(初始化为0,表示有没有线程访问共享资源)和一个双向链表队列(head结点代表当前占用的线程)。
  • 有线程访问时,第一个抢到执行权的线程放在头节点,将state加1。期间如果有其他的线程访问时,如果state=1,将其他线程添加到队列中,等待锁的释放。

state由于是多线程共享变量,所以定义成volatile,以保证state的可见性,但不能保证原子性,所以AQS提供了对state的原子操作方法,保证了线程安全。

队列由Node对象组成,Node是AQS中的内部类。

AQS 的锁模式分为:独占和共享

独占锁:每次只能有一个线程持有锁,比如ReentrantLock是以独占方式实现的。

共享锁:允许多个线程同时获取锁,并发访问共享资源,比如ReentrantReadWriteLock

ReentrantLock锁实现

ReentrantLock是java.util.concurrent.locks包下的类,实现Lock接口。

public class ReentrantLock implements Lock, java.io.Serializable{ }

ReentrantLock基于AQS,在并发编程中可以实现公平锁和非公平锁来对共享资源进行同步。ReentrantLock类内部总共存在Sync、NonfairSync、FairSync三个,NonfairSync与FairSync类继承自Sync类,Sync类继承自AbstractQueuedSynchronizer抽象类。

ReentrantLock构造方法

  • 无参构造方法默认是非公平实现
  • 有参构造方法可以选择,true—公平实现,false—非公平实现

NonfairSync类继承了Sync类,表示采用非公平策略获取锁,其实现了Sync类中抽象的lock方法。 

static final class NonfairSync extends Sync {
//若通过 CAS 设置变量 state 成功,就是获取锁成功,则将当前线程设置为独占线程。
//若通过 CAS 设置变量 state 失败,就是获取锁失败,则进入 acquire 方法进行后续处理。
    final void lock() {
        if (compareAndSetState(0, 1))//每个线程进入到lock方法时,会尝试获取锁,有可能获取到了
            setExclusiveOwnerThread(Thread.currentThread());
        else//获取不到,将线程添加到队列中,排队获取锁
            acquire(1);
    }
	//尝试获取锁,无论是否获得都立即返回
    protected final boolean tryAcquire(int acquires) {
        return nonfairTryAcquire(acquires);
    }
}

FairSync类也继承了Sync类,表示采用公平策略获取锁,其实现了Sync类中的抽象lock方法。

static final class FairSync extends Sync {
    final void lock() {//公平锁,默认排队获取锁
        acquire(1);
    }
}

七、JUC常用类

1、ConcurrentHashMap

HashMap是线程不安全的,不能在多线程环境下使用

Hashtable是线程安全的,但是synchronized直接锁住的是整个方法,效率低(public synchronized V put(K key,V value{}))

ConcurrentHashMap是线程安全的,效率高于Hashtable。

不像Hashtable将整个方法锁起来,将每个位置的第一个节点当做锁对象,将锁的力度减小,进而提高了效率;同时可以有多个线程对ConcurrentHashMap进行操作,如果多个线程操作的是同一个位置,那么必须等待,因为用的是同一把锁。当算出的位置,第一个节点为null时,采用CAS机制添加。

Hashtable和ConcurrentHashMap不支持存储null键和null值。源码中看到为null,就报空指针异常。为什么这样设计呢?

为了消除歧义,因为无法分辨key的值为null还是key不存在返回的null,这在多线程里面是模糊不清的,所以压根就不让 put null。

2、CopyOnWriteArrayList

ArraayList是线程不安全的,在高并发情况下可能会出现问题;

Vector是线程安全的,get、add方法都加锁,读读都互斥,效率低。

CopyOnWriteArrayList在读的时候不加锁,写入也不会阻塞读取操作,只有同时写入和写入之间需要进行同步等待,提高了读的效率。

CopyOnWriteArrayList在进行add、set等修改操作时,是通过底层数组的副本实现的。先将底层数组进行复制,修改复制出来的数组,修改后将数据赋值给原来的底层数组。写入时,不影响其他线程读

3、CopyOnWriteArraySet

CopyOnWriteArraySet线程安全的,底层使用的是CopyOnWriteArrayList不能存储重复数据

4、辅助类 CountDownLatch

CountDownLatch允许一个线程 等待其他线程各自执行完毕后再执行。底层实现是通AQS来完成的,创建CountDownLatch对象时指定一个初始值(线程的数量)。每当一个线程执行完毕后,AQS内部的state就-1,当state的值为0时,表示所有线程都执行完毕,然后等待的线程就可以恢复工作了。

八、对象引用

在JDK1.2版之后,Java对引用的概念进行了扩充,将引用分为:

  • 强引用
  • 软引用(SoftReference)
  • 弱引用(WeakReference)
  • 虚引用(PhantomReference)

这4种引用强度依次逐渐减弱。除强引用外,其他3种引用均可以在java.lang.ref包中找到它们的身影。

1、强引用(不是垃圾)

有引用指向该对象,Object obj = new Object(); 这种情况下new出来的对象不能被垃圾回收的。

软引用、弱引用、虚引用都是用来标记对象的一种状态。当一些对象称为垃圾后,通过不同的状态来判断什么时候被清理。可以继承SoftReference、WeakReference、PhantomReference或者把自己的对象添加到软、弱、虚的对象中。

2、软引用(内存不足时回收)

被软引用关联的对象,被判定为垃圾时,可以不用立即回收;直到垃圾回收后内存仍然不够用时,才会回收软引用关联的对象。

Object obj = new Object();// 声明强引用
SoftReference<Object> sf = new SoftReference<>(obj);
obj = null; //销毁强引用

3、弱引用(发现时回收)

弱引用管理的对象,只能存活到下一次垃圾回收。

4、虚引用(对象回收跟踪)

最弱的引用,对对象的生命周期没有任何的影响,跟踪对象是否被回收(如果对象被回收后,会给队列返回信息)

Object obj = new Object();
ReferenceQueue phantomQueue = new ReferenceQueue();//声明引用队列
PhantomReference<Object> sf = new PhantomReference<>(obj,phantomQueue);//声明虚引用(还需要传入引用队列),如果对象被回收后,会给队列返回信息
obj = null;

九、线程池

1、池的概念

如果并发的线程数量很多,并且每个线程都是执行一个时间很短的任务就结束了,频繁创建线程和销毁线程需要时间。 可以事先创建出一些连接对象,每次使用时,从集合中直接获取,用完不销毁。减少频繁创建、销毁。

在 JDK5 版本中增加了内置线程池实现 ThreadPoolExecutor,同时提供了Executors来创建不同类型的线程池。

池的好处:减少频繁创建销毁时间,统一管理线程,提高速度。

2、ThreadPoolExecutor类

Java.uitl.concurrent.ThreadPoolExecutor类是线程池中最核心的一个类,因此如果要透彻地了解Java中的线程池,必须先了解这个类。

ThreadPoolExecutor继承了AbstractExecutorService类,并提供了四个构造器,但是前三个构造器都是调用的第四个构造器进行的初始化工作。

3、构造器中各个参数的含义

1.corePoolSize

核心池的大小,一旦创建不会被销毁的;非核心池中的线程,在没有被使用时,可以被回收。

2.maximumPoolSize

线程池最大线程数量,包含核心池中的数量。

3.keepAliveTime

非核心线程池中的线程,在不被使用后,多久就终止。(假如核心线程池5个,最大数量10,但是任务少的情况下,核心线程池够用了,等多长时间,就把非核心线程池中的线程终止)

4.unit

为keepAliveTime设置时间单位,有7种取值。

5.workQueue

一个阻塞队列,用来存储执行的任务。有以下工作队列:

  1. ArrayBlockingQueue:数组实现的有界阻塞队列,创建时必须设置长度,按FIFO排序。
  2. LinkedBlockingQueue:链表结构的阻塞队列,按FIFO排序任务,容量可以选择进行设置,不设置是一个最大长度为 Integer.MAX_VALUE;
6.threadFactory

创建线程的工厂

7.handler

拒绝策略。当线程池中的核心池、阻塞队列、非核心池已满时,如果有任务继续到达,如何执行。有以下四种拒绝策略:

  1. AbortPolicy();直接抛出异常,拒绝执行。
  2. CallerRunsPolicy();交由当前提交任务的线程执行(如果任务被拒绝了,则由提交任务的线程(例如:main)直接执行此任务)
  3. DiscardOldestPolicy();丢弃等待时间最长的任务。
  4. DiscardPolicy();直接丢弃,不执行。

4、线程池的执行

创建完成ThreadPoolExecutor之后,当向线程池提交任务时,通常使用execute方法。 execute方法的执行流程图如下:

当请求到来时,如果核心线程池没有满,就提交到核心线程池,如果核心线程池已满,则添加到队列中(前提是队列没有满);如果队列中已满,则在非核心线程中创建线程,直到到达最大线程数量;如果非核心线程池也已经满了,那么则使用适当的拒绝策略处理。

execute与submit的区别

  • execute() 提交任务,没有返回值
  • submit() 提交任务,可以有返回值(任务需要实现callable接口)

关闭线程池

  • shutdownNow() 直接关闭,对还未开始执行的任务全部取消
  • shutdown() 等待任务执行完关闭
//任务
public class MyTask implements Runnable {
    private int taskNum;

    public MyTask(int num) {
        this.taskNum = num;
    }

    @Override
    public void run() {
        try {
            Thread.currentThread().sleep(4000);
        } catch (InterruptedException e) {
            e.printStackTrace();
        }
        System.out.println(Thread.currentThread().getName()+":task "+taskNum+"执行完毕");
    }
}
public class Test {
    public static void main(String[] args) {
        //创建线程池
        ThreadPoolExecutor executor = new ThreadPoolExecutor(2,
                                     5, 200,
                                     TimeUnit.MILLISECONDS,
                                     new ArrayBlockingQueue<>(2),
                                     Executors.defaultThreadFactory(),
                                     new ThreadPoolExecutor.CallerRunsPolicy());
        executor.prestartAllCoreThreads();

        for(int i=1;i<=8;i++){
            MyTask myTask = new MyTask(i);
            executor.execute(myTask);//添加任务到线程池
           //Future<?> submit = executor.submit(myTask);
                        //submit.get();//返回值
        }
        executor.shutdown();
    }
}

十、ThreadLocal

本地线程变量,可以为每个线程都创建一个属于自己的变量副本,使得多个线程之间隔离,不影响。(在每一个线程里都有一个自己的localNum)

package com.ffyc.javapro.thread.threadlocal;

public class ThreadLocalDemo {

    //创建一个ThreadLocal对象,复制保用来为每个线程会存一份变量,实现线程封闭
    private  static ThreadLocal<Integer> localNum = new ThreadLocal<Integer>(){
        @Override
        protected Integer initialValue() {
            return 0;
        }
    };

    public static void main(String[] args) {
          new Thread(){
              @Override
              public void run() {
                   localNum.set(1);
                  try {
                      Thread.sleep(2000);
                  } catch (InterruptedException e) {
                      e.printStackTrace();
                  }
                  localNum.set(localNum.get()+10);
                  System.out.println(Thread.currentThread().getName()+":"+localNum.get());//11
              }
          }.start();

        new Thread(){
            @Override
            public void run() {
                localNum.set(3);
                try {
                    Thread.sleep(2000);
                } catch (InterruptedException e) {
                    e.printStackTrace();
                }
                localNum.set(localNum.get()+20);
                System.out.println(Thread.currentThread().getName()+":"+localNum.get());//23
            }
        }.start();
        System.out.println(Thread.currentThread().getName()+":"+localNum.get());//0(main线程)
    }
}

ThreadLocal底层实现:

在一个线程中使用ThreadLocal时,为每个当前线程创建了一个ThreadLocalMap,看似用唯一的ThreadLocal对象作为键,其实每个线程中都有一个属于自己的ThreadLocalMap,所以每个线程中都有一个自己的变量副本。

ThreadLocal会造成内存泄漏:

由于ThreadLocal被弱引用关联,有可能在下一次垃圾回收时被回收掉,会导致key为null,而value还存在着强引用。但是value却被Entry对象关联,Entry又被ThreadLocalMap关联,ThreadLocalMap又被Thread关联,要是当前线程长期不结束,value就不能被销毁,但是key有可能已被回收,就获取不到value造成内存泄漏。

正确的使用:不再使用这个本地线程变量后,将其主动删除掉,调用remove方法删除。

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

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

相关文章

「常识」浮点数和定点数

浮点数和定点数 本篇文章旨在简短的介绍浮点数、定点数的定义&#xff0c;以及一些常见的数制、补码。 一、常识 如果缺少以下常识的话&#xff0c;将很难理解浮点数和定点数的概念。 1、数 自然数整数/分数小数&#xff1a;有限小数、无限循环小数、无限不循环小数实数&a…

独创改进 | RT-DETR 引入 Asymptotic Hybrid Encoder | 渐进混合特征解码结构

本专栏内容均为博主独家全网首发,未经授权,任何形式的复制、转载、洗稿或传播行为均属违法侵权行为,一经发现将采取法律手段维护合法权益。我们对所有未经授权传播行为保留追究责任的权利。请尊重原创,支持创作者的努力,共同维护网络知识产权。 文章目录 网络结构实验结果…

97. 交错字符串

题目链接&#xff1a;力扣&#xff08;LeetCode&#xff09;官网 - 全球极客挚爱的技术成长平台 解题思路&#xff1a;动态规划。 如果s1.length()s2.length ! s3.length()&#xff0c;直接返回false&#xff0c;否则使用动态规划求解。定义状态&#xff1a;dp[i][i]&#xff…

在CPU上运行yolov5

https://blog.csdn.net/weixin_54721509/article/details/122983561 前提是安装好了 python和 opencv基本环境 &#xff0c;如果没按转可以参考&#xff1a;https://blog.csdn.net/yangshengwei230612/article/details/127606771 安装pytorch 进入pytorch官网 https://pytor…

No175.精选前端面试题,享受每天的挑战和学习

🤍 前端开发工程师(主业)、技术博主(副业)、已过CET6 🍨 阿珊和她的猫_CSDN个人主页 🕠 牛客高级专题作者、在牛客打造高质量专栏《前端面试必备》 🍚 蓝桥云课签约作者、已在蓝桥云课上架的前后端实战课程《Vue.js 和 Egg.js 开发企业级健康管理项目》、《带你从入…

SOLIDWORKS PDM 2024数据管理5大新功能

1. 改进的视觉内容 • 通过装配体可视化功能&#xff0c;在 SOLIDWORKS 中以图形方式查看零部件数据&#xff0c;如工作流程状态。• 使用特定图标迅速识别焊件切割清单零部件。 优点&#xff1a;重要数据和系统信息一目了然。 2.增强的数据保护和跟踪功能 •保护“包含”和…

D-LINK SQL注入漏洞让攻击者获得管理员权限

D-Link DAR-7000 设备中发现了一个名为 SQL 注入的安全漏洞。 SQL注入是一种恶意攻击&#xff0c;它利用Web应用程序中的漏洞注入恶意SQL语句并获得对数据库的未经授权的访问。 此技术允许攻击者查看、修改和删除数据库中的数据&#xff0c;这可能对数据的机密性、完整性和可…

leetcode经典面试150题---4.删除有序数组中的重复项II

目录 题目描述 前置知识 代码 方法一 双指针 思路 图解 实现 复杂度 题目描述 给你一个有序数组 nums &#xff0c;请你 原地 删除重复出现的元素&#xff0c;使得出现次数超过两次的元素只出现两次 &#xff0c;返回删除后数组的新长度。 不要使用额外的数组空间&…

YouTube博主数据信息资源

YouTube博主数据信息资源 &#x1f525;我是一位拥有10年编程经验的程序猿&#xff0c;为你带来一个全新的优质资源 &#x1f50d;您是否在寻找最新、最活跃的YouTube博主数据&#xff0c;以助力你的项目、营销或研究&#xff1f; 我们的数据&#xff0c;您的优势&#xff1a;…

【《数据库系统原理》学习笔记-第1章】

数据库系统概述 概述基本概念数据数据库数据库管理系统数据库系统 管理发展『人工管理阶段』『文件系统阶段』『数据库系统阶段』 主页传送门&#xff1a;&#x1f4c0; 传送 概述 数据库系统原理课程是一门理论与实践相结合的课程&#xff0c;通过大数据技术、数据保护、关系…

【Leetcode】【每日一题】【中等】274. H 指数

力扣&#xff08;LeetCode&#xff09;官网 - 全球极客挚爱的技术成长平台备战技术面试&#xff1f;力扣提供海量技术面试资源&#xff0c;帮助你高效提升编程技能&#xff0c;轻松拿下世界 IT 名企 Dream Offer。https://leetcode.cn/problems/h-index/description/?envTyped…

云游长江大桥,3DCAT实时云渲染助力打造沉浸化数字文旅平台

南京长江大桥是中国第一座自主设计建造的双层公路铁路桥&#xff0c;也是世界上最早的双层公路铁路桥之一。它不仅是一座桥梁&#xff0c;更是一座历史文化的见证者和传承者。它见证了中国人民的智慧和奋斗&#xff0c;承载了中国社会的变迁和发展。 如何让这座不可移动的文物…

【Android】Android Framework系列---CarPower电源管理

Android Framework系列—CarPower电源管理 智能座舱通常包括中控系统、仪表系统、IVI系统 、后排娱乐、HUD、车联网等。这些系统需要由汽车电源进行供电。由于汽车自身的特殊供电环境&#xff08;相比手机方便的充电环境&#xff0c;汽车的蓄电池如果没有电是需要专业人士操作…

这个第一个输出为啥是2 不是4 啊?

#include <iostream> using namespace std;class Point{ public:int x;int y;Point(int x1, int y1) : x(x1), y(y1) //成员初始化列表{ }int getDistance() {return x * x y * y;} };void changePoint1(Point point) //使用对象作为函数参数 {point.x 1;point.y -…

上海高考英语科目命题趋势和备考建议,附1990-2023真题解析

英语和语文一样&#xff0c;都是侧重语言的阅读理解、交流和运用&#xff0c;但是英语作为外国语和语文又不太一样&#xff0c;相对来说考的更简单一些。不过要拿高分也不容易。 为了帮助高三的学子们提升英语备考的效率&#xff0c;争取多提分&#xff0c;六分职场特撰写这篇…

信息系统项目管理师教程 第四版【第5章-信息系统工程-思维导图】

信息系统项目管理师教程 第四版【第5章-信息系统工程-思维导图】 课本里章节里所有蓝色字体的思维导图

ZYNQ连载05-Vitis更新xsa硬件配置

ZYNQ连载05-Vitis更新xsa硬件配置 1. 简述 在开发过程中&#xff0c;Vivado中硬件配置在开发过程中有所变动&#xff0c;Vitis需要根据Vivado生成的xsa文件&#xff0c;更新相应的BSP配置 2. 操作步骤

基于SSM的个性化美食推荐系统设计与实现

末尾获取源码 开发语言&#xff1a;Java Java开发工具&#xff1a;JDK1.8 后端框架&#xff1a;SSM 前端&#xff1a;采用JSP技术开发 数据库&#xff1a;MySQL5.7和Navicat管理工具结合 服务器&#xff1a;Tomcat8.5 开发软件&#xff1a;IDEA / Eclipse 是否Maven项目&#x…

【斗罗二】王东升级三环,戴华斌挑衅,雨浩单手接鼎订下赌约

【侵权联系删除】【文/郑尔巴金】 深度爆料&#xff0c;《绝世唐门》第20集&#xff0c;一场瞩目的战斗即将爆发。王冬&#xff0c;这位一年级的强攻系班长&#xff0c;将与戴华斌进行一场激烈的较量。王冬拥有三大武魂&#xff0c;其中最为人们所熟知的是那光明女神蝶&#x…

美食论坛大全订阅交流系统 uniapp+vue 微信小程序设计与实现

美食大全订阅小程序在系统的智能手机上可以运行&#xff0c;主要实现了首页、个人中心、用户管理、菜系管理、口味管理、美食分类管理、美食信息管理、美食论坛、系统管理等主要功能模块的操作和管理。 后端&#xff1a;java(springbootssm)/python/php/nodejs/ 开发运行&…