# Java 并发编程的艺术(二)

news2024/12/22 15:19:48

Java 并发编程的艺术(二)

文章目录

  • Java 并发编程的艺术(二)
    • 并发编程的挑战
      • 上下文切换
        • 如何减少上下文的切换
      • 死锁
      • 资源限制的挑战
    • Java 并发机制的底层实现原理
      • volatile 的应用
      • synchronized 的实现原理与应用
        • 三大特性
        • 实现原理
    • Java 并发编程基础
      • 线程简介
        • 使用多线程的原因
        • 线程优先级
        • 线程的状态
        • 线程状态变迁
        • Daemon 线程
      • 启动和终止线程
        • 构造线程
        • 启动线程
        • 理解中断
        • 过期的 suspend()、resume()和 stop()
        • 安全地终止线程
      • 线程间通信
        • volatile 和 synchronized 关键字
        • 等待/通知机制
          • 等待方遵循如下原则
          • 通知方遵循如下原则
        • 管道输入/输出流
        • Thread.join()的使用
        • ThreadLocal 的使用

并发编程的挑战

上下文切换

  • 即使是单核处理器也支持多线程执行代码,CPU 通过给每个线程分配 CPU 时间片来实现这个机制。
  • CPU 通过时间片分配算法来循环执行任务,当前任务执行一个时间片后会切换到下一个任务。
  • 任务从保存到再加载的过程就是一次上下文切换
  • 切换上下文需要一定的消耗

如何减少上下文的切换

  • 使用最少线程:避免创建不需要的线程
  • CAS 算法:JavaAtomic 包使用 CAS 算法来更新数据,而不需要加锁。
  • 无锁并发编程:多线程竞争锁时,会引起上下文切换,所以多线程处理数据时,可以用一些办法来避免使用锁

死锁

  • 避免一个线程同时获取多个锁。

  • 避免一个线程在锁内同时占用多个资源,尽量保证每个锁只占用一个资源。

  • 尝试使用定时锁,使用 lock.tryLock(timeout)来替代使用内部锁机制。

  • 对于数据库锁,加锁和解锁必须在一个数据库连接里,否则会出现解锁失败的情况

资源限制的挑战

  • 在并发编程中,将代码执行速度加快的原则是将代码中串行执行的部分变成并发执行,但是如果将某段串行的代码并发执行,因为受限于资源,仍然在串行执行,这时候程序不仅不会加快执行,反而会更慢,因为增加了上下文切换和资源调度的时间

Java 并发机制的底层实现原理

volatile 的应用

  • volatile保证共享变量的可见性
  • volatile 是轻量级的 synchronized

synchronized 的实现原理与应用

  • 对于普通同步方法,锁是当前实例对象。

  • 对于静态同步方法,锁是当前类的 Class 对象。

  • 对于同步方法块,锁是 Synchonized 括号里配置的对象。

三大特性

  • 原子性:一个或多个操作要么全部成功,要么全部失败。保证只有一个线程拿到锁访问共享资源
  • 可见性:当一个线程对共享变量进行修改后,其他线程可以立刻看到
  • 有序性:程序的执行顺序会按照代码的先后顺序执行。

实现原理

  • Java虚拟机则是通过进入和退出Monitor对象来实现方法同步和代码块同步的
  • 同步代码块的实现是由monitorentermonitorexit指令完成的,其中monitorenter指令所在的位置是同步代码块开始的位置,第一个monitorexit指令是用于正常结束同步代码块的指令

Java 并发编程基础

  • 线程作为操作系统调度的最小单元,多个线程能够同时执行,这将显著提升程序性能,在多核环境中表现得更加明显。
  • 过多地创建线程和对线程的不当管理也容易造成问题。

线程简介

  • 操作系统调度的最小单元是线程,也叫轻量级进程(Light Weight Process),在一个进程里可以创建多个线程,这些线程都拥有各自的计数器、堆栈和局部变量等属性,并且能够访问共享的内存变量。处理器在这些线程上高速切换,让使用者感觉到这些线程在同时执行。

使用多线程的原因

  • 提升处理器的使用效率

  • 异步处理请求,提升系统响应速度提升用户体验

  • Java 为多线程编程提供了良好、考究并且一致的编程模型,使开发人员能够更加专

    注于问题的解决

线程优先级

  • 线程优先级的范围从1-10,默认优先级是5

  • 针对频繁阻塞(休眠或者 I/O 操作)的线程需要设置较高优先级,而偏重计算(需要较多 CPU

    时间或者偏运算)的线程则设置较低的优先级,确保处理器不会被独占。

  • 线程优先级不能作为程序正确性的依赖

线程的状态

在这里插入图片描述

  • 测试.java
public class ThreadTest {

    public static void main(String[] args) {
        new Thread(new TimeWaiting(), "超时等待线程").start();
        new Thread(new Waiting(), "等待线程").start();
        // 使用两个 Blocked 线程,一个获取锁成功,另一个被阻塞
        new Thread(new Blocked(), "阻塞线程1").start();
        new Thread(new Blocked(), "阻塞线程2").start();
    }

    // 该线程不断地进行睡眠
    static class TimeWaiting implements Runnable {
        private static final Logger logger = LoggerFactory.getLogger(TimeWaiting.class);
        @Override
        public void run() {
            while (true) {
                logger.info(Thread.currentThread().toString());
                second(100);
            }
        }
    }

    // 该线程在 Waiting.class 实例上等待
    static class Waiting implements Runnable {
        private static final Logger logger = LoggerFactory.getLogger(Waiting.class);

        @Override
        public void run() {
            while (true) {
                synchronized (Waiting.class) {
                    try {
                        logger.info(Thread.currentThread().toString());
                        Waiting.class.wait();
                    } catch (InterruptedException e) {
                        e.printStackTrace();
                    }
                }
            }
        }
    }

    // 该线程在 Blocked.class 实例上加锁后,不会释放该锁
    static class Blocked implements Runnable {
        private static final Logger logger = LoggerFactory.getLogger(Blocked.class);

        public void run() {
            synchronized (Blocked.class) {
                while (true) {
                    logger.info(Thread.currentThread().toString());
                    second(100);
                }
            }
        }
    }

    public static final void second(long seconds) {
        try {
            TimeUnit.SECONDS.sleep(seconds);
        } catch (InterruptedException e) {
        }
    }
}
  • 运行结果
[超时等待线程] INFO util.thread.ThreadTest$TimeWaiting - Thread[超时等待线程,5,main]
[等待线程] INFO util.thread.ThreadTest$Waiting - Thread[等待线程,5,main]
[阻塞线程1] INFO util.thread.ThreadTest$Blocked - Thread[阻塞线程1,5,main]
  • Jstack查看线程信息
// 线程被阻塞
"阻塞???程2" #14 prio=5 os_prio=0 tid=0x00000000282d2800 nid=0x231a0 waiting for monitor entry [0x0000000028f0f000]
   java.lang.Thread.State: BLOCKED (on object monitor)
        at util.thread.ThreadTest$Blocked.run(ThreadTest.java:61)
        - waiting to lock <0x00000007167efbb8> (a java.lang.Class for util.thread.ThreadTest$Blocked)
        at java.lang.Thread.run(Thread.java:748)
// 线程获取到了 Blocked.class 的锁
"阻塞线程1" #13 prio=5 os_prio=0 tid=0x00000000282d2000 nid=0x25670 waiting on condition [0x0000000028e0f000]
   java.lang.Thread.State: TIMED_WAITING (sleeping)
        at java.lang.Thread.sleep(Native Method)
        at java.lang.Thread.sleep(Thread.java:340)
        at java.util.concurrent.TimeUnit.sleep(TimeUnit.java:386)
        at util.thread.ThreadTest.second(ThreadTest.java:70)
        at util.thread.ThreadTest$Blocked.run(ThreadTest.java:62)
        - locked <0x00000007167efbb8> (a java.lang.Class for util.thread.ThreadTest$Blocked)
        at java.lang.Thread.run(Thread.java:748)
 // 等待线程 线程在 Waiting 实例上等待
"等待线程" #12 prio=5 os_prio=0 tid=0x00000000282d1000 nid=0x24514 in Object.wait() [0x0000000028d0e000]
   java.lang.Thread.State: WAITING (on object monitor)
        at java.lang.Object.wait(Native Method)
        - waiting on <0x00000007167ecd70> (a java.lang.Class for util.thread.ThreadTest$Waiting)
        at java.lang.Object.wait(Object.java:502)
        at util.thread.ThreadTest$Waiting.run(ThreadTest.java:45)
        - locked <0x00000007167ecd70> (a java.lang.Class for util.thread.ThreadTest$Waiting)
        at java.lang.Thread.run(Thread.java:748)
// 超时等待
"超时等待线程" #11 prio=5 os_prio=0 tid=0x00000000282cf800 nid=0x254d8 waiting on condition [0x0000000028c0e000]
   java.lang.Thread.State: TIMED_WAITING (sleeping)
        at java.lang.Thread.sleep(Native Method)
        at java.lang.Thread.sleep(Thread.java:340)
        at java.util.concurrent.TimeUnit.sleep(TimeUnit.java:386)
        at util.thread.ThreadTest.second(ThreadTest.java:70)
        at util.thread.ThreadTest$TimeWaiting.run(ThreadTest.java:30)
        at java.lang.Thread.run(Thread.java:748)

线程状态变迁

  • 线程创建之后,调用 start()方法开始运行。当线程执行 wait()方法之后,线程进入等待状态。进入等待状态的线程需要依靠其他线程的通知才能够返回到运行状态,而超时等待状态相当于在等待状态的基础上增加了超时限制,也就是超时时间到达时将会返回到运行状态。当线程调用同步方法时,在没有获取到锁的情况下,线程将会进入到阻塞状态。线程在执行 Runnablerun()方法之后将会进入到终止状态。
    在这里插入图片描述
  • Java 将操作系统中的运行和就绪两个状态合并称为运行状态。
  • 阻塞状态是线程阻塞在进入 synchronized 关键字修饰的方法或代码块(获取锁)时的状态,但是阻塞在 java.concurrent 包中 Lock 接口的线程状态却是等待状态,因为 java.concurrent 包中Lock 接口对于阻塞的实现均使用了 LockSupport 类中的相关方法。

Daemon 线程

  • Daemon 属性需要在启动线程之前设置,不能在启动线程之后设置。
  • Daemon 线程被用作完成支持性工作,但是在 Java 虚拟机退出时 Daemon 线程中的 finally块并不一定会执行。

启动和终止线程

构造线程

  • 构造一个线程对象
  • 提供线程所属的线程组、线程优先级、是否是 Daemon 等属性
private void init(ThreadGroup g, Runnable target, String name,
                      long stackSize, AccessControlContext acc,
                      boolean inheritThreadLocals) {
}
  • 一个新构造的线程对象是由其 parent 线程来进行空间分配的,而child 线程继承了 parent 是否为 Daemon、优先级和加载资源的 contextClassLoader 以及可继承的 ThreadLocal,同时还会分配一个唯一的 ID 来标识这个 child 线程。至此,一个能够运行的线程对象就初始化好了,在堆内存中等待着运行。

启动线程

  • 当前线程(即 parent 线程)同步告知 Java 虚拟机,只要线程规划器空闲,应立即启动调用 start()方法的线程
new Thread(new TimeWaiting(), "超时等待线程").start();

理解中断

  • 线程的一个标识位属性,它表示一个运行中的线程是否被其他线程进行了中断操作。中断好比其他线程对该线程打了个招呼,其他线程通过调用该线程的interrupt()方法对其进行中断操作。

过期的 suspend()、resume()和 stop()

  • suspend()resume()stop()方法完成了线程的暂停、恢复和终止工作
  • 这些方法是过期的不建议使用:因为 suspend()resume()stop()方法带来的副作用,这些方法才被标注为不建议使用的过期方法,而暂停和恢复操作可以用后面提到的等待/通知机制来替代。

安全地终止线程

  • 通过中断操作和 cancel()方法均可使线程得以终止。这种通过标识位或者中断操作的方式能够使线程在终止时有机会去清理资源,而不是武断地将线程停止,因此这种终止线程的做法显得更加安全和优雅。
@Test
public void test1() throws InterruptedException {
    Thread thread = new Thread(() -> {
        while (!Thread.currentThread().isInterrupted()) {
            logger.info("Current date:{}", System.currentTimeMillis());
        }
    });
    thread.start();
    Thread.sleep(3000);
    thread.interrupt();
    if(thread.isInterrupted()){
        logger.info("Thread was interrupted..");
    }
    Thread.sleep(3000);
}

线程间通信

volatile 和 synchronized 关键字

  • 关键字volatile可以用来修饰字段(成员变量),就是告知程序任何对该变量的访问均需要从共享内存中获取,而对它的改变必须同步刷新回共享内存,它能保证所有线程对变量访问的可见性。
  • 关键字synchronized可以修饰方法或者以同步块的形式来进行使用,它主要确保多个线程在同一个时刻,只能有一个线程处于方法或者同步块中,它保证了线程对变量访问的可见性和排他性。

等待/通知机制

  • 一个线程修改一个对象的值,另一个线程感知变化。线程A调用对象Owait()进入等待状态,另一个线程B调用对象O的notify()或者 notifuAll()方法,线程A 收到通知后从对象Owait()方法返回,执行后续操作。两个线程通过对象O来完成交互,而对象上的wait()notify/notifyAll()的关系就如同开关信号一样,用来完成等待方和通知方之间的交互工作。
  • 等待通知相关方法如下
    在这里插入图片描述
  • Consume.java
public class Consume {

    private static final Logger logger = LoggerFactory.getLogger(Consume.class);

    private final Object lockValue;

    public Consume(Object object) {
        this.lockValue = object;
    }

    /**
     * 生产者赋值
     */
    public void getValue() {
        synchronized (lockValue) {
            if (ObjectUtils.isEmpty(ProductConsumeValue.value)) {
                try {
                    lockValue.wait();
                } catch (InterruptedException e) {
                    throw new RuntimeException(e);
                }
            }
            logger.info("Consume :{}", ProductConsumeValue.value);
            ProductConsumeValue.value = "";
            lockValue.notifyAll();
        }
    }
}
  • Product.java
public class Product {

    private static final Logger logger = LoggerFactory.getLogger(Consume.class);

    private Object lockValue;

    public Product(Object lockValue) {
        this.lockValue = lockValue;
    }

    /**
     * 生产者赋值
     */
    public void setValue() {
        synchronized (lockValue) {
            if (!ObjectUtils.isEmpty(ProductConsumeValue.value)) {
                try {
                    lockValue.wait();
                } catch (InterruptedException e) {
                    throw new RuntimeException(e);
                }
            }
            ProductConsumeValue.value = System.currentTimeMillis() + "_" + System.nanoTime();
            logger.info("Product :{}", ProductConsumeValue.value);
            lockValue.notify();
        }
    }
}
  • Test.java
public static void main(String[] args) {
    String value = "";
    Product product = new Product(value);
    Consume consume = new Consume(value);
    ProductThread productThread = new ProductThread(product);
    ConsumerThread consumerThread = new ConsumerThread(consume);
    productThread.start();
    consumerThread.start();
}
等待方遵循如下原则
  • 获取对象的锁。

  • 如果条件不满足,那么调用对象的 wait()方法,被通知后仍要检查条件。

  • 条件满足则执行对应的逻辑。

    对应的伪代码如下:

synchronized(对象) { 
    while(条件不满足) 
    {  
    	对象.wait();  
    }  
	对应的处理逻辑 
} 
通知方遵循如下原则
  • 获得对象的锁。
  • 改变条件
  • 通知所有等待在对象上的线程。

​ 对应的伪代码如下:

synchronized(对象){  
	改变条件  
	对象.notifyAll(); 
} 

管道输入/输出流

  • 管道输入/输出流和普通的文件输入/输出流或者网络输入/输出流不同之处在于,它 主要用于线程之间的数据传输,而传输的媒介为内存。管道输入/输出流主要包括了如下4 种具体实现:PipedOutputStreamPipedInputStreamPipedReaderPipedWriter,前两种面向字节,而后两种面向字符。

Thread.join()的使用

  • 如果主线程处理完其他的事务后,需要用到子线程的处理结果,也就是主线程需要等待子线程执行完成之后再结束,这个时候就要用到join()方法了。

  • 如果一个线程 A 执行了 thread.join()语句,其含义是:当前线程 A 等待 thread 线程终止之后才从 thread.join()返回。

public class ThreadJoinTest {

    private static final Logger logger = LoggerFactory.getLogger(ThreadJoinTest.class);

    /**
     * 如果主线程处理完其他的事务后,需要用到子线程的处理结果,也就是主线程需要等待子线程执行完成之后再结束,这个时候就要用到 join() 方法了。
     *
     * @param args args
     * @throws InterruptedException 中断异常
     */
    public static void main(String[] args) throws InterruptedException {
        Thread currentThread = Thread.currentThread();
        for (int i = 0; i < 10; i++) {
            JoinThreadTest joinTestTread = new JoinThreadTest(currentThread);
            Thread thread = new Thread(joinTestTread, "线程 " + i);
            thread.start();
            currentThread = thread;
        }
        Thread.sleep(5000);
    }

    private static class JoinThreadTest implements Runnable {
        private final Thread thread;

        private JoinThreadTest(Thread currentThread) {
            thread = currentThread;
        }

        @Override
        public void run() {
            try {
                thread.join();
            } catch (InterruptedException e) {
                throw new RuntimeException(e);
            }
            logger.info("当前线程:{}", Thread.currentThread().getName());
        }
    }
}

ThreadLocal 的使用

  • 线程变量,每个线程可以根据一个 ThreadLocal 对象查询到绑定在这个线程上的一个值。可以通过 set(T)方法来设置一个值,在当前线程下再通过 get()方法获取到原先设置的值。
public class ThreadLocalTest {

    private static final Logger logger = Logger.getLogger(String.valueOf(ThreadLocalTest.class));

    /**
     * 当使用 ThreadLocal 维护变量时,ThreadLocal 为每个使用该变量的线程提供独立的变量副本,
     * 所以每一个线程都可以独立地改变自己的副本,而不会影响其它线程所对应的副本。
     * 为了避免重复创建TSO(thread specific object,即与线程相关的变量) 使用 static final 修饰
     */
    private static final ThreadLocal<Map<String, String>> THREAD_LOCAL_MAP = new ThreadLocal<>();

    @Test
    public void test1() {
        Map<String, String> map = new HashMap<>();
        map.put("methodTest", "张三");
        map.put("test2", "李四");
        THREAD_LOCAL_MAP.set(map);
        getThreadLocalMap();
        THREAD_LOCAL_MAP.remove();
    }

    private void getThreadLocalMap() {
        Map<String, String> map = THREAD_LOCAL_MAP.get();
        logger.info(String.valueOf(map));
    }
}

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

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

相关文章

智慧冷链园区三维可视化,数字孪生助力大数据实时监控

近年来&#xff0c;业界学者及企业就智慧冷链物流展开深入研究&#xff0c;2010 年 IBM 发布的《智慧的未来供应链》研究报告中提出智慧供应链概念&#xff0c;并由此延伸出智慧物流概念&#xff0c;即智慧物流是以信息化为依托并广泛应用物联网、人工智能、大数据、云计算等技…

基于U-Net系列的医学图像分割

U-Net 在FCN 的基础上增加了上采样操作的次数和跳跃连接&#xff0c;使用跳跃连接将解码器的输出特征与编码器的语义特征融合&#xff0c;提高了分割精度&#xff0c;改善了 FCN 上采样不足的问题。 U-Net中没有全连接层&#xff0c;通过互连卷积与反卷积过程中的特征&#xff…

一文打通java泛型

目录 为什么要有泛型 生活场景 泛型的设计背景 泛型的概念 那么为什么要有泛型呢&#xff0c;直接Object不是也可以存储数据吗&#xff1f; 在集合中使用泛型 自定义泛型结构 注意点 自定义泛型结构&#xff1a;泛型类 自定义泛型结构&#xff1a;泛型方法 泛型在…

【微服务笔记23】使用Spring Cloud微服务组件从0到1搭建一个微服务工程

这篇文章&#xff0c;主要介绍如何使用Spring Cloud微服务组件从0到1搭建一个微服务工程。 目录 一、从0到1搭建微服务工程 1.1、基础环境说明 &#xff08;1&#xff09;使用组件 &#xff08;2&#xff09;微服务依赖 1.2、搭建注册中心 &#xff08;1&#xff09;引入…

网课/网校/知识付费/在线教育系统,100%全功能开源,可免费商用

一、开源项目简介 酷瓜云课堂&#xff0c;依托腾讯云基础服务架构&#xff0c;采用C扩展框架Phalcon开发&#xff0c;GPL-2.0开源协议&#xff0c;致力开源网课系统&#xff0c;开源网校系统&#xff0c;开源知识付费系统&#xff0c;开源在线教育系统。 酷瓜云课堂 - 网课系…

【LeetCode: 322. 零钱兑换 | 暴力递归=>记忆化搜索=>动态规划 | 背包模型】

&#x1f680; 算法题 &#x1f680; &#x1f332; 算法刷题专栏 | 面试必备算法 | 面试高频算法 &#x1f340; &#x1f332; 越难的东西,越要努力坚持&#xff0c;因为它具有很高的价值&#xff0c;算法就是这样✨ &#x1f332; 作者简介&#xff1a;硕风和炜&#xff0c;…

JavaScript 知识总结中篇(更新版)

72.get 请求传参长度的误区 常说&#xff1a;get 请求参数的大小存在限制&#xff0c;而 post 请求的参数大小无限制。 实际上 HTTP 协议并未规定 get / post 的请求参数大小限制。 纠正误区&#xff1a;是浏览器或 web 服务器对 get 请求参数的最大长度显示进行限制&#xf…

Nature Neuroscience:高家红团队首次发布中国人脑连接组计划研究成果及其大数据资源

人类生活在充满多样性的世界里。长久以来的研究发现&#xff0c;人类的脑与行为受到基因、环境和文化及其相互作用的塑造&#xff0c;然而这种影响发生的机制始终缺乏系统性探索与研究。近年来&#xff0c;前沿神经影像技术方法飞速进步&#xff0c;推动着多模态脑成像大数据集…

索尼ILCE-7SM3覆盖恢复案例

ILCE-7SM3算是索尼的流量级产品了&#xff0c;目前使用的比较多。今天我们来看一个格式化后又覆盖的恢复案例&#xff0c;看看这种情况下如何恢复残留的视频素材。 故障文件:80G SD卡 故障现象: 80G卡实际容量在74.5G左右&#xff0c;ExFat文件系统&#xff0c;格式化后又录…

Java8新特性-Stream

文章目录 简介Stream 的特性创建Stream通过集合创建流通过数组创建流通过Stream.of方法创建流创建规律的无限流创建无限流创建空流 Stream操作分类中间操作无状态filtermapflapMap 有状态distinctsortedreversedthenComparinglimitskipconcat 终结操作非短路操作forEachreducec…

D. Kilani and the Game(BFS模拟向四周漫延的过程)

Problem - D - Codeforces Kilani正在和他的朋友玩一个游戏。这个游戏可以表示为一个nm的网格&#xff0c;其中每个单元格都是空的或者被阻塞的&#xff0c;并且每个玩家在一些单元格中拥有一个或多个城堡&#xff08;一个单元格中没有两个城堡&#xff09;。 游戏分轮进行。每…

JS高级 -- 构造函数、数据常用函数

1. 深入对象 1.1 创建对象三种方式 利用对象字面量创建对象 const o {name:佩奇 }利用 new object 创建对象 const o new Object({ name:佩奇}) console.log(o) // {name: 佩奇}利用构造函数创建对象 1.2 构造函数 构造函数&#xff1a;是一种特殊的函数&#xff0c;主要…

SpringBoot+Vue3实现登录验证码功能

系列文章目录 Redis缓存穿透、击穿、雪崩问题及解决方法Spring Cache的使用–快速上手篇分页查询–Java项目实战篇全局异常处理–Java实战项目篇 Java实现发送邮件&#xff08;定时自动发送邮件&#xff09;_java邮件通知_心态还需努力呀的博客-CSDN博客 该系列文章持续更新…

最高效的七个云原生开发原则

​Cloud native是一种软件开发方法&#xff0c;利用云基础架构实现更快、更可扩展的部署。云原生应用程序旨在充分利用现代化的工程实践&#xff0c;如自动化、托管服务和自动扩展控制。 这种模式对组织文化和工作实践也有影响。云技术应成为软件交付的重要组成部分。每个人都…

迅为三星4412开发板UDP实现服务器和客户端

UDP 协议是开放式&#xff0c;无连接&#xff0c;不可靠的传输层通信协议&#xff0c;但它收发数据的速度相对于 TCP 快很多&#xff0c;常 用在传输音视频等数据量非常大的场合。 udp 网络编程只需要使用一个类 QUdpSocket。 本实验中对 QUdpSocket 的基本使用&#xff1a; …

选择DAO的组织结构时,应着重考虑的各个关键阶段与安全可靠性

近年来&#xff0c;去中心化自治组织 (Decentralized Autonomous Organizations&#xff0c;DAO)已成为了管理智能合约项目和社区的流行方式。简单而言&#xff0c;DAO是一个基于智能合约运作的数字化组织。组织内的成员可以根据对应的模型结构&#xff0c;做出不同的决策。虽然…

哪款蓝牙耳机学生用合适?学生党平价无线耳机推荐

近年来&#xff0c;蓝牙耳机凭借使用便捷&#xff0c;成为了许多人生活中不可或缺的一部分。不管是听歌、追剧还是玩游戏&#xff0c;或者运动等等&#xff0c;都能看到戴蓝牙耳机的人。那么&#xff0c;哪款蓝牙耳机适合学生用&#xff1f;下面&#xff0c;我来给大家推荐几款…

Unsupervised Learning of Depth and Ego-Motion from Video 论文精读

视频中深度和自我运动的无监督学习 摘要 我们提出了一个无监督学习框架&#xff0c;用于从非结构化视频序列中进行单眼深度和相机运动估计。与其他工作[10&#xff0c;14&#xff0c;16]一样&#xff0c;我们使用端到端的学习方法&#xff0c;将视图合成作为监督信号。与之前…

C++ 线程

linux使用线程 在linux使用线程可能出现&#xff0c;在编译时不会报错&#xff0c;但执行出错的问题。 undefined reference to pthread_create这是由于ubuntu平台下调用pthread_create()函数&#xff0c;pthread 库不是 Linux 系统默认的库&#xff0c;连接时需要使用静态库 …

Word转PDF:简单步骤,轻松完成!推荐两个实现的方法

随着社会的发展&#xff0c;PDF 文件已经成为一种非常流行的文件格式&#xff0c;对于一些需要对文件进行保密的企业而言&#xff0c;更是必不可少。在这样的大背景下&#xff0c;如何将 Word 文件转换为 PDF 文件呢&#xff1f;下面是一些简单的步骤&#xff0c;帮助您轻松完成…