线程的概念及基本应用

news2024/12/25 13:54:20

目录

 

线程的概念

Java中多线程应用

 继承Thread类

实现Runnable接口

实现Callable接口

 

线程的生命周期

线程的基本操作及原理

Thread.join的使用及原理

Thread.sleep的作用

问题

Thread.sleep的工作流程

wait和notify的使用

wait

notify

例子

生产者消费者模式

Thread.interrupted和Thread.interrupt

为什么Thread.stop不推荐使用?

通过共享变量来终止线程

interrupt 方法

Interrupted方法

总结


 

线程的概念

是操作系统能够进行运算调度的最小单位。它被包含在进程之中,是进程中的实际运作单位。

一个进程可以有很多线程,每条线程并行执行不同的任务。

为什么会有线程?

  • 在多核CPU中,利用多线程可以实现真正意义上的并行执行

  • 在一个应用进程中,会存在多个同时执行的任务,如果其中一个任务 被阻塞,将会引起不依赖该任务的任务也被阻塞。通过对不同任务创 建不同的线程去处理,可以提升程序处理的实时性

  • 线程可以认为是轻量级的进程,所以线程的创建、销毁比进程更快

为什么要用多线程?

  • 异步执行(避免阻塞)

  • 利用多CPU资源实现真正意义行的并行执行

Java中多线程应用

 继承Thread类

既然线程启动时会去调用 run 方法,那么我们只要重写 Thread 类的 run 方法也是可以定义出我们的线程类的。

public class ThreadDemo extends Thread {
    @Override
    public void run() {
        System.out.println("当前线程:" + Thread.currentThread().getName());
    }

    public static void main(String[] args) {
        ThreadDemo thread = new ThreadDemo();
        thread.start();
    }
}

执行结果: 当前线程:Thread-0

实现Runnable接口

public class RunnableDemo implements Runnable {
    public void run() {
        System.out.println("当前线程:" + Thread.currentThread().getName());
    }

    public static void main(String[] args) {
        RunnableDemo runnableDemo = new RunnableDemo();
        Thread thread = new Thread(runnableDemo);
        thread.start();
    }
}

执行结果: 当前线程:Thread-0

实现Callable接口

import java.util.concurrent.*;

public class CallableDemo implements Callable<String> {
    public String call() throws Exception {
        System.out.println("当前线程:" + Thread.currentThread().getName());
        Thread.sleep(10000);    // 等待sleep执行完后才会返回“hello ly”
        return "hello ly";
    }

    public static void main(String[] args) throws ExecutionException, InterruptedException {
        ExecutorService executorService = Executors.newFixedThreadPool(1);
        Future<String> future = executorService.submit(new CallableDemo());
        // future.get() 是一个阻塞方法
        System.out.println(Thread.currentThread().getName() + " - " + future.get());
    }
}

 

执行结果: 当前线程:pool-1-thread-1
main - hello ly

线程的生命周期

Java线程从创建到销毁,可能会经历一下6个状态:

  • NEW:初始状态,线程被构建,但是还没有调用start方法

  • RUNNABLED:运行状态,JAVA线程把操作系统中的就绪和运行两种状态统一称为”运行中”

  • BLOCKED:阻塞状态,表示线程进入等待状态,也就是线程因为某种原因放弃了CPU使 用权,阻塞也分为几种情况

  • WAITING: 等待状态

  • TIME_WAITING:超时等待状态,超时以后自动返回

  • TERMINATED:终止状态,表示当前线程执行完毕

2382a4c8895744a792db1f6f2c3d8870.png

 

import java.util.concurrent.TimeUnit;

public class ThreadStatusDemo {

    public static void main(String[] args) {
        // TIME_WAITING
        new Thread(()->{
            while (true) {
                try {
                    TimeUnit.SECONDS.sleep(100);
                } catch (InterruptedException e) {
                    e.printStackTrace();
                }
            }
        }, "Time_Wating_Demo").start();
        // WAITING
        new Thread(()->{
            while (true) {
                synchronized (ThreadStatusDemo.class) {
                    try {
                        ThreadStatusDemo.class.wait();
                    } catch (InterruptedException e) {
                        e.printStackTrace();
                    }
                }
            }
        }, "Wating").start();
        new Thread(new BlockedDemo(), "Blocked-Demo-01").start();
        new Thread(new BlockedDemo(), "Blocked-Demo-02").start();
    }

    static class BlockedDemo extends Thread {
        @Override
        public void run() {
            synchronized (BlockedDemo.class) {
                try {
                    TimeUnit.SECONDS.sleep(100);
                } catch (InterruptedException e) {
                    e.printStackTrace();
                }
            }
        }
    }
}

运行ThreadStatusDemo.main后,如果在Terminal下执行jps命令无显示,如下图所示:

a6db347eeb574eab9ed9db6d29eb1e9a.png

产生原因:是因为 jps 命令 没有权限 读取 hsperfdata_用户名的文件夹, 这个文件夹运行java 程序时产生的。

解决办法:

  1. 找到 hsperfdata_用户名的文件夹添加读写权限,例如本机路径C:\Users\admin\AppData\Local\Temp\hsperfdata_admin;

  2. 右击-->属性-->安全-->高级;

  3. 选中当前登录用户,点击【更改权限】;

  4. 选中当前登录用户,点击【编辑】;

  5. 勾选【完全控制】,点击【确定】。

线程的基本操作及原理

Thread.join的使用及原理

public class ThreadJoinDemo {
    private static int x = 0;
    private static int i = 0;

    public static void main(String[] args) throws Exception {
        Thread t1 = new Thread(()->{
            i = 1;
            x = 2;
        });
        Thread t2 = new Thread(()->{
            i = x + 2;
        });
        t1.start();
        t2.start();
        Thread.sleep(1000);
        System.out.println("result : " + i);
    }
}

执行结果可能是:4         

5e36b23e19ca4b0aa9b6b8b34bd4b505.png

多执行几次结果有可能是:1

86247832e6d245f3a1d05659ce783896.png

由于线程的执行顺序是不确定的,如何保证线程的执行顺序呢?

我们在t1.start();t2.start();之间加入t1.join();就能保证了。

......
        t1.start();
        t1.join();    // t1线程的执行结果对于t2可见(t1线程一定会比t2线程优先执行)
        t2.start();
......

 

Thread.sleep的作用

使线程暂停执行一段时间,直到等待的时间结束才恢复执行或在这段时间内被中断。

 

import java.text.SimpleDateFormat;

public class ThreadSleepDemo extends Thread {
    public static void main(String[] args) {
        new ThreadSleepDemo().start();
    }

    @Override
    public void run() {
        SimpleDateFormat sdf = new SimpleDateFormat("yyyy-MM-dd HH:mm:ss");
        long dt1 = System.currentTimeMillis();
        System.out.println("begin: " + dt1 + " | " + sdf.format(dt1));
        try {
            Thread.sleep(3000);
            long dt2 = System.currentTimeMillis();
            System.out.println("end: " + dt2 + " | " + sdf.format(dt2));
            long dt = dt2 - dt1;
            System.out.println("dt2 - dt1 = " + dt);
        } catch (InterruptedException e) {
            e.printStackTrace();
        }
    }
}

执行结果:

begin: 1650361034229 | 2022-04-19 17:37:14
end: 1650361037243 | 2022-04-19 17:37:17
dt2 - dt1 = 3014

问题

  • 假设现在是2022-04-19 12:00:00.000,如果调用Thread.Sleep(1000),那么在2022-04-19 12:00:01.000的时候,这个线程会不会被唤醒?

  • Thread.Sleep(0)的意义

    Thread.sleep(0)并非是真的让线程挂起0毫秒,意义在于调用Thread.sleep(0)的当前线程确实被冻结了一下,让其他线程有机会优先执行,Thread.sleep(0)是使你的线程暂时放弃cpu,也是释放一些未使用的时间片给其他线程或者进程使用,就相当于一个让位动作

Thread.sleep的工作流程

  • 挂起线程并修改其运行状态

  • 用sleep()提供的参数来设置一个定时器。

  • 当时间结束,定时器会触发,内核收到中断后修改线程的运行状态。

例如线程会被标志为就绪而进入就绪队列等待调度

wait和notify的使用

wait

  • wait(),当前线程进入 无限等待状态,必须被唤醒才能继续执行,调用后会释放锁对象

  • wait(long timeout),wait(long timeout,int nanos),当前线程进入等待状态,可以被提前唤醒,但在指定时间后会自动唤醒

notify

  • notify(), 随机唤醒一个在锁对象上调用wait的线程

  • notifyAll(),唤醒 全部在锁对象上调用wait的线程

例子

public class TestDemo {
    public static void main(String[] args) {
        // 创建锁对象,保证唯一
        Object obj = new Object();
        // 创建一个顾客线程(消费者)
        new Thread() {
            @Override
            public void run() {
                // 保证正等待和唤醒的线程只能有一个执行,需要使用同步技术
                synchronized (obj) {
                    System.out.println("告诉老板要的包子的种类和数量");
                    // 调用wait方法,放弃cpu的执行,进入到WAITING状态(无限等待)
                    try {
                        obj.wait();
                    } catch (InterruptedException e) {
                        e.printStackTrace();
                    }
                    // 唤醒之后执行的代码
                    System.out.println("包子已经准备好了,开吃!");
                }
            }
        }.start();

        // 创建一个老板线程(生产者)
        new Thread() {
            @Override
            public void run() {
                // 花了5秒钟准备包子
                try {
                    Thread.sleep(5000);
                } catch (InterruptedException e) {
                    e.printStackTrace();
                }

                synchronized (obj) {
                    System.out.println("老板5秒钟之后准备好包子,告知顾客,可以吃包子了");
                    // 准备好包子之后,调用notify方法,唤醒顾客吃包子
                    obj.notify();
                }
            }
        }.start();
    }
}

执行结果:

告诉老板要的包子的种类和数量
老板5秒钟之后准备好包子,告知顾客,可以吃包子了
包子已经准备好了,开吃!

 

生产者消费者模式

  • 生产者Producer.java源码

import java.util.Queue;

public class Producer implements Runnable {
    private Queue<String> bags;
    private int size;

    public Producer(Queue<String> bags, int size) {
        this.bags = bags;
        this.size = size;
    }

    public void run() {
        int i = 0;
        while (true) {
            i++;
            synchronized (bags) {
                while (bags.size() == size) {
                    System.out.println("bags 满了");
                    try {
                        bags.wait();
                    } catch (InterruptedException e) {
                        e.printStackTrace();
                    }
                }
                try {
                    Thread.sleep(1000);
                } catch (InterruptedException e) {
                    e.printStackTrace();
                }
                System.out.println("生产者 - 生产:bag " + i);
                bags.add("bag " + i);
                // 唤醒处于阻塞状态下的消费者
                bags.notifyAll();
            }
        }
    }
}

消费者Consumer.java源码

import java.util.Queue;

public class Consumer implements Runnable {
    private Queue<String> bags;
    private int size;

    public Consumer(Queue<String> bags, int size) {
        this.bags = bags;
        this.size = size;
    }

    public void run() {
        while (true) {
            synchronized (bags) {
                while (bags.isEmpty()) {
                    System.out.println("bags 为空。");
                    try {
                        bags.wait();
                    } catch (InterruptedException e) {
                        e.printStackTrace();
                    }
                }
                try {
                    Thread.sleep(1000);
                } catch (InterruptedException e) {
                    e.printStackTrace();
                }
                String bag = bags.remove();
                System.out.println("消费者消费:" + bag);
                // 唤醒处于阻塞状态下的生产者
                bags.notifyAll();
            }
        }
    }
}
  • 测试WaitNotifyDemo.java源码

import java.util.LinkedList;
import java.util.Queue;

public class WaitNotifyDemo {
    public static void main(String[] args) {
        Queue<String> queue = new LinkedList<String>();
        int size = 10;
        Producer producer = new Producer(queue, size);
        Consumer consumer = new Consumer(queue, size);
        Thread t1 = new Thread(producer);
        Thread t2 = new Thread(consumer);
        t1.start();
        t2.start();
    }
}

Thread.interrupted和Thread.interrupt

为什么Thread.stop不推荐使用?

因为它本质上是不安全的。停止线程会导致它解锁所有已锁定的监视器。(当ThreadDeath异常在堆栈中传播时,监视器被解锁。)如果之前由这些监视器保护的对象中的任何一个处于不一致状态,则其他线程现在可以以不一致的状态查看这些对象。据称这些物体被 损坏。当线程操作受损对象时,可能导致任意行为。这种行为可能微妙且难以检测,或者可能会发音。与其他未经检查的异常不同,可以 ThreadDeath静默地杀死线程; 因此,用户没有警告他的程序可能被损坏。腐败现象可能会在实际损害发生后随时出现,甚至可能在未来数小时甚至数天。

 

通过共享变量来终止线程

import java.util.concurrent.TimeUnit;

public class StopDemo {
    static volatile boolean bStop = false;

    public static void main(String[] args) throws InterruptedException {
        Thread thread = new Thread(new StopThread());
        thread.start();
        TimeUnit.SECONDS.sleep(2000);
        bStop = true;
    }

    static class StopThread implements Runnable {
        public void run() {
            while(!bStop) {
                System.out.println("持续运行");
                try {
                    Thread.sleep(1000);
                } catch (InterruptedException e) {
                    e.printStackTrace();
                }
            }
        }
    }
}

interrupt 方法

当其他线程通过调用当前线程的interrupt方法,表示向当前线程打个招呼,告诉他可以中断线程的执行了,至于什么时候中断,取决于当前线程自己。

import java.util.concurrent.TimeUnit;

public class InterruptDemo {
    public static void main(String[] args) throws InterruptedException {
        Thread thread = new Thread(()-> {
            while(!Thread.currentThread().isInterrupted()) {
                System.out.println("持续执行......");
            }
        });
        thread.start();
        Thread.sleep(1);
        thread.interrupt();
        System.out.println("线程中断了...");
    }
}
  • 什么情况下会抛出InterruptedException异常?

当一个线程处于阻塞状态下(例如休眠)的情况下,调用了该线程的interrupt()方法,则会出现InterruptedException。

  • 如何处理InterruptedException?

    1. 不要生吞此异常;

    2. 如果可以处理此异常:完成清理工作之后退出;

    3. 不处理此异常,不继续执行任务:重新抛出;

    4. 不处理此异常,继续执行任务:捕捉到异常之后恢复中断标记(交由后续程序检查中断)。

Interrupted方法

public class MyThreadDemo extends Thread {
    @Override
    public void run() {
        for (int i = 0; i < 10; i++) {
            System.out.println("i = " + (i + 1));
        }
    }

    public static void main(String[] args) {
        MyThreadDemo thread = new MyThreadDemo();
        thread.start();
        thread.interrupt();
        System.out.println("第一次调用thread.isInterrupted() : " + thread.isInterrupted());
        System.out.println("第二次调用thread.isInterrupted() : " + thread.isInterrupted());
        System.out.println("thread是否存活 : " + thread.isAlive());
    }
}

运行结果: 第一次调用thread.isInterrupted() : true
第二次调用thread.isInterrupted() : true
thread是否存活 : true
i = 1
i = 2
i = 3
i = 4
i = 5
i = 6
i = 7
i = 8
i = 9
i = 10

 

从结果可以看出调用interrupt()方法后,线程仍在继续运行,并未停止,但已经给线程设置了中断标志,两个isInterrupted()方法都会输出true,也说明isInterrupted()方法并不会清除中断状态。

下面我们把代码修改一下,多加两行调用interrupted()方法:

public class MyThreadDemo extends Thread {
    @Override
    public void run() {
        for (int i = 0; i < 10; i++) {
            System.out.println("i = " + (i + 1));
        }
    }

    public static void main(String[] args) {
        MyThreadDemo thread = new MyThreadDemo();
        thread.start();
        thread.interrupt();
        System.out.println("第一次调用thread.isInterrupted() : " + thread.isInterrupted());
        System.out.println("第二次调用thread.isInterrupted() : " + thread.isInterrupted());
        System.out.println("第一次调用threadinterrupted() : " + thread.interrupted());
        System.out.println("第二次调用thread.interrupted() : " + thread.interrupted());
        System.out.println("thread是否存活 : " + thread.isAlive());
    }
}

运行结果: 第一次调用thread.isInterrupted() : true
i = 1
第二次调用thread.isInterrupted() : true
i = 2
第一次调用threadinterrupted() : false
第二次调用thread.interrupted() : false
i = 3
thread是否存活 : true
i = 4
i = 5
i = 6
i = 7
i = 8
i = 9
i = 10

 

从输出结果看,可能会有疑惑,为什么后面两个interrupted方法输出的都是false,而不是预料中的一个true一个false?

注意!!!这是一个坑!!!上面说到,interrupted()方法测试的是当前线程是否被中断

这里当前线程是main线程,而thread.interrupt()中断的是thread线程。

所以当前线程main从未被中断过,尽管interrupted()方法是以thread.interrupted()的形式被调用,但它检测的仍然是main线程而不是检测thread线程,所以thread.interrupted()在这里相当于main.interrupted()。对于这点,下面我们再修改进行测试。

Thread.currentThread()函数可以获取当前线程,下面代码中获取的是main线程

public static void main(String[] args) {
    Thread.currentThread().interrupt();
    System.out.println("第一次调用Thread.currentThread().interrupt():"
                       +Thread.currentThread().isInterrupted());
    System.out.println("第一次调用thread.interrupted():"
                       +Thread.currentThread().interrupted());
    System.out.println("第二次调用thread.interrupted():"
                       +Thread.currentThread().interrupted());
}

运行结果: 第一次调用Thread.currentThread().interrupt():true
第一次调用thread.interrupted():true
第二次调用thread.interrupted():false

结果证明猜想是正确的。

若果想要是实现调用interrupt()方法真正的终止线程,则可以在线程的run方法中做处理即可,比如直接跳出run()方法使线程结束,视具体情况而定,下面是一个例子。

public class MyThreadDemo extends Thread {
    @Override
    public void run() {
        for (int i = 0; i < 1000; i++) {
            System.out.println("i = " + (i + 1));
            if(this.isInterrupted()) {
                System.out.println("通过this.isInterrupted()检测到中断");
                System.out.println("第一个interrupted()" + this.interrupted());;
                System.out.println("第二个interrupted()" + this.interrupted());
                break;
            }
        }
        System.out.println("因为检测到中断,所以跳出循环,线程到这里结束,因为后面没有内容了。");
    }

    public static void main(String[] args) throws InterruptedException {
        MyThreadDemo myThread = new MyThreadDemo();
        myThread.start();
        myThread.interrupt();
        // sleep等待一秒,等myThread运行完
        Thread.currentThread().sleep(1000);
        System.out.println("myThread线程是否存活:" + myThread.isAlive());
    }
}

运行结果: i = 1
通过this.isInterrupted()检测到中断
第一个interrupted()true
第二个interrupted()false
因为检测到中断,所以跳出循环,线程到这里结束,因为后面没有内容了。
myThread线程是否存活:false

 

总结

  • interrupt() 是给线程设置中断标志

  • interrupted() 是检测中断并清除中断状态

  • isInterrupted() 只检测中断。

  • 还有一点就是interrupted()作用于当前线程interrupt()isInterrupted() 作用于此线程,即代码中调用此方法的实例所代表的线程

 

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

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

相关文章

联通软研院:基于OceanBase落地检索增强生成 (RAG) 的应用实践

本文作者&#xff1a;邱永刚&#xff0c;联通软件研究院OceanBase研发负责人&#xff0c;主要负责中国联通自研关系型数据库分布式CUDB研发、支撑、运维工作。 近年来&#xff0c;生成式人工智能技术取得了飞速进步&#xff0c;很多大模型在自然语言处理及对话系统领域的运用吸…

【更新】LLM Interview

课程链接&#xff1a;BV1o217YeELo 文章目录 LLM基础相关1. LLMs概述2. 大语言模型尺寸3. LLMs的优势与劣势4. 常见的大模型分类5. 目前主流的LLMs开源模型体系有哪些&#xff08;Prefix Decoder&#xff0c;Causal Decoder&#xff0c;Encoder-Decoder的区别是什么&#xff09…

模型 课题分离

系列文章 分享 模型&#xff0c;了解更多&#x1f449; 模型_思维模型目录。明确自我与他人责任。 1 课题分离的应用 1.1课题分离在心理治疗中的应用案例&#xff1a;李晓的故事 李晓&#xff0c;一位28岁的软件工程师&#xff0c;在北京打拼。他面临着工作、家庭和感情的多重…

sqlite 自定以脚本解释器

应用程序使用 libfdt 解析设备树,获取兼容性配置 内核源码支持libfdt 标准设备树语法,不用自己再创造 非常的爽,因为设备树支持预编译 一些可以跑类 BSD 系统的设备也可以使用这样的方法,不仅仅是在linux 系统上跑 有pylibfdt 支持解析设备树&#xff0c;校验设备树是否是正确的…

某医院vsan部署技术手册

环境配置 服务器4900G5五台 配置信息&#xff1a;cpu 8368*2颗 &#xff0c;内存256GB ,双口万兆网卡两个&#xff0c;四口千兆&#xff0c;RAID卡LSI9361-8i12G SAS RAID &#xff0c;两块固态盘480SSD &#xff0c;2*1.92TB NVME盘&#xff0c;5*8T盘。 万兆交换机两台H3C…

【编辑器扩展】打开持久化路径/缓存路径/DataPath/StreamingAssetsPath文件夹

代码 [MenuItem("Assets/Open Explorer/PersistentDataPath")]public static void OpenPersistentDataPath(){Application.OpenURL(Application.persistentDataPath);}[MenuItem("Assets/Open Explorer/DataPath")]public static void OpenDataPath(){Appl…

【day14】异常处理与Object类深入解析

【day13】回顾 在深入探讨异常处理与Object类之前&#xff0c;让我们回顾一下【day13】中的关键内容&#xff1a; 权限修饰符&#xff1a; public&#xff1a;最广的访问范围&#xff0c;任何地方都可以访问。protected&#xff1a;在同包和子类中可以访问。默认&#xff08;无…

【NLP 17、NLP的基础——分词】

我始终相信&#xff0c;世间所有的安排都有它的道理&#xff1b;失之东隅&#xff0c;收之桑榆 —— 24.12.20 一、中文分词的介绍 1.为什么讲分词&#xff1f; ① 分词是一个被长期研究的任务&#xff0c;通过了解分词算法的发展&#xff0c;可以看到NLP的研究历程 ② 分词…

11.vector的介绍及模拟实现

1.vector的介绍 记得之前我们用C语言实现过顺序表&#xff0c;vector本质上也是顺序表&#xff0c;一个能够动态增长的数组。 vector 的底层实现机制 - 动态数组&#xff1a;vector 的底层实现是动态数组。它在内存中连续存储元素&#xff0c;就像一个可以自动调整大小的数…

# 起步专用 - 哔哩哔哩全模块超还原设计!(内含接口文档、数据库设计)

↑ 上方下载文档 (大小374KB) 接口文档预览 (超过50个接口) 一、数据库25张表er-关系清晰构图&#xff01;(tip: 鼠标右键图片 > 放大图像) 二、难点/经验 详细说明 热门评论排序评论点赞列表|DTO封装经验分享|精华接口文档说明 组员都说喜欢分档对应枚举码 如果这篇文章…

android RecyclerView 垂直显示示例(java)

RecyclerView垂直列表显示示例&#xff0c;显示图片加文字。 1、RecyclerView.Adapter适配器 public class ListAdapter extends RecyclerView.Adapter<ListAdapter.ViewHolder> {private Context mContext;private List<TitleBean> titleBeans;public ListAdapt…

华为云语音交互SIS的使用案例(文字转语音-详细教程)

文章目录 题记一 、语音交互服务&#xff08;Speech Interaction Service&#xff0c;简称SIS&#xff09;二、功能介绍1、实时语音识别2、一句话识别3、录音文件识别4、语音合成 三、约束与限制四、使用1、API2、SDK 五、项目集成1、引入pom依赖2、初始化 Client1&#xff09;…

GitCode 光引计划投稿|智能制造一体化低代码平台 Skyeye云

随着智能制造行业的快速发展&#xff0c;企业对全面、高效的管理解决方案的需求日益迫切。然而&#xff0c;传统的开发模式往往依赖于特定的硬件平台&#xff0c;且开发过程繁琐、成本高。为了打破这一瓶颈&#xff0c;Skyeye云应运而生&#xff0c;它采用先进的低代码开发模式…

网络刷卡器的功能和使用场景

网络刷卡器是一种连接互联网的设备&#xff0c;能够通过网络将读取到的各种卡片信息传输至服务器进行处理。这类刷卡器通常支持多种类型的卡片&#xff0c;如银行卡、身份证、会员卡、公交卡等&#xff0c;并运用现代信息技术确保数据的安全性和高效性&#xff0c;功能十分强大…

从零开始C++游戏开发之第七篇:游戏状态机与回合管理

在游戏开发的道路上&#xff0c;状态管理是一个无法绕开的重要课题。尤其是在棋牌类游戏中&#xff0c;游戏的进行需要有条不紊地按照回合推进&#xff0c;同时管理多个游戏状态&#xff0c;如“等待玩家加入”、“游戏进行中”、“结算阶段”等。如何优雅且高效地实现这些逻辑…

有没有检测吸烟的软件 ai视频检测分析厂区抽烟报警#Python

在现代厂区管理中&#xff0c;安全与规范是重中之重&#xff0c;而吸烟行为的管控则是其中关键一环。传统的禁烟管理方式往往依赖人工巡逻&#xff0c;效率低且存在监管死角&#xff0c;难以满足当下复杂多变的厂区环境需求。此时&#xff0c;AI视频检测技术应运而生&#xff0…

CentOS7网络配置,解决不能联网、ping不通外网、主机的问题

1. 重置 关闭Centos系统 编辑->虚拟网络编辑器 还原默认设置 2. 记录基本信息 查看网关地址,并记录在小本本上 查看网段,记录下 3. 修改网卡配置 启动Centos系统 非root用户,切换root su root查看Mac地址 ifconfig 或 ip addr记录下来 修改配置文件 vim /et…

32岁前端干了8年,是继续做前端开发,还是转其它工作

前端发展有瓶颈&#xff0c;变来变去都是那一套&#xff0c;只是换了框架换了环境。换了框架后又得去学习&#xff0c;虽然很快上手&#xff0c;但是那些刚毕业的也很快上手了&#xff0c;入门门槛越来越低&#xff0c;想转行或继续卷&#xff0c;该如何破圈 这是一位网友的自述…

麒麟操作系统服务架构保姆级教程(三)ssh远程连接

如果你想拥有你从未拥有过的东西&#xff0c;那么你必须去做你从未做过的事情 作为一名成熟运维架构师&#xff0c;我们需要管理的服务器会达到几十台&#xff0c;上百台&#xff0c;上千台&#xff0c;甚至是上万台服务器&#xff0c;而且咱们的服务器还不一定都在一个机房&am…

Hmsc包开展群落数据联合物种分布模型分析通用流程(Pipelines)

HMSC&#xff08;Hierarchical Species Distribution Models&#xff09;是一种用于预测物种分布的统计模型。它在群落生态学中的应用广泛&#xff0c;可以帮助科学家研究物种在不同环境条件下的分布规律&#xff0c;以及预测物种在未来环境变化下的潜在分布范围。 举例来说&a…