【从零开始学习JAVA | 三十九篇】深入多线程

news2024/11/22 20:27:23

目录

前言:        

​1.线程的寿命周期​

2.线程的安全问题

3.锁

同步代码块:

同步方法:

死锁:

4.生产者和消费者模式(等待唤醒机制)

总结:


前言:        

        当今软件开发领域中,多线程编程已成为一项至关重要的技能。然而,要编写出高效、可靠的多线程程序并不容易。多线程编程面临着许多挑战,如线程安全性、资源共享、死锁等问题。因此,对于初学者来说,深入理解Java多线程的工作原理和机制是至关重要的。只有通过掌握多线程的核心概念、了解常见问题和解决方案,我们才能写出健壮且高性能的多线程应用。

        本文将为大家逐步深入介绍Java多线程的重要概念和机制。我们将从线程的创建和启动开始,讨论如何使用线程池管理线程,并探讨线程间的通信和同步技术。我们还将介绍一些常用的多线程设计模式和最佳实践,帮助读者更好地应用多线程技术解决实际问题。

1.线程的寿命周期

线程的生命周期描述了一个线程从创建到终止的整个过程,一般包含以下几个阶段:

  1. 新建状态(New):

    • 当线程对象被创建后,它处于新建状态。
    • 此时,线程还未被启动,即尚未调用start()方法。
  2. 可运行状态(Runnable):

    • 当线程调用start()方法后,进入可运行状态。
    • 线程处于此状态时,可能正在执行,也可能正在等待系统资源。
  3. 运行状态(Running):

    • 可运行状态中的线程被系统调度执行,处于运行状态。
    • 线程执行run()方法中的任务代码。
  4. 阻塞状态(Blocked):

    • 阻塞状态指线程因为某些原因暂时停止执行,例如等待某个资源、等待锁的释放等。
    • 当满足特定条件时,线程会进入阻塞状态,等待条件满足后被唤醒。
  5. 无限期等待状态(Waiting):

    • 线程在某些条件下调用无参数的wait()方法,会进入无限期等待状态。
    • 只有当其他线程显式地调用notify()或notifyAll()方法,或者被中断,才能解除该状态。
  6. 限期等待状态(Timed Waiting):

    • 线程在某些条件下调用具有超时参数的wait()、sleep()、join()或LockSupport.parkNanos()等方法,会进入限期等待状态。
    • 时间一过,或者收到特定事件的通知,该线程将会被唤醒。
  7. 终止状态(Terminated):

    • 线程执行完run()方法中的任务代码,或者线程发生异常而提前结束,都会进入终止状态。
    • 一旦线程进入终止状态,就不能再切换到其他状态。

需要注意的是,线程的状态可以相互切换,具体的转换由Java的线程调度器和操作系统决定。线程的生命周期和状态的转换对于多线程编程非常重要,合理地管理线程的状态可以提高程序的性能和并发能力。

2.线程的安全问题

我们用一个案例来说明:

现在我们要开设三个窗口来买票,一共有100张票,请你利用多线程的知识完成。


class MyThread extends Thread {
    static int  tick=0;
    public void run() {
        // 定义线程要执行的任务

        while(true)
       {
           if(tick<100)
           {

               try {
                   Thread.sleep(100);
               } catch (InterruptedException e) {
                   throw new RuntimeException(e);
               }
               tick++;
               System.out.println(getName()+"正在卖第"+tick+"张票");
           }
           else
           {
               break;
           }
       }
    }
}

public class test05 {
    public static void main(String[] args) {
        MyThread  t1 = new MyThread();
        MyThread  t2 = new MyThread();
        MyThread  t3 = new MyThread();

        t1.start();
        t2.start();
        t3.start();
    }
}

很多同学在第一时间就会写出这样一个简单的多线程,但是当我们运行之后,就会有一个明显的问题:出现了一张票卖了两次这种情况 ,也会出现了卖超了的这种现象。

 我们来详细解释一下为什么

线程1和线程2和线程3都在抢夺cpu调度,假设线程1抢到之后,那么他先进入if语句,但if语句中有一个sleep,执行到这里后,线程1就会被阻塞睡眠,此时线程2和线程3重新抢夺cpu调度,线程2抢到资源之后进入if语句也会睡眠,然后就是线程3进入资源,也会睡眠。随着这三个的睡眠周期结束,就又会执行if中的代码。当tick还没来得及打印的时候,线程2醒来又会抢夺cpu资源,如果抢到了,就又会执行一次tick++,接下来又是线程3.如此这样循环,就会造成卖出两张票并且可能卖超的结果。

通过这个案例我们可以看出多线程在执行的时候,有一个重要的隐患:

 线程的执行具有随机性

那么我们最简单的思路就是:

设计一种方法,这个方法使得  如果一个线程正在执行代码,那么其他的线程必须等待,只有当这个线程执行完之后,其他的线程才可以抢占CPU资源。这就是我们下面要介绍的东西

3.锁

同步代码块:

把代码块用锁锁起来

synchronized(锁)
{
    操作共享数据的代码
}

特点:

  •         锁是默认打开的,如果有一个进程进去了,锁就会自动关闭。
  •         里面的代码全部执行完毕,线程出来,锁自动打开

因此我们尝试一下用锁来改进一下


class MyThread extends Thread {
    static int  tick=0;
    static Object oj = new Object();
    public void run() {
        // 定义线程要执行的任务

        while(true)
        {

           try {
            Thread.sleep(10);
        } 
         catch (InterruptedException e)
        {
            throw new RuntimeException(e);
        }
             synchronized (oj)
          {

              if(tick<10000)
              {

                  tick++;
                  System.out.println(getName()+"正在卖第"+tick+"张票");
              }
              else
              {
                  break;
              }
          }
      }
    }
}

锁的注意点:

  1. 锁的粒度:要在保证线程安全的前提下,尽量减小锁的范围。过大的锁粒度可能导致不必要的线程阻塞,影响性能。可以考虑使用细粒度锁或者使用并发集合类来提高并发性能。

  2. 锁的公平性:锁可以是公平的或非公平的。公平锁会按照线程请求锁的顺序依次获取锁,而非公平锁则不保证线程获取锁的先后顺序。在选择锁时,根据具体情况选择公平或非公平锁。

  3. 死锁情况:死锁是指两个或多个线程相互等待对方释放持有的锁,从而导致所有线程无法继续执行的情况。为避免死锁,需要谨慎设计锁的获取顺序,并尽量避免嵌套锁的情况。

  4. 锁的释放:在使用锁时,需要保证锁的正确释放,以免出现资源泄漏或线程饥饿等问题。一般可以使用try-finally块来确保在发生异常时仍能正确释放锁。

  5. 锁的性能:锁的竞争会带来一定的性能开销,过多的锁竞争可能会影响应用的并发性能。可以考虑使用读写锁、无锁数据结构或并发集合类等替代方案,来降低锁竞争带来的性能开销。

  6. 死锁检测和避免:一旦发生死锁,所有线程都将无法继续执行。为了避免死锁,可以使用工具进行死锁检测,并合理设计锁的获取和释放顺序,避免潜在的死锁情况。

同步方法:

把方法用  锁  锁起来

修饰符   synchronized  返回值类型  方法名  (方法参数){...}

特点:

  • 同步方法是锁住方法里面的所有代码
  • 锁对象不能自己指定

非静态:this

静态:当前类的字节码文件

 则我们可把前面的改写为:

class MyThread   extends Thread  {
    static int  tick=0;
    static final Object oj = new Object();
    public synchronized void run() {
        // 定义线程要执行的任务

        while(true)
        {
                if (tick < 100) {

                    tick++;
                    System.out.println(getName() + "正在卖第" + tick + "张票");
                } else {

                    break;
                }

            }
      }
    }

死锁:

死锁是指在多线程编程中,两个或多个线程互相持有对方需要的资源,导致它们都无法继续执行,称为死锁现象。

死锁的发生通常需要满足以下四个条件,也称为死锁的必要条件:

  1. 互斥条件:至少有一个资源同时只能被一个线程持有。
  2. 请求与保持条件:一个线程在持有某个资源的同时,又请求获取其他线程持有的资源。
  3. 不可剥夺条件:已经分配给一个线程的资源不能被强制性地剥夺,只能由持有该资源的线程显式释放。
  4. 循环等待条件:多个线程之间形成循环等待一系列资源,而每个线程都在等待下一个线程所持有的资源。

当以上四个条件都满足时,就可能出现死锁。在死锁发生时,这些线程将无法继续执行下去,需要通过一些策略进行解决,如避免死锁的产生、检测死锁、解除死锁等。

解决死锁的方法一般有以下几种:

  1. 避免死锁:通过破坏死锁的必要条件之一,如避免循环等待,确保资源分配的顺序性。
  2. 检测与恢复:通过资源分配图、银行家算法等方法检测死锁的发生,然后采取相应的策略进行恢复,如终止某些线程、回收资源等。
  3. 预防死锁:通过一些算法和策略在设计阶段预防死锁的发生,如资源有序分配法、资源剥夺等。
  4. 忽略死锁:对于一些系统来说,死锁的发生概率较低且解决代价较高,可以选择忽略死锁。当发生死锁时,通过系统重启或人工介入恢复正常。
public class DeadlockExample {
    public static void main(String[] args) {
        final Object resource1 = new Object();
        final Object resource2 = new Object();

        Thread thread1 = new Thread(() -> {
            synchronized (resource1) {
                System.out.println("Thread 1 acquired lock on resource1");
                try {
                    Thread.sleep(1000);
                } catch (InterruptedException e) {
                    e.printStackTrace();
                }
                synchronized (resource2) {
                    System.out.println("Thread 1 acquired lock on resource2");
                }
            }
        });

        Thread thread2 = new Thread(() -> {
            synchronized (resource2) {
                System.out.println("Thread 2 acquired lock on resource2");
                try {
                    Thread.sleep(1000);
                } catch (InterruptedException e) {
                    e.printStackTrace();
                }
                synchronized (resource1) {
                    System.out.println("Thread 2 acquired lock on resource1");
                }
            }
        });

        thread1.start();
        thread2.start();

        // 等待两个线程执行完毕
        try {
            thread1.join();
            thread2.join();
        } catch (InterruptedException e) {
            e.printStackTrace();
        }

        System.out.println("Execution completed");
    }
}

在上述代码中,两个线程 thread1 和 thread2 分别尝试获取 resource1 和 resource2 的锁。但是它们获取锁的顺序是相反的,即 thread1 先获取 resource1 的锁,再获取 resource2 的锁;而 thread2 先获取 resource2 的锁,再获取 resource1 的锁。

这种情况下,如果两个线程同时启动,则 thread1 获取了 resource1 的锁并等待 resource2 的锁释放,而 thread2 获取了 resource2 的锁并等待 resource1 的锁释放。由于两个线程相互等待对方所持有的锁,它们将处于死锁状态,无法继续执行下去。

4.生产者和消费者模式(等待唤醒机制)

在Java中,生产者-消费者模式是一种常见的多线程协作模式,用于解决生产者消费者之间的数据交换和同步问题。

我们在以前的多线程中,会发现每条线程是都执行都是随机的,可能会是

A A A A B B A A B A 

而等待唤醒机制可以是线程的交替变得有规律,变为

A B A B A B A B A B A

生产者是生成数据的线程,而消费者是消耗数据的线程。下面是对Java中生产者和消费者的详细介绍:

  1. 生产者:

    • 生产者负责生产数据,并将其放入共享的缓冲区或队列中,以供消费者使用。
    • 生产者线程通常会循环执行,生成数据并将其添加到缓冲区中。
    • 当缓冲区已满时,生产者会等待,直到有足够的空间来存放新的数据。
  2. 消费者:

    • 消费者负责从缓冲区中获取数据,并进行消费或处理。
    • 消费者线程通常会循环执行,从缓冲区中取出数据并进行相应的处理操作。
    • 当缓冲区为空时,消费者会等待,直到有新的数据可供消费。
  3. 共享缓冲区:

    • 生产者和消费者之间的数据交换通常通过共享的缓冲区或队列来进行。
    • 缓冲区可以是一个数组、一个队列或其他数据结构,用来存放生产者生成的数据,供消费者取出。
    • 缓冲区的大小是有限的,当缓冲区已满时,生产者必须等待;当缓冲区为空时,消费者必须等待。
    • 生产者将数据添加到缓冲区的末尾,而消费者从缓冲区的前端消费数据。

为了实现生产者-消费者模式,可以使用以下方法之一:

  1. wait() 和 notify():

    • 使用对象的 wait() 和 notify() 方法来实现线程的等待和唤醒操作。
    • 生产者在缓冲区已满时调用 wait() 方法进行等待,并在生产数据后调用 notify() 方法唤醒消费者。
    • 消费者在缓冲区为空时调用 wait() 方法进行等待,并在消费数据后调用 notify() 方法唤醒生产者。
  2. Condition 和 Lock:

    • 使用 java.util.concurrent.locks.Condition 和 java.util.concurrent.locks.Lock 接口来实现线程的等待和唤醒操作。
    • 生产者和消费者分别使用不同的条件变量来等待和唤醒。
    • 使用Lock对象来保护共享数据的访问,通过条件变量的 await() 和 signal() 方法进行线程的等待和唤醒操作。

生产者-消费者模式可以帮助解决多线程并发情况下的数据同步和数据交换问题,确保生产者和消费者之间的协调运行。这种模式在许多并发编程场景中都有应用,如线程池、消息队列、生产者-消费者问题等。

生产者与消费者模式的意义:

  1. 解耦生产者和消费者:

    • 生产者-消费者模式将数据的生产和消费过程解耦,使得生产者和消费者可以独立进行操作。
    • 生产者只需关注生成数据并将其放入缓冲区,而不需要关心数据如何被消费。
    • 消费者只需关注从缓冲区中获取数据并进行相应处理,而不需要关心数据的生成过程。
  2. 提高系统的并发性和吞吐量:

    • 生产者和消费者可以并行地执行,从而提高系统的并发性能。
    • 生产者不必等待消费者完成对数据的处理,而可以继续生产新的数据。
    • 消费者不必等待生产者生成新的数据,而可以并行地处理已有的数据。
  3. 缓冲区平衡生产和消费速度:

    • 生产者和消费者之间通过共享的缓冲区进行数据交换和同步。
    • 缓冲区充当了生产者和消费者之间的中介,平衡了它们之间的生产和消费速度。
    • 当生产者速度快于消费者时,数据会被存储在缓冲区中,以供消费者使用。
    • 当消费者速度快于生产者时,消费者可以从缓冲区中获取数据,而不必等待生产者生成。
  4. 实现线程间的通信和同步:

    • 生产者-消费者模式为线程间的通信和同步提供了一种有效的方式。
    • 生产者和消费者可以利用等待和唤醒机制来实现线程的同步和协作。
    • 生产者在缓冲区已满时等待,直到有可用空间;消费者在缓冲区为空时等待,直到有可供消费的数据。
    • 当生产者生成新的数据或消费者消耗了数据时,它们可以相互通知和唤醒对方。

综上所述,生产者-消费者模式是一种重要的多线程编程模式,它能够提高系统的并发性、吞吐量和效率,实现生产者和消费者之间的解耦和协作,确保数据交换和同步的正确性和可靠性。在并发编程和异步系统中广泛应用。

import java.util.LinkedList;

class Producer implements Runnable {
    private LinkedList<Integer> buffer;
    private int maxSize;
    
    public Producer(LinkedList<Integer> buffer, int maxSize) {
        this.buffer = buffer;
        this.maxSize = maxSize;
    }
    
    @Override
    public void run() {
        for (int i = 1; i <= 10; i++) {
            try {
                produce(i);
            } catch (InterruptedException e) {
                e.printStackTrace();
            }
        }
    }
    
    private void produce(int value) throws InterruptedException {
        synchronized (buffer) {
            while (buffer.size() == maxSize) {
                System.out.println("缓冲区已满,生产者等待...");
                buffer.wait();
            }
            
            buffer.add(value);
            System.out.println("生产者生产: " + value);
            
            buffer.notifyAll();
        }
    }
}

class Consumer implements Runnable {
    private LinkedList<Integer> buffer;
    
    public Consumer(LinkedList<Integer> buffer) {
        this.buffer = buffer;
    }
    
    @Override
    public void run() {
        while (true) {
            try {
                consume();
            } catch (InterruptedException e) {
                e.printStackTrace();
            }
        }
    }
    
    private void consume() throws InterruptedException {
        synchronized (buffer) {
            while (buffer.size() == 0) {
                System.out.println("缓冲区为空,消费者等待...");
                buffer.wait();
            }
            
            int value = buffer.removeFirst();
            System.out.println("消费者消费: " + value);
            
            buffer.notifyAll();
        }
    }
}

public class ProducerConsumerExample {
    public static void main(String[] args) {
        LinkedList<Integer> buffer = new LinkedList<>();
        int maxSize = 5;
        
        Producer producer = new Producer(buffer, maxSize);
        Consumer consumer = new Consumer(buffer);
        
        Thread producerThread = new Thread(producer);
        Thread consumerThread = new Thread(consumer);
        
        producerThread.start();
        consumerThread.start();
    }
}

这个示例中,生产者线程通过 produce() 方法在 buffer 中生产数据,而消费者线程通过 consume() 方法从 buffer 中消费数据。其中,buffer 是一个共享的缓冲区,采用了等待和唤醒机制来实现线程的同步。

注意,在示例中使用了 LinkedList 作为缓冲区,但这只是一种示例使用的数据结构,实际上可以使用其他线程安全的数据结构,如 ArrayBlockingQueue 或 LinkedBlockingQueue 来实现更高效的生产者-消费者模式。

运行代码示例后,你可以观察到生产者逐个生成数据并放入缓冲区,而消费者逐个从缓冲区中取出数据消费,它们之间的执行是交替进行的。当缓冲区已满时,生产者线程会等待;当缓冲区为空时,消费者线程会等待。这样,生产者和消费者之间的数据交换和同步就实现了。

总结:

        今天我们学习了多线程中必要有意思的寿命周期,锁以及一个多线程的经典模式:生产者和消费者模式。多线程作为一项处理高并发和高吞吐量的重要技术,其各项知识点我们都应该拥有较好的掌握程度,这样才可以熟练的使用多线程。

如果我的内容对你有帮助,请点赞,评论,收藏。创作不易,大家的支持就是我坚持下去的动力!

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

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

相关文章

构建Docker容器监控系统(2)(Cadvisor +Prometheus+Grafana)

Cadvisor产品简介 Cadvisor是Google开源的一款用于展示和分析容器运行状态的可视化工具。通过在主机上运行Cadvisor用户可以轻松的获取到当前主机上容器的运行统计信息&#xff0c;并以图表的形式向用户展示。 接着上一篇来继续 部署Cadvisor 被监控主机上部署Cadvisor容器…

Echart(v5)实现中国地图区域图

一、需求背景 需要实现一个中国地图的区域图&#xff08;区域级别到市&#xff09;&#xff0c;并且指定区域可以高亮。 二、相关工具 1、中国的GeoJSON数据获取&#xff1a;DataV.GeoAtlas地理小工具系列 2、Echart组件库 Apache ECharts 三、实现 echart配置&#xff1a; …

MySQL查看当前数据库视图-SQL语句

引言 查询语句为&#xff1a; show full tables where table_type 可查询当前数据库表 一&#xff0c;创建一个视图 # 创建视图 create view v_stu as # 视图内容&#xff08;连接的一个表&#xff09; select name from t_stu union all select tname from t_teach; 二&…

RISC-V云测平台:Compiling The Fedora Linux Kernel Natively on RISC-V

注释&#xff1a;编译Fedora&#xff0c;HS-2 64核RISC-V服务器比Ryzen5700x快两倍&#xff01; --- 以下是blog 正文 --- # Compiling The Fedora Linux Kernel Natively on RISC-V ## Fedora RISC-V Support There is ongoing work to Fedora to support RISC-V hardwar…

2.4 网络安全新技术

数据参考&#xff1a;CISP官方 目录 云计算安全大数据安全移动互联网安全物联网安全工业互联网安全 一、云计算安全 1、云计算定义 云计算是指通过网络访问可扩展的、灵活的物理或虚拟共享资源池&#xff0c;并按需自助获取和管理资源的模式。在云计算中&#xff0c;计算资…

Goland搭建远程Linux开发

Windows和Linux都需要先构建好go环境&#xff0c;启用ssh服务。 打开Windows上的Goland&#xff0c;建立项目。 点击添加配置&#xff0c;选择go构建 点击运行于&#xff0c;选择ssh 填上Linux机器的IP地址和用户名 输入密码 没有问题 为了不让每次运行程序和调试程序都生…

AIGC自动生成内容真的好吗

一、前言 博主认为某些技术领域的发展对人类而言&#xff0c;并没有多大的益处。反而让人类更加困扰。纵观这几十年的技术发展&#xff0c;日新月异&#xff0c;但是人类生活的幸福指数并没有提高&#xff0c;反而产生了无数的社会问题。 AI大模型迅速发展&#xff0c;A…

【Spring专题】手写简易Spring容器过程分析

前置知识 《【Spring专题】Spring底层核心原理解析》 思路整理 我们在上一节《【Spring专题】Spring底层核心原理解析》课里面有简单分析过一个Spring容器的一般流程&#xff0c;所以&#xff0c;本节课我们这里尝试写一下简易的Spring容器。 手写源码示例 一、手写前的准…

FirmAE 模拟固件

一、介绍 FirmAE目标是创造一个可以供用户动态调试分析的环境&#xff0c;而不是复现和硬件一模一样的环境,&#xff0c;其卖点是大规模(Large-Scale)。 论文开头就直指“友商”Firmadyne&#xff0c;提出其模拟成功率低等问题&#xff0c; 其将原本Firmadyne的成功率16.28%提高…

Navicat 导出excel表数据结构

效果展示&#xff1a; 实现过程&#xff1a; 1 打开Navicat 执行以下SQL&#xff0c;注意&#xff1a;将以下SQL中的数据库名称和表名称替换。 SELECT COLUMN_NAME 列名, COLUMN_TYPE 数据类型, DATA_TYPE 字段类型, CHARACTER_MAXIMUM_LENGTH 长度, IS_NULLABLE 是否为空, C…

代码随想录算法学习心得 50 | 739.每日温度、496.下一个更大元素I...

一、每日温度 链接&#xff1a;力扣 描述如下&#xff1a;给定一个整数数组 temperatures &#xff0c;表示每天的温度&#xff0c;返回一个数组 answer &#xff0c;其中 answer[i] 是指对于第 i 天&#xff0c;下一个更高温度出现在几天后。如果气温在这之后都不会升高&…

pytest自动化测试框架之标记用例(指定执行、跳过用例、预期失败)

pytest中提供的mark模块&#xff0c;可以实现很多功能&#xff0c;如&#xff1a; 标记用例&#xff0c;即打标签skip、skipif标记跳过&#xff0c;skip跳过当前用例&#xff0c;skipif符合情况则跳过当前用例xfail标记为预期失败 标记用例 有时候我们可能并不需要执行项目中…

HTTP协议——应用层

HTTP协议 只要保证, 一端发送时构造的数据, 在另一端能够正确的进行解析, 就是ok的. 这种约定, 就是 应用层协议 HTTP简介 HTTP&#xff08;Hyper Text Transfer Protocol&#xff09;协议又叫做超文本传输协议&#xff0c;是一个简单的请求-响应协议&#xff0c;HTTP通常运行…

Spring-1-深入理解Spring XML中的依赖注入(DI):简化Java应用程序开发

学习目标 前两篇文章我们介绍了什么是Spring,以及Spring的一些核心概念&#xff0c;并且快速快发一个Spring项目&#xff0c;以及详细讲解IOC&#xff0c;今天详细介绍一些DI(依赖注入) 能够配置setter方式注入属性值 能够配置构造方式注入属性值 能够理解什么是自动装配 一、…

lwip不同的socket分别作为监听和客户端连接

在LWIP中&#xff0c;一个网络设备&#xff08;如以太网卡&#xff09;可以创建多个socket&#xff0c;用于处理不同的网络连接。一般&#xff0c;你可以创建一个socket用于监听&#xff08;listen&#xff09;连接&#xff0c;另一个socket用于主动发起&#xff08;connect&am…

本地化部署自建类ChatGPT服务远程访问

本地化部署自建类ChatGPT服务远程访问 文章目录 本地化部署自建类ChatGPT服务远程访问前言系统环境1. 安装Text generation web UI2.安装依赖3. 安装语言模型4. 启动5. 安装cpolar 内网穿透6. 创建公网地址7. 公网访问8. 固定公网地址 &#x1f340;小结&#x1f340; 前言 Te…

【ARM64 常见汇编指令学习 15 -- ARM 标志位的学习】

文章目录 ARM 标志位介绍Zero Condition flag(零标志位)零标志位判断实例 上篇文章&#xff1a;ARM64 常见汇编指令学习 14 – ARM 汇编 .balign,.balignw,.balign 伪指令学习 下篇文章&#xff1a;ARM64 常见汇编指令学习 16 – ARM64 SMC 指令 ARM 标志位介绍 在ARM架构中&am…

医学图像处理

医学图像处理 opencv批量分片高像素图像病理图像数据增强提升样本多样性基于 imgaug、skimage 实现色彩增强 降低样本多样性基于 DCGAN、TransposeConv 完成染色标准化 精细化提取特征自动生成数据标注 分类场景下的医学图像分析分割场景下的医学图像分析检测场景下的医学图像分…

揭秘Word高级技巧:事半功倍的文字处理策略

Microsoft Word是一款广泛使用的文字处理软件&#xff0c;几乎每个人都有使用过它的经历。但是&#xff0c;你是否知道Word中隐藏着许多高级技巧和功能&#xff0c;可以帮助你事半功倍地处理文字&#xff1f;在本文中&#xff0c;我们将揭秘一些Word的高级技巧&#xff0c;让你…

[] Adobe XD免费版功能一览,设计师们速来免费使用!

Adobe XD 作为一款流行的原型设计工具&#xff0c;免费使用对许多设计师来说是非常重要的。但现在 Adobe XD 的免费版体验已经不是那么舒适了。别担心&#xff0c;本文将为你推荐真正免费好用的 Adobe XD 替代工具。 Adobe XD 是免费的吗&#xff1f; Adobe XD 在早期确实是完…