并发编程

什么是并发编程

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

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

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

并发编程的根本原因:

  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方法删除。

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

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

相关文章

git 推送到github远程仓库细节处理(全网最良心)

我查看了很多网上的教程都不是很好 我们先在github创建一个仓库&#xff0c;且初始化 readme 我们到本地文件初始化仓库 添加远程仓库 这时候我们就 git add . , git commit ,再准备git push 的时候 显示没有指定远程的分支 我们按照提示操作 提示我们要先git pull 提示我…

激活函数作用以及 sigmoid和softmax

激活函数 激活函数在神经网络中起着非常重要的作用&#xff0c;它的主要功能是引入非线性性质&#xff0c;使得神经网络可以学习和表示更加复杂的模式和关系。下面是激活函数的几个主要作用&#xff1a; 引入非线性&#xff1a;激活函数通过引入非线性变换&#xff0c;打破了…

Kubernetes - Ingress HTTP 负载搭建部署解决方案(新版本v1.21+)

在看这一篇之前&#xff0c;如果不了解 Ingress 在 K8s 当中的职责&#xff0c;建议看之前的一篇针对旧版本 Ingress 的部署搭建&#xff0c;在开头会提到它的一些简介Kubernetes - Ingress HTTP 负载搭建部署解决方案_放羊的牧码的博客-CSDN博客 开始表演 1、kubeasz 一键安装…

M1安装OpenPLC Editor

下载OpenPLC Editor for macOS.zip文件后&#xff0c;使用tar -zvxf命令解压&#xff0c;然后将"OpenPLC Editor"拖入到"应用程序"文件夹 右键点击"OpenPLC Editor"&#xff0c;打开这个""文件&#xff0c;替换为以下内容 #!/bin/bash…

C++设计模式_15_Proxy 代理模式

Proxy 代理模式也是属于“接口隔离”模式&#xff0c;通过增加一层间接层来解决问题的模式。 文章目录 1. 动机( Motivation)2. 模式定义3. 结构( Structure )4. 代码演示Proxy 代理模式4.1 常规方法4.2 Proxy 代理模式 5. 要点总结6. 其他参考 1. 动机( Motivation) 在面向对…

How to install the console system of i-search rpa on Centos 7

How to install the console system of i-search rpa on Centos 7 1、 准备1.1 、查看磁盘分区状态1.2、上传文件1.2.1、添加上传目录1.2.2、上传安装包1.2.3、解压安装包1.2.4、查看安装包结构 1.3、安装依赖包1.3.1、基础依赖包1.3.2 相关依赖 1.4、关闭防火墙1.5、解除SeLin…

MyBaties存储和查询json格式的数据(实体存储查询版本)

最近在做的功能&#xff0c;由于别的数据库有值&#xff0c;需要这边的不同入口的进来查询&#xff0c;所以需要同步过来&#xff0c;如果再继续一个一个生成列对应处理感觉不方便&#xff0c;如果没有别的操作&#xff0c;只是存储和查询&#xff0c;那就可以用MySql支持的jso…

2022年上半年上午易错题(软件设计师考试)

1.以下关于冯诺依曼计算机的叙述中&#xff0c;不正确的是( )。 A.程序指令和数据都采用二进制表示 B.程序指令总是存储在主存中&#xff0c;而数据则存储在高速缓存中 C.程序的功能都由中央处理器(CPU)执行指令来实现 D.程序的执行过程由指令进行自动控制 程序指令和数据…

【Android】MQTT入门——服务器部署与客户端搭建

目录 MQTT 协议简介应用场景优点缺点 部署服务端下载安装包启动服务器 搭建客户端下载SDK添加依赖配置MQTT服务和权限建立连接订阅主题发布消息取消订阅断开连接 MQTT客户端工具最终效果实现传感器数据采集与监测功能思路 MQTT 协议 简介 MQTT&#xff08;Message Queuing Te…

Mac 安装使用NPM及常用命令

环境&#xff1a; Mac 工具&#xff1a; NPM 可通过官网查询一些模块相关 NPM Doc 通过官网文档了解更多的关于NPM的使用 安装 NPM是Node.js的包管理工具&#xff0c;可用于解决 Node.js在代码部署上的问题。 新版本的Node.js已经集成了NPM&#xff0c; 因此可通过下载 Nod…

OpenCV C++ 图像处理实战 ——《缺陷检测》

OpenCV C++ 图像处理实战 ——《缺陷检测》 一、结果演示二、缺陷检测算法2.1、多元模板图像2.2、训练差异模型三、图像配准3.1 功能源码3.1 功能效果四、多元模板图像4.1 功能源码五、缺陷检测5.1 功能源码六、源码测试图像下载总结一、结果演示

Python爬虫基础之Requests详解

目录 1. 简介2. 安装3. 发送请求4. 处理响应5. IP代理6. Cookie登录参考文献 原文地址&#xff1a;https://program-park.top/2023/10/27/reptile_4/ 本文章中所有内容仅供学习交流使用&#xff0c;不用于其他任何目的&#xff0c;严禁用于商业用途和非法用途&#xff0c;否则由…

【Unity3D】Unity与Android交互

1 Unity 发布 apk 1.1 安装 Android Build Support 在 Unity Hub 中打开添加模块窗口&#xff0c;操作如下。 选择 Android Build Support 安装&#xff0c;如下&#xff08;笔者这里已安装过&#xff09;。 创建一个 Unity 项目&#xff0c;依次点击【File→Build Settings→…

uniapp实现瀑布流

首先我们要先了解什么是瀑布流&#xff1a; 瀑布流&#xff08;Waterfall Flow&#xff09;是一种常见的网页布局方式&#xff0c;也被称为瀑布式布局或砌砖式布局。它通常用于展示图片、博客文章、商品等多个不同大小和高度的元素。 瀑布流布局的特点是每个元素按照从上到下…

制作自己的前端组件库并上传到npm上

最近实现了自己的一个前端组件库demo&#xff0c;目前只上传了几个小组件。话不多说&#xff0c;上图&#xff1a; 我分了三个项目&#xff1a;yt-ui组件库、使用文档、demo。线上地址如下&#xff1a; [yt-ui组件库](mhfwork/yt-ui - npm) [组件库使用文档](介绍 | mhfwork/y…

【Java 进阶篇】解决Java Web应用中请求参数中文乱码问题

在Java Web应用开发中&#xff0c;处理请求参数时经常会遇到中文乱码的问题。当浏览器向服务器发送包含中文字符的请求参数时&#xff0c;如果不正确处理&#xff0c;可能会导致乱码问题&#xff0c;使得参数无法正确解析和显示。本文将详细探讨Java Web应用中请求参数中文乱码…

安全狗安装

安装waf 关闭apache程序及httpd.exe进程; 运行cmd&#xff0c;cd进入apache/bin文件夹目录&#xff0c; 执行httpd.exe -k install -n apache2.4.39; 启动apache,启动phpstudy 安全狗安装服务名称填写apache2.4.39; 安装安全狗之后就会提示报错 IP黑白名单 可以设备黑白名单 …

本机spark 通idea连接Oracle的坑

1. 报错&#xff1a;Exception in thread "main" java.lang.NoSuchMethodError: scala.Product.$init$(Lscala/Product;)V 查询网上资料&#xff0c;是idea引入的scala运行环境版本与idea默认的scala版本不一样 也就是写的项目中的pom的spark版本与idea默认的版本不…

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

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

Ant Design Vue UI框架的基础使用,及通用后台管理模板的小demo【简单】

一、创建 VUE 项目 npm create vuelatest二、安装使用 ant-design-vue 安装脚手架工具 $ npm install -g vue/cli # OR $ yarn global add vue/cli使用组件 # 安装 $ npm i --save ant-design-vue4.x全局完整注册 import { createApp } from vue; import Antd from ant-de…