Java的第十三篇文章——JAVA多线程

目录

学习目标

1. 线程的基本概念

1.1 进程

1.2 线程

2. Java实现线程程序

2.1 java.lang.Thread类

2.2 线程的内存图

2.3 Thread类的方法

3. Java实现线程程序

3.1 java.lang.Runnable接口

3.2 实现接口的好处

4. 线程安全

4.1 售票例子

4.2 同步代码块

4.3 同步方法

5. 死锁

6. JDK5新特性Lock锁

7. 生产者与消费者例题

7.1 安全问题产生

7.2 线程方法sleep和wait的区别

7.3 生产者和消费者案例的性能问题

7.4 Lock接口深入

7.5 生产者与消费者改进薇Lock接口

7.6 Lock锁的实现原理

8. 单例设计模式 

8.1 单例模式

8.2 懒汉式的安全问题

8.2 关键字 volatile

9. 线程池ThreadPool

9.1 Executors 类

9.2 Callable 接口

10. ConcurrentHashMap(需进一步了解)

11. 线程的状态图——生命周期


学习目标

  • 线程概念

  • Java实现多线程程序一

  • Thread类的方法

  • Java实现多线程程序二

  • 线程安全问题

  • 同步synchronized使用

  • 锁对象的选择

  • 死锁案例

  • 生产者与消费者

  • JDK5特性JUC

  • 单例模式

  • 关键字volatile

  • 线程池

  • ConcurrentHashMap

1. 线程的基本概念

1.1 进程

任何的软件存储在磁盘中,运行软件的时候,OS(操作系统)使用IO技术,将磁盘中的软件的文件加载到内存,程序才能运行。

进程的概念:应用程序(typerpa、word、IDEA)运行的时候进入到内存,程序在内存中占用的内存空间(进程)。

1.2 线程

线程(Thread):在内存和CPU之间,建立一条连接通路,CPU可以到内存中取出数据进行计算,这个连接的通路,就是线程

一个内存资源:一个独立的进程,进程中可以开启多个线程 (多条通路) 。

并发:同一个时刻多个线程同时操作了同一个数据。

并行:同一个时刻多个线程同时执行不同的程序。

2. Java实现线程程序

今天之前的所有程序都有一个共性:main启动之后,一条线走到底 (单线程)

2.1 java.lang.Thread类

第一种方法:继承的方式实现多线程。

一切都是对象,线程也是对象,Thread类是线程对象的描述类

  • 实现线程程序的步骤:

    • 定义类继承Thread

    • 子类重写方法run

    • 创建子类对象

    • 调用子类对象的方法start()启动线程

//- 定义类继承Thread
//- 子类重写方法run
public class SubThread extends Thread {
    public void run(){
        for(int x = 0 ; x < 50 ;x++)
            System.out.println("run..."+x);
    }
}
public static void main(String[] args) {
    //创建线程程序
    SubThread subThread = new SubThread();
    //调用子类对象的方法start()启动线程
    //启动线程,JVM调用方法run
    subThread.start();
    for(int x = 0 ; x < 50 ;x++)
    	System.out.println("main..."+x);
}

(1)调用子类对象的方法start()启动线程。我们只需要调用start()方法,run()方法不是我们自己调用的。
(2)启动线程,JVM调用方法run。

2.2 线程的内存图

2.3 Thread类的方法

  • Thread类的方法 getName()返回线程的名字,返回值是String类型

public class ThreadName extends Thread {
    public void run (){
        System.out.println("线程名字:: "+ super.getName());
    }
}
   public static void main(String[] args) {
        ThreadName threadName = new ThreadName();
        //threadName.setName("旺财");
        threadName.start();

        ThreadName threadName1 = new ThreadName();
        //threadName1.setName("小强");
        threadName1.start();
    }
  • Thread类静态方法 : Thread currentThread()

    • 静态调用,作用是返回当前的线程对象

    • "当前" , 当今皇上——本地主机

//获取当前线程对象,拿到运行main方法的线程对象
Thread thread =  Thread.currentThread();
System.out.println("name::"+thread.getName());
  • Thread类的方法 join()

    • 解释:执行join()方法的线程,他不结束,其它线程运行不了

    public static void main(String[] args) throws InterruptedException {
        JoinThread t0 = new JoinThread();
        JoinThread t1 = new JoinThread();

        t0.start();
        t0.join();
        t1.start();
    }
  • Thread类的方法 static yield()

    • 线程让步,线程把执行权让出

    public void run() {
        for(int x = 0 ; x < 50 ;x++){
            Thread.yield();
            System.out.println(Thread.currentThread().getName()+"x.."+x);
        }
    }

3. Java实现线程程序

3.1 java.lang.Runnable接口

第二种实现多线程的方法:用接口来实现

  • 实现线程程序的步骤 :

    • 定义类实现接口

    • 重写接口的抽象方法run()

    • 创建Thread类对象

      • Thread类构造方法中,传递Runnable接口的实现类对象

    • 调用Thread对象方法start()启动线程

//- 定义类实现接口
// - 重写接口的抽象方法run()
public class SubRunnable implements Runnable{
    @Override
    public void run() {
        for(int x = 0 ; x < 50 ;x++){
            System.out.println(Thread.currentThread().getName()+"x.."+x);
        }
    }
}
    public static void main(String[] args) {
        //创建接口实现类对象
        Runnable r = new SubRunnable();
        //创建Thread对象,构造方法传递接口实现类
        Thread t0 = new Thread(r);
        t0.start();

        for(int x = 0 ; x < 50 ;x++){
            System.out.println(Thread.currentThread().getName()+"x.."+x);
        }
    }

3.2 实现接口的好处

接口实现好处是设计上的分离效果:线程要执行的任务和线程对象本身是分离的

继承Thread重写方法run():Thread是线程对象,run()是线程要执行的任务。

实现Runnable接口:方法run在实现类,和线程无关,创建Thread类传递接口的实现类对象,线程的任务和Thread没有联系,,解开耦合性。

4. 线程安全

出现线程安全的问题需要一个前提:多个线程同时操作同一个资源。

线程执行调用方法run,同一个资源是堆内存的。

4.1 售票例子

火车票的票源是固定的,购买渠道在火车站买,n多个窗口。

public class Ticket implements Runnable {
    //定义票源
    private int tickets = 100;

    @Override
    public void run() {
        while (true) {
            if (tickets > 0) {
                try {
                    Thread.sleep(10);
                } catch (Exception e) {}
                System.out.println(Thread.currentThread().getName() + " 出售第" + tickets + "张");
                tickets--;
            }else{
                break;
            }
        }
    }
}
public class ThreadTest {
    public static void main(String[] args) {
        //这个属于一个种子,被三个线程共享
        Ticket ticket = new Ticket();
        //创建3个窗口,3个线程
        Thread t0 = new Thread(ticket);
        Thread t1 = new Thread(ticket);
        Thread t2 = new Thread(ticket);

        t0.start();
        t1.start();
        t2.start();

    }
}

我们可以发现发生了线程安全问题:多个线程抢同一张票源。

 解决线程的安全问题:当一个线程没有完成全部操作的时候,其它线程不能操作。

4.2 同步代码块

同步代码块可以解决线程安全问题:格式 synchronized关键字。

synchronized(任意对象){
    //线程操作的共享资源
}

任意对象:在同步中这个对象称为对象锁,简称锁,官方的稳定称为对象监视器。

同步代码块,如何保证线程的安全性。

  • 同步代码块的执行原理:关键点就是对象锁

    • 线程执行到同步,判断锁是否存在

      • 如果锁存在,获取到锁,进入到同步中执行

      • 执行完毕,线程出去同步代码块,将锁对象归还

    • 线程执行到同步,判断锁所否存在

      • 如果锁不存在,线程只能在同步代码块这里等待,锁的到来

使用同步:线程要先判断锁,然后获取锁,出去同步要释放锁,增加了许多步骤,因此线程安全运行速度慢,牺牲性能,不能牺牲数据安全。

4.3 同步方法

当一个方法中,所有代码都是线程操作的共享内容,可以在方法的定义上添加同步的关键字 synchronized ,同步的方法,或者称为同步的函数。

  • 同步方法中有对象锁吗?有且是this对象

  • 静态同步方法中有对象锁吗?锁对象是本类.class属性。 这个属性表示这个类的class文件的对象。

    @Override
    public void run() {
        while (true)
          sale();
    }

private static synchronized void sale(){
    //  synchronized (Ticket.class) {
    if (tickets > 0) {
    try {
        Thread.sleep(20);//线程休眠,暂停执行
        } catch (Exception ex) {
    }
    System.out.println(Thread.currentThread().getName() + " 出售第" + tickets + "张");
    tickets--;
    }
//  }
}

5. 死锁

死锁程序:多个线程同时争夺同一个锁资源,出现程序的假死现象。

面试点:考察开发人员是否充分理解同步代码的执行原理

同步代码块:线程判断锁,获取锁,释放锁,不出代码,锁不释放

  • 死锁代码

/**
 * 实现死锁程序
 */
public class ThreadDeadLock implements Runnable{

    private boolean flag ;

    public ThreadDeadLock(boolean flag){
        this.flag = flag;
    }

    @Override
    public void run() {
        while (true){
            //同步代码块的嵌套
            if (flag){
                //先进入A锁同步
                synchronized (LockA.lockA){
                    System.out.println("线程获取A锁");
                    //在进入另一个同步B锁
                    synchronized (LockB.lockB){
                        System.out.println("线程获取B锁");
                    }
                }
            }else {
                //先进入B锁同步
                synchronized (LockB.lockB){
                    System.out.println("线程获取B锁");
                    //再进入另一个同步锁A锁
                    synchronized (LockA.lockA){
                        System.out.println("线程获取A锁");
                    }
                }
            }
        }
    }
}
public class LockA {
    public static LockA lockA = new LockA();
}
public class LockB {
    public static LockB lockB = new LockB();
}
    public static void main(String[] args) {
        ThreadDeadLock threadDeadLock = new ThreadDeadLock(true);
        ThreadDeadLock threadDeadLock2 = new ThreadDeadLock(false);

        new Thread(threadDeadLock).start();
        new Thread(threadDeadLock2).start();
    }

6. JDK5新特性Lock锁

JDK5新的特性:java.util.concurrent.locks包。定义了接口Lock。

Lock接口替代了synchronized,可以更加灵活

  • Lock接口的方法

    • void lock() 获取锁

    • void unlock()释放锁

  • Lock接口的实现类ReentrantLock

/**
 *  优化为juc包的接口Lock
 */
public class Ticket implements Runnable {

    //定义票源
    private  int tickets = 100;
    //获取Lock接口的实现类对象
    private Lock lock = new ReentrantLock();

    @Override
    public void run() {
        while (true)
          sale();
    }

    private void sale(){
        //获取锁
        lock.lock();
        if (tickets > 0) {
            try {
                Thread.sleep(20);//线程休眠,暂停执行
            } catch (Exception ex) {
            }
            System.out.println(Thread.currentThread().getName() + " 出售第" + tickets + "张");
            tickets--;
        }
        //释放锁
        lock.unlock();
    }
}

7. 生产者与消费者例题

创建2个线程,一个线程表示生产者,另一个线程表示消费者

/**
 * 定义资源对象
 *   成员 : 产生商品的计数器
 *          标志位
 */
public class Resource {
    int count ;
    boolean flag ;
}
/**
 * 生产者线程
 *   资源对象中的变量++
 */
public class Produce implements Runnable{

    private Resource r ;

    public Produce(Resource r) {
        this.r = r;
    }

    @Override
    public void run() {
        while (true){
            synchronized (r) {
                //判断标志位,是否允许生产
                //flag是true,生产完成,等待消费
                if (r.flag )
                    //无限等待
                   try{ r.wait();
                   }catch (Exception ex){}
                r.count++;
                System.out.println("生产第" + r.count + "个");
                //修改标志位,已经生产了,需要消费
                r.flag = true;
                //唤醒消费者线程
                r.notify();
            }
        }
    }
}
/**
 * 消费者线程
 *   资源对象中的变量输出打印
 */
public class Customer implements Runnable{
    private Resource r ;

    public Customer(Resource r) {
        this.r = r;
    }

    @Override
    public void run() {
        while (true){
            synchronized (r) {
                //是否要消费,判断标志位 ,允许消费才能执行
                if (!r.flag )
                    //消费完成,不能再次消费,等待生产
                    try{r.wait();}catch (Exception ex){}
                System.out.println("消费第" + r.count);
                    //消费完成后,修改标志位,变成已经消费
                r.flag = false;
                //唤醒生产线程
                r.notify();
            }
        }
    }
}
public static void main(String[] args) {
    Resource r = new Resource();
    //接口实现类,生产的,消费的
    Produce produce = new Produce(r);
    Customer customer = new Customer(r);
    //创建线程
    new Thread(produce).start();
    new Thread(customer).start();
}
  • 线程通信的方法 wait() notify()

    • 方法的调用必须写在同步中

    • 调用者必须是作为锁的对象

    • wait(),notify()为什么要定义在Object类

      • 同步中的锁,是任意对象,任何类都继承Object

结果如下:

改为使用同步方法实现该功能

public class Resource {
    int count;
    boolean flag = false;

    public synchronized void getCustomer() {
        while (true) {
            if (!this.flag) {
                try {
                    this.wait();
                } catch (InterruptedException e) {
                    e.printStackTrace();
                }
            } else {
                System.out.println("消费者消费了第:" + this.count);
                this.flag = false;
            }
            this.notify();
        }
    }

    public synchronized void getProduce() {
        while (true) {
            if (this.flag) {
                try {
                    this.wait();
                } catch (InterruptedException e) {
                    e.printStackTrace();
                }
            } else {
                this.count++;
                System.out.println("生产者生产了第:" + this.count + "个");
                flag = true;
            }
            this.notify();
        }
    }
}
public class Customer implements Runnable {
    private Resource r;

    public Customer(Resource r) {
        this.r = r;
    }


    @Override
    public void run() {
        r.getCustomer();
    }
}
public class Produce implements Runnable {
    private Resource r;

    public Produce(Resource r) {
        this.r = r;
    }

    @Override
    public void run() {
        r.getProduce();
    }
}

7.1 安全问题产生

  • 线程本身就是一个新创建的方法栈内存 (CPU进来读取数据)

  • 线程的notify(),唤醒第一个等待的线程

    • 解决办法:全部唤醒用 notifyAll() 方法

  • 被唤醒线程,已经进行过if判断,一旦醒来继续执行

    • 线程被唤醒后,不能立刻就执行,再次判断标志位,利用循环

    • while(标志位) 标志位是true,永远也出不去

public class Resource {
    int count;
    boolean flag = false;

    public synchronized void getCustomer(){
        while(!flag)
            try{
                this.wait();
            }catch (Exception ex){
                ex.printStackTrace();
            }
        System.out.println(Thread.currentThread().getName()+"消费者消费了第:" + this.count);
        this.flag = false;
        this.notifyAll();
    }

    public synchronized void getProduce(){
        while(flag)
            try{
                this.wait();
            }catch (Exception ex){
                ex.printStackTrace();
            }
        this.count++;
        System.out.println(Thread.currentThread().getName()+"生产者生产了第:" + this.count + "个");
        this.flag = true;
        this.notifyAll();
    }
public class Produce implements Runnable {
    private Resource r;

    public Produce(Resource r) {
        this.r = r;
    }

    @Override
    public void run() {
        while (true) {
            r.getProduce();
        }
    }
}
public class Customer implements Runnable {
    private Resource r;

    public Customer(Resource r) {
        this.r = r;
    }


    @Override
    public void run() {
        while(true){
            r.getCustomer();
        }
    }
}
public class Test {
    public static void main(String[] args) {
        Resource resource = new Resource();
        Customer customer = new Customer(resource);
        Produce produce = new Produce(resource);

        new Thread(customer).start();
        new Thread(customer).start();
        new Thread(customer).start();
        new Thread(produce).start();
        new Thread(produce).start();
        new Thread(produce).start();
    }
}

结果如下: 

7.2 线程方法sleep和wait的区别

  • sleep在休眠的过程中,同步锁不会丢失,不释放

  • wait()等待的时候,发布监视器的所属权,释放锁。唤醒后要重新获取锁,才能执行

7.3 生产者和消费者案例的性能问题

wait()方法和notify()方法,本地方法调用OS的功能,和操作系统交互,JVM找OS,把线程停止。频繁等待与唤醒,导致JVM和OS交互的次数过多。

notifyAll()唤醒全部的线程,也浪费线程资源,为了一个线程,不得以唤醒的了全部的线程。

7.4 Lock接口深入

Lock接口替换了同步synchronized,提供了更加灵活,性能更好的锁定操作

  • Lock接口中方法:newCondition() 方法的返回值是接口:Condition

  • 用集合的方式去管理线程

7.5 生产者与消费者改进薇Lock接口

  • Condition接口 (线程的阻塞队列)

    • 进入队列的线程,释放锁

    • 出去队列的线程,再次的获取锁

    • 接口的方法:await() 线程释放锁,进入队列

    • 接口的方法:signal() 线程出去队列,再次获取锁,此方法是唤醒一个线程

线程的阻塞队列,依赖Lock接口创建

/**
 *  改进为高性能的Lock接口和线程的阻塞队列
 */
public class Resource {
   private int count ;
   private boolean flag ;
   private Lock lock = new ReentrantLock();//Lock接口实现类对象

    //Lock接口锁,创建出2个线程的阻塞队列
    private Condition prod = lock.newCondition();//生产者线程阻塞队列
    private Condition cust = lock.newCondition();//消费者线程阻塞队列

   //消费者调用
   public  void getCount() {
       lock.lock();//获取锁
         //flag是false,消费完成,等待生产
         while (!flag)
            //无限等待,消费线程等待,执行到这里的线程,释放锁,进入到消费者的阻塞队列
             try{cust.await();}catch (Exception ex){}

         System.out.println("消费第"+count);
            //修改标志位,为消费完成
         flag = false;
         //唤醒生产线程队列中的一个
         prod.signal();
         lock.unlock();//释放锁
   }
   //生产者调用
   public  void setCount() {
       lock.lock();//获取锁
         //flag是true,生产完成,等待消费
       while (flag)
            //无限等待,释放锁,进入到生产线程队列
            try{prod.await();}catch (Exception ex){}
         count++;
         System.out.println("生产第"+count+"个");
         //修改标志位,为生产完成
         flag = true;
         //唤醒消费者线程阻塞队列中年的一个
         cust.signal();
       lock.unlock();//释放锁
   }
}

7.6 Lock锁的实现原理

使用技术不开源,技术的名称叫做轻量级锁

使用的是CAS锁 (Compare And Swap) 自旋锁

JDK限制:当竞争的线程大于等于10,或者单个线程自旋超过10次的时候

JDK强制CAS锁取消,升级为重量级锁 (OS锁定CPU和内存的通信总线)

8. 单例设计模式 

设计模式:不是技术,是以前的人开发人员,为了解决某些问题实现的写代码的经验。

所有的设计模式核心的技术,就是面向对象。

Java的设计模式有23种,分为3个类别,创建型、行为型和功能型三类

8.1 单例模式

要求:保证一个类的对象在内存中的唯一性

第一种为饿汉式

  • 私有修饰构造方法

  • 自己创建自己的对象

  • 方法get,返回本类对象

/**
 * - 私有修饰构造方法
 * - 自己创建自己的对象
 * - 方法get,返回本类对象
 */
public class Single {
    private Single(){}
	//饿汉式
    private static Single s = new Single(); // 自己创建自己的对象

//    方法get,返回本类对象
    public static Single getInstance(){
        return s;
    }
}
 public static void main(String[] args) {
        //静态方法,获取Single类的对象
        Single instance = Single.getInstance();
        System.out.println("instance = " + instance);
 }

第二种为懒汉式

  • 私有修饰构造方法

  • 创建本类的成员变量,不new对象

  • 方法get,返回本类对象

/**
 * - 私有修饰构造方法
 * - 创建本类的成员变量, 不new对象
 * - 方法get,返回本类对象
 */
public class Single {
    private Single(){}
	//懒汉,对象的延迟加载
    private static Single s = null;

    public static Single getInstance(){
        //判断变量s,是null就创建
        if (s == null) {
            s = new Single();
        }
        return s;
    }
}

8.2 懒汉式的安全问题

注:一个线程判断完变量 s=null,还没有执行new对象,被另一个线程抢到CPU资源,同时有2个线程都进行判断变量,对象创建多次。

    public static Single getInstance(){
        synchronized (Single.class) {
            //判断变量s,是null就创建
            if (s == null) {
                s = new Single();
            }
        }
        return s;
    }

性能问题:第一个线程获取锁,创建对象,返回对象。第二个线程调用方法的时候,变量s已经有对象了,根本就不需要在进同步,不要在判断空,直接return才是最高效的。双重的if判断,提高效率 Double Check Lock。

private static volatile Single s = null; 
public static Single getInstance(){
        //再次判断变量,提高效率
        if(s == null) {
            synchronized (Single.class) {
                //判断变量s,是null就创建
                if (s == null) {
                    s = new Single();
                }
            }
        }
        return s;
}

8.2 关键字 volatile

成员变量修饰符,不能修饰其它内容。

  • 关键字作用 :

    • 保证被修饰的变量,在线程中的可见性

    • 防止指令重排序

      • 单例的模式,不使用该关键字,可能线程会拿到一个尚未初始化完成的对象(半初始化)

如果我将以下代码中的volatile关键字去掉,程序就不会结束,陷入死循环。因为线程与线程之前的变量是不可见的,一个线程修改了变量,另一个线程是看不到。所以如以下例子,如果没有volatile关键字,main线程即使修改了flag变量,MyRunnable线程也是看不到的修改信息的。

public class MyRunnable implements Runnable {
    private volatile boolean flag = true;

    @Override
    public void run() {
        m();
    }

    private void m(){
        System.out.println("开始执行");
        while (flag){

        }
        System.out.println("结束执行");
    }


    public void setFlag(boolean flag) {
        this.flag = flag;
    }
}
public class MyRunnable implements Runnable {
    private volatile boolean flag = true;

    @Override
    public void run() {
        m();
    }

    private void m(){
        System.out.println("开始执行");
        while (flag){

        }
        System.out.println("结束执行");
    }


    public void setFlag(boolean flag) {
        this.flag = flag;
    }
}

9. 线程池ThreadPool

线程的缓冲池,目的就是提高效率。new Thread().start(),线程是内存中的一个独立的方法栈区,JVM没有能力开辟内存空间,和OS交互,会影响程序的速度。

9.1 Executors 类

  • 静态方法static newFixedThreadPool(int 线程的个数)

    • 方法的返回值ExecutorService接口的实现类,管理池子里面的线程

  • ExecutorService接口的方法

    • submit (Runnable r)提交线程执行的任务(此方法会去线程池拿线程,然后调用该线程的start方法)

9.2 Callable 接口

实现多线程的程序:接口特点是有返回值,可以抛出异常 (Runnable没有)

抽象的方法只有一个 call()

启动线程,线程调用重写方法call()

  • ExecutorService接口的方法

    • submit (Callable c)提交线程执行的任务

    • Future submit()方法提交线程任务后,方法有个返回值 Future接口类型

    • Future接口,获取到线程执行后的返回值结果

public class MyCall implements Callable<String> {
    public String call() throws Exception{
        return "返回字符串";
    }
}
    public static void main(String[] args) throws ExecutionException, InterruptedException {
        //创建线程池,线程的个数是2个
       ExecutorService  es =  Executors.newFixedThreadPool(2);
       //线程池管理对象service,调用方法啊submit提交线程的任务
        //提交线程任务,使用Callable接口实现类
        Future<String> future = es.submit(new MyCall());//返回接口类型 Future
        //接口的方法get,获取线程的返回值
        String str = future.get();
        System.out.println("str = " + str);

//        es.submit(my);
//        es.submit(my);
//        es.submit(my);
       // es.shutdown();//销毁线程池
    }

10. ConcurrentHashMap(需进一步了解)

ConcurrentHashMap类本质上Map集合,键值对的集合。使用方式和HashMap没有区别。

凡是对于此Map集合的操作,不去修改里面的元素,不会锁定。

11. 线程的状态图——生命周期

在某一个时刻,线程只能处于其中的一种状态。这种线程的状态反应的是JVM中的线程状态和OS无关。

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

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

相关文章

为什么视频画质会变差,如何提升视频画质清晰度。

在数字时代&#xff0c;视频已经成为我们生活中不可或缺的一部分。然而&#xff0c;随着视频的传输和处理过程中的多次压缩&#xff0c;画质损失逐渐凸显&#xff0c;影响了我们对影像的真实感受。为了让视频画质更加清晰、逼真&#xff0c;我们需要采取一些措施来保护和修复视…

物联网网关模块可以带几台plc设备吗?可以接几个modbus设备?

随着物联网技术的快速发展&#xff0c;物联网网关模块已经成为了实现物联网应用的重要工具。很多客户在选择物联网网关模块时想了解物联网网关模块的设备接入能力&#xff0c;一个物联网网关模块可以带几台PLC设备&#xff1f;可以接几个Modbus设备&#xff1f; 物联网网关模块…

基于Jquery EasyUI JSZip FileSaver的简单使用

一、前言 在前端的项目开发中 &#xff0c;下载文件压缩包是很重要的一个环节&#xff0c;那么怎么下载多个文件并压缩成ZIP下载呢&#xff1f; 二、使用步骤 1、引用库 <script type"text/javascript" src"~/Scripts/comm/jszip.min.js" ></…

【设计模式——学习笔记】23种设计模式——适配器模式Adapter(原理讲解+应用场景介绍+案例介绍+Java代码实现)

介绍 生活中的案例 不同国家的插座不同&#xff0c;出国旅游充电器不能直接使用&#xff0c;可以通过使用多功能转换插头来辅助使用 基础介绍 适配器模式将某个类的接口转换成客户端期望的另一个接口表示&#xff0c;主的目的是兼容性&#xff0c;让原本因接口不匹配不能一起…

JVM理论(六)执行引擎--垃圾回收

概述 垃圾: 指的是在运行程序中没有任何指针指向的对象垃圾回收目的: 为了及时清理空间使得程序可以正常运行垃圾回收机制: JVM采取的是自动内存管理,即JVM负责对象的创建以及回收,将程序员从繁重的内存管理释放出来,更加专注业务的开发垃圾回收区域: 频繁收集Young区(新生代)…

【面试】 redis击穿现象?如何防止击穿?

文章目录 背景击穿案例解决方案:通过synchronized双重检查机制&#xff1a;某个key只让一个线程查询&#xff0c;阻塞其它线程设置value永不过期&#xff08;设置热点数据永不过期&#xff09;使用互斥锁(mutex key) 背景 大家都知道,计算机的瓶颈之一就是IO,为了解决内存与磁…

【深度学习】yolov 图片训练的时候的遇到的warning: corrupt JPEG restored and saved

报错原因 是图片在dataset.py 走验证时报的错误。 if im.format.lower() in (jpg, jpeg):with open(im_file, rb) as f:f.seek(-2, 2)if f.read() ! b\xff\xd9: # corrupt JPEGImageOps.exif_transpose(Image.open(im_file)).save(im_file, JPEG, subsampling0, quality100)m…

数据结构——顺序表

即使你内心没有一尊明月&#xff0c;也要给自己留下一方皎洁 文章目录 什么是顺序表 顺序表的实现 顺序表内部基础设置 结构体数据类型重定义 顺序表结构定义 顺序表空间初始化及扩容设置 顺序表空间的初始化及销毁 顺序表的扩容 顺序表基本功能 尾插尾删 头插头删…

css设置八等分圆

现需要上图样式的布局&#xff0c;我通过两张向右方的图片&#xff0c;通过定位和旋转完成了布局。 问题&#xff1a; 由于是通过旋转获取到的样式&#xff0c;实际的盒子是一个长方形&#xff0c;当鼠标移入对应的箭头时选中的可能是其他盒子&#xff0c;如第一张设计稿可以看…

10.函数

10.1为什么需要函数 ●函数: function&#xff0c;是被设计为 执行特定任务的代码块 ●作用&#xff1a; 精简代码方便复用&#xff08;实现代码复用&#xff0c;提高开发效率&#xff09; 比如我们前面使用的alert()、prompt() 和console.log()都是一些js函数&#xff0c;只不…

编译/反编译

1.Android APK 1.软件 1.apktool 1.作用&#xff1a;反编译apk或重新打包apk 2.dex2jar 1.作用&#xff1a;将Android的可执行文件.dex转换为.jar 3.jd-gui 1.作用&#xff1a;方便阅读jar文件的代码工具 2.步骤 1.通过apktool将apk软件反编译2.使用dex2jar将classes.dex文件转…

(十五) InfluxDB服务进程参数(influxd命令的用法)

以下内容来自 尚硅谷&#xff0c;写这一系列的文章&#xff0c;主要是为了方便后续自己的查看&#xff0c;不用带着个PDF找来找去的&#xff0c;太麻烦&#xff01; 第 15 章 InfluxDB服务进程参数&#xff08;influxd命令的用法&#xff09; 15.1 influxd命令罗列 1、我们的…

Django 图书管理系统

一、功能及页面设计 二、页面展示 (1)首页 (2)注册 (3)登录 (4)普通用户登录 4.1查看图书页面 4.2查看图书详情页 4.3修改密码 (5)管理员登录 5.1添加图书 5.2添加图片 三、代码展示 因为代码太多不好一个个展示 所以需要源码的小伙伴可以找我要代码 感谢三连支持&#xff0…

【C++】多态原理剖析,Visual Studio开发人员工具使用查看类结构cl /d1 reportSingleClassLayout

author&#xff1a;&Carlton tag&#xff1a;C topic&#xff1a;【C】多态原理剖析&#xff0c;Visual Studio开发人员工具使用查看类结构cl /d1 reportSingleClassLayout website:黑马程序员C tool&#xff1a;Visual Studio 2019 date&#xff1a;2023年7月24日 目…

最快桌面UI:Siticone Desktop UI 2.1.1 cRACK

富图尔主义控制 80 多个 .NET UI 组件和控件 现代未来 UI/UX 组件 为 Visual Studio 开发做好准备 无限的免费产品支持案例 超轻量和快速性能 广泛可定制和主题化 低资源消耗和占地面积 免版税开发和部署 NET 的最佳 UI 和 UX 库 从最好的图书馆探索无缝流畅的体验 使…

二、SQL-5.DQL-9.执行顺序

一、案例&#xff1a; 查询年龄大于15的员工的姓名、年龄&#xff0c;并根据年龄进行升序排序 select name, age from emp where age > 15 order by age asc; 先执行①from&#xff08;定义emp的别名为e&#xff09;&#xff0c;再执行②where&#xff08;调用别名e&…

SpringBoot集成Druid实现数据库连接池

一、引入依赖 完整的pom文件如下所示: <?xml version"1.0" encoding"UTF-8"?> <project xmlns"http://maven.apache.org/POM/4.0.0" xmlns:xsi"http://www.w3.org/2001/XMLSchema-instance"xsi:schemaLocation"http…

如何从任何地方远程解决电脑问题?

​如何远程解决电脑问题&#xff1f; “嗨&#xff01;我有一台Windows 10家用电脑。我外出旅行&#xff0c;但我的家人告诉我我的电脑有一段时间无法正常工作。我该如何远程检查电脑并解决相应的问题&#xff1f;提前谢谢&#xff01;” 您是否正在寻找远程解决电…

JAVA解析EXCEL(JExcelAPI,POI,EasyExcel)

前言 文章目录 前言JExcelAPIDemo POIHSSFWorkBookXSSFWorkBookDemo SXSSFWorkBookDemo XSSFReaderDemo EasyExcelDemo demo代码&#xff1a;https://github.com/RwTo/excel-demo JAVA解析Excel 一般有三种方式 JExcelAPI POI EasyExcel JExcelAPI 官网&#xff1a;https://je…

【问题记录】Ubuntu 22.04 环境下,程序报:段错误(核心已转储)怎么使用 core 文件和GDB调试器 解决?

目录 环境 问题情况 解决思路 原因分析 解决方法 番外知识 环境 VMware Workstation 16 Pro &#xff08;版本&#xff1a;16.1.2 build-17966106&#xff09;ubuntu-22.04.2-desktop-amd64 问题情况 本人在运行百万并发的服务端程序时&#xff0c;程序运行报&#xff1a…