Java多线程基础复习

文章目录

  • 多线程
    • 1.进程
      • 进程属性
      • 并发和并行
      • 虚拟地址空间
    • 2.线程
      • 概念
      • 线程的创建方式
    • 3.Thread类
      • 常见构造方法和属性
      • 线程的状态
      • 优先级
      • 后台线程
      • 线程是否存活
      • start和run
    • 4. 线程的一些基本操作
      • 线程中断(interrupted)
      • 线程等待join
      • currentThread(获取当前线程引用)
      • 线程休眠sleep


多线程

1.进程

进程是操作系统中非常核心的一个概念,进程也叫做“任务”,一个运行起来的程序就称为进程,像QQ安装后是一个存储在磁盘的一个可执行程序(静态的),当双击QQ运行的时候操作系统就会把文件中的核心数据加载到内存里,同时在系统中生成一个进程,同时给进程分配一定的系统硬件资源(CPU、内存、磁盘、网络带宽等…),在任务管理器中就可以查看到。

在这里插入图片描述

进程属性

同一时刻系统中运行的进程是有很多的,这么多的进程是如何被操作系统管理的呢?操作系统管理进程是通过描述+组织管理进程。

  1. 描述:详细的描述清楚一个进程有哪些属性/信息,操作系统通过一个PCB来描述一个进程。
    • PCB:也叫进程的控制块,它是C语言的一个结构体,一个结构体对象就对应着一个进程
    • 一个进程可能是一个PCB,也可能对应多个
  2. 组织:通过一定的数据结构,把若干个用来描述的实体,给放到一起,并且进行增删改查。
    • 系统中通常会使用双向链表这样的结构来把这些PCB给组织在一起
    • 创建一个进程,本质就是创建PCB,并且加入到链表上
    • 销毁一个进程,本质上就是从链表上删除对应的PCB节点
    • 查看任务管理器的进程列表,本质上就是在遍历这个链表

那么PCB里具体有哪些信息?(进程里面有哪些关键的要素)

  1. pid:进程的身份标识,一个机器这些进程的pid是唯一的,通过pid来区分一个进程

  2. 内存指针

    • 一个可执行文件,双击后开始在内存中运行,操作系统把文件中的核心数据(要执行的指令、指令依赖的数据)加载到内存中
    • 既然要创建进程,就要给进程分配内存空间,然后在这个内存空间上就有很多区域
    • 内存指针就是指向进程持有的内存资源,在程序关闭时也方便释放内存资源。
  3. 文件描述符表

    • 每个进程都可以打开一些文件(文件其实就是存在硬盘上的数据)
    • 文件描述符表里面就记录了当前进程都打开了哪些文件(打开了之后就可以后续针对这些文件进行读写操作了)

    下面的这些属性都是和进程调度相关的

  4. 进程状态

    • 运行状态:进程正在CPU上运行
    • 就绪状态:进程已经做好准备,随时准备被CPU调度执行
    • 阻塞状态:进程在此状态下不能执行,只有等阻塞该进程的事假完成之后才能执行
  5. 进程的优先级

    • 系统调度的时候,会根据优先级来给进程安排运行时间
    • 进程优先级越高就越容易被CPU调度执行
    • 创建进程的时候,可以通过一些系统调用来干预优先级
  6. 进程的上下文

    • 进程在CPU上执行了一会之后,要切换给别的进程,就需要保存当前运行的中间结果(类似存档),下次进程再被调度执行的时候,恢复到之前的中间结果(类似读档),继续往下执行
    • 对于进程来说,上下文就是CPU中的寄存器的值(寄存器的值就包含了运行的中间结果,需要把这这写结果保存到PCB的上下文信息中(内存))
    • 进程的上下文主要是存储调度出CPU之前,寄存器中的信息(把寄存器信息保存到内存中),等到这个进程下次恢复到CPU上执行的时候,就把内存中保存好的数据恢复到寄存器中
  7. 进程的记账信息

    • 记账信息主要是记录进程在CPU上执行多久了,用来辅助决定这个进程是继续执行,还是要被调度出CPU了
    • 通过进程记账信息就可以让进程运行更加均衡,避免有进程完全到不了CPU上执行

并发和并行

电脑上有着几百个进程都在运行,但是电脑只有1个CPU,而且一般都是4核或者8核心的CPU,是不足以运行这么进程的。操作系统就采用了进程调度这样的机制来进行执行的。

并发执行

并发执行是指一个CPU运行多个进程,一个CPU先运行进程1、再运行进程2…,这样调度执行,虽然CPU在一直进行切换,但是在电脑前坐着的使用者是感受不到这个过程的。

并行执行

并行执行是指多个CPU运行着多个进程,比如CPU1运行进程1,CPU2运行进程2,进程1和进2无论是从微观还是宏观都是同时执行的,

虚拟地址空间

一个进程想要运行,就需要给它分配一些系统资源,其中内存就是最核心的资源。

虚拟地址空间是指一个进程可用的地址空间,它是在进程被创建时由操作系统给出的,它是一种特殊的地址空间,它使得每个进程都可以访问自己的一块独立的内存空间,而不需要关心实际的物理地址。

MMU是计算机硬件中用于管理虚拟内存和物理内存之间映射的芯片,MMU通过将虚拟地址从CPU发出的程序地址装换为物理地址,来管理内存和提供进程之间的保护。

在这里插入图片描述

也就是我们访问的内存是虚拟内存而不是真实的物理内存,MMU会对我们的内存访问进行校验,判断是否越界访问,只有合法访问才能正常访问内存,如果越界就会MMU就会给操作系统发送异常信息。

通过虚拟地址空间,操作系统可以管理活跃的进程和内存,同时也提供了更好的保护机制,从而确保系统的安全性和可靠性。

由于进程之间相互隔离,进程间的通讯又是一个新的问题。可以使用文件或者socket等两个进程都可以访问的公共资源。

2.线程

概念

线程就是一个“执行流”,可以理解为线程是一个“轻量级进程”。虽然进程已经可以实现“并发编程”,但是频繁创建和销毁进程,开销还是比较大的,引入多线程是对多进程程序的优化。

  • 创建线程比创建进程更加高效
  • 销毁线程比销毁进程更加高效
  • 调度线程比调度进程更加高效
  • 同一个进程中的这些线程之间,共用同一份系统资源(内存+文件描述符表)

创建线程并没有向操作系统申请资源,销毁线程也不需要释放资源,线程是产生在进程内部,共用之前的资源。进程包含了线程,一个线程对应一个PCB,一个进程对应一组PCB(内存指针和文件描述符表,都是一份,但状态、优先级、记账信息、上下文、每个线程都有独立的)。进程是操作系统分配资源的基本单位,线程是调度执行的基本单位。

创建线程其实就是在内核里创建了PCB

线程的创建方式

1.继承Thread类重写run方法

public class ThreadDemo {
    static class MyThread extends Thread {
        @Override
        public void run() {
            System.out.println("继承Thread重写run方法");
        }
    }

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

2.实现Runnable接口,重写run方法

public class ThreadDemo {
    static class MyRunnable implements Runnable {
        @Override
        public void run() {
            System.out.println("实现Runnable接口重写run方法");
        }
    }
    public static void main(String[] args) {
        Thread thread = new Thread(new MyRunnable());
        thread.start();
    }
}

3.使用Thread匿名内部类

public static void main(String[] args) {
    Thread thread = new Thread() {
        @Override
        public void run() {
            System.out.println("使用匿名内部类");
        }
    };
    thread.start();
}

4.使用Runnable匿名内部类

public static void main(String[] args) {
    Thread thread1 = new Thread(new Runnable() {
        @Override
        public void run() {
            System.out.println("Runnable匿名内部类");
        }
    });
    thread1.start();

}

5.使用lambda表达式

public static void main(String[] args) {
    Thread thread2 = new Thread(()->{
        System.out.println("使用lambda表达式");
    });
    thread2.start();
}

6.使用Callable+FutureTak

  • Callable和Runnable类似都是描述了一个过程,只不过Callable带有有返回值。Callable的泛型参数就是返回值
  • Callable中包含call()方法,和Runnable的run()方法类似,不过call()方法是带有返回值的
  • 通过FutureTask的get()方法来获取Callable的返回值,如果此时还没有获取到返回值,该方法就会阻塞.
public static void main(String[] args) throws ExecutionException, InterruptedException {
        //这是一个能有返回值的线程,也是一个接口
        Callable<Integer> callable = new Callable<Integer>() {
            @Override
            public Integer call() throws Exception {
                //Thread.sleep(4000);
                int sum = 0;
                for (int i = 0; i <= 100000; i++) {
                    sum+=i;
                }
                return sum;
            }
        };
        //通过 FutureTask 来接收 Callable的返回值
        FutureTask<Integer> futureTask = new FutureTask<>(callable);
        Thread t = new Thread(futureTask);
        //调用t.start() 就会执行 FutureTask() 内部的 call 方法,完成计算,计算结果就会返回到 FutureTask对象中
        t.start();
        System.out.println("hhh");
        //调用FutureTask的 get 方法就能获取到结果
        //如果FutureTask没有接受到值就会阻塞等待
        int tmp = futureTask.get();
        System.out.println(tmp);
}

3.Thread类

常见构造方法和属性

方法说明
Thread()创建线程对象
Thread(Runnable target)使用 Runnable对象创建线程
Thread(String name)创建线程对象,并命名
Thread(Runnable target, String name)使用 Runnable 对象创建线程对象,并命名
Thread(ThreadGroup group,Runnable target)线程可以被用来分组管理,分好的组即为线程组

Thread常用方法

属性获取的方法
线程IdgetId()
名称getName()
状态getState()
优先级getPriority()
是否后台线程isDaemon()
是否存活isAlive()
是否被中断isInterrupted()
获取当前线程对象Thread.currentThread()

线程的状态

Java中线程的状态是和操作系统的状态不一样,这是Java自己的一套线程状态。Java中的线程状态其实主要是就绪状态和阻塞状态。

  • NEW:Thread对象创建出来来,但是内核的PCB还没有创建(还没有真正创建线程)
  • TERMINATED:内核的PCB销毁了,但是Thread对象还在
  • RUNNABlE:就绪状态(线程正在CPU上运行或者是在就绪队列中排队)
  • TIMED_WATING:安装一定的时间进行阻塞,sleep或者其它指定时间阻塞
  • WAITING:特殊的阻塞状态,调用wait时
  • BLOCKED:等待锁的时候进入的阻塞状态

优先级

优先级,也是和"进程的优先级”是类似的效果,此处的状态和优先级,和内核PCB中的状态优先级并不完全一致。

后台线程

关于后台线程(守护线程),我们创建的线程默认都是“前台线程”,前台线程会阻止进程退出,如果main运行完了,前台线程还没有执行完毕,进程是不会退出的。

如果是后台线程,后台线程是不阻止进程退出的,如果main等其他的前台线程执行完了,这个时候,即使后台线程没有执行完,进程也会退出。

线程是否存活

判断一个线程是否存活,最简单的方法就是看run方法是否已经结束。

public static void main(String[] args) {
    Thread thread = new Thread() {
        @Override
        public void run() {
            System.out.println("使用匿名内部类");
        }
    };
    thread.start();
    //下面还有一些逻辑
    //......
}

比如上述代码,run方法执行完毕后,其实线程就销毁了,但是由于Thread对象是靠JVM的GC来进行销毁的,所以它和内核的线程生命周期是不一样的,它会比内核的线程存活时间更长,所以此时就可以使用isAlive()方法来判断线程是否存活。

start和run

start()方法会在内核创建新的线程,也就是创建了新的PCB,此时代码就是多线程的方式执行,而如果直接调用的是run()方法,就并不会在内核创建新的线程,也就是说此时代码是串行执行,和多线程没有任何关系。

public static void main(String[] args) {
    Thread thread = new Thread() {
        @Override
        public void run() {
            System.out.println("使用匿名内部类");
        }
    };
}

4. 线程的一些基本操作

线程中断(interrupted)

如果一个线程的run方法执行完了,线程就已经结束了,但实际应用中可能是一没那么快结束,甚至可能是一个死循环,那么想让线程结束就需要用到线程中断了。

  1. 直接定义一个变量作为一个标记位判断线程是否结束(并不推荐)

    public class Interrupted {
        private static boolean FLAG = true;
    
        public static void main(String[] args) throws InterruptedException {
            Thread thread = new Thread(()->{
                while (FLAG) {
                    System.out.println("test");
                    try {
                        Thread.sleep(1000);
                    } catch (InterruptedException e) {
                        e.printStackTrace();
                    }
                }
            });
            thread.start();
            Thread.sleep(5000);
            FLAG = false;
        }
    }
    
  2. 使用标准库中的标记位

    方法说明
    public void interrupt()中断对象关联线程,如果线程正在阻塞,则以异常方式通知,否则设置标记位
    public static boolean interrupted()判断当前线程的中断标志位是否设置,调用后清除标志位(默认返回false)
    public boolean isInterrupted()判断对象关联的线程的标志位是否设置,调用后不清除标志位(默认返回false)

    代码示例:

    public static void demo2() throws InterruptedException {
        Thread thread = new Thread(()->{
            while (!Thread.interrupted()) {
                try {
                    Thread.sleep(1000);
                } catch (InterruptedException e) {
                    e.printStackTrace();
                }
                System.out.println("test");
            }
        });
        thread.start();
        Thread.sleep(5000);
        thread.interrupt();
    }
    

    interrupt() 方法本来是会把isInterrupted()的标志位修改为ture的,但这段代码在阻塞就会抛出一个异常,且线程不会停止循环继续运行。

    这里的 interrupt 方法有两种行为

    1.如果当前线程正在运行中,此时就会修改 Thread.islnterruppted() 标记位为 true

    2.如果当前线程 正在 sleep、wait、等待锁,此时就会触发 InterruptedException

    如果要结束循环在catch加上brak即可

    public static void demo2() throws InterruptedException {
        Thread thread = new Thread(()->{
            while (!Thread.currentThread().isInterrupted()) {
                try {
                    Thread.sleep(1000);
                } catch (InterruptedException e) {
                    e.printStackTrace();
                    break;
                }
                System.out.println("test");
            }
        });
        thread.start();
        Thread.sleep(5000);
        thread.interrupt();
    }
    

    还有一个静态的方法interrupted()也是标志位

    public static void  demo3() throws InterruptedException {
            Thread thread = new Thread(()->{
                while (!Thread.interrupted()) {
                    try {
                        Thread.sleep(1000);
                    } catch (InterruptedException e) {
                        e.printStackTrace();
                        break;
                    }
                    System.out.println("test");
                }
            });
            thread.start();
            Thread.sleep(5000);
            thread.interrupt();
        }
    

那么interrupted()isInterrupted()方法有什么区别呢?

列如:调用 interrupt() 方法,把标记位设为 true,就应该结束循环

  • 当调用 静态的 interrupted 来判定标记位的时候,就会返回 true,同时就会把标记位再改回 false,下次再调用interrupted() 就返回 false
  • 如果是调用非静态的 isInterrupted() 来判断标记位,也会返回 true,但不会对标记位进行修改,后面再调用isInterrupted() 的时候仍然返回 true

线程等待join

线程之间的调度顺序,是不确定的。可以通过一些特殊的操作,来对线程的执行顺序,做出干预。其中

join就是一个办法,控制线程之间的结束顺序。

比如这里在main方法里调用join的效果就是等thread线程的代码执行完毕后才继续执行main方法里的逻辑,此时main方法的线程就进入阻塞状态,不参与cpu调度。

当然根据需要join可以设置指定时间的等待,正常使用一般不会死等的。

public static void main(String[] args) throws InterruptedException {
        Thread thread = new Thread(()->{
            for (int i = 0; i < 5; i++) {
                System.out.println("线程执行中...");
                try {
                    Thread.sleep(500);
                } catch (InterruptedException e) {
                    e.printStackTrace();
                }
            }
        });
        thread.start();
        thread.join();
        System.out.println("线程执行结束");
    }

currentThread(获取当前线程引用)

currentThread 能够获取到当前线程对应的 Thread 实例的引用,相当于 this关键字

public static void demo() {
    Thread thread = new Thread(){
        @Override
        public void run() {
            System.out.println(this.getId());
            System.out.println(Thread.currentThread().getId());
        }
    };
}

但是需要注意的是,如果是使用 Runnable 或者 lambda 的方式来创建的线程,就无法使用 this 了。
this指向的是 Runnable 实例,而不是Thread 实例了,此时也就没有 getId 方法了。

public static void demo1() {
    Thread thread = new Thread(new Runnable() {
        @Override
        public void run() {
            System.out.println(Thread.currentThread().getId());
            //System.out.println(this.getId); 错误写法
        }
    });
}

线程休眠sleep

也是我们比较熟悉一组方法,有一点要记得,因为线程的调度是不可控的,所以,这个方法只能保证实际休眠时间是大于等于参数设置的休眠时间的
通过 sleep() 方法来休眠一个线程,sleep() 是一个类方法

public static void main(String[] args) {
    Thread t = new Thread(new Runnable() {
        @Override
        public void run() {
            try {
                //休眠1秒
                Thread.sleep(1000);
            } catch (InterruptedException e) {
                e.printStackTrace();
            }
        }
    });
}

Sleep 这个方法,本质上就是把线程PCB给从就绪队列,移动到了阻塞队列,只有当 Sleep时间到了或者抛出异常了才会回到就绪队列中

在这里插入图片描述


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

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

相关文章

pytest常用执行参数详解

1. 查看pytest所有可用参数 我们可以通过pytest -h来查看所有可用参数。 从图中可以看出&#xff0c;pytest的参数有很多&#xff0c;下面是归纳一些常用的参数&#xff1a; -s&#xff1a;输出调试信息&#xff0c;包括print打印的信息。-v&#xff1a;显示更详细的信息。…

hdu7298 Coin(网络流+按时间拆点)

题目 t(t<10)组样例&#xff0c;每次给n(n<3e3)个人&#xff0c; 第i个人&#xff0c;在任意时刻&#xff0c;都最多只能有ai(1<ai<3e3)个硬币 其中k(k<n)个是小F的朋友&#xff0c;依次用点号的形式给出 初始时&#xff0c;每个人都有一个硬币&#xff0c;…

opencv 图像腐蚀膨胀 erode dilate

#include "iostream" #include "opencv2/opencv.hpp" using namespace std; using namespace cv;int main() {Mat img, dst, dstbin, distancetransform,rel, rel2;img imread("m3.jpg");//转为灰度图cvtColor(img, dst, COLOR_BGR2GRAY);//二…

(css)自定义登录弹窗页面

(css)自定义登录弹窗页面 效果&#xff1a; 代码&#xff1a; <!-- 登录弹窗 --> <el-dialog:visible.sync"dialogVisible"title"用户登录"width"25%"centerclass"custom-dialog":show-close"false":close-on-cli…

动态规划入门第2课,经典DP问题1 --- 线性

动态规划要点 阶段的2个方向&#xff1a;从上到下&#xff1b;从下到上。 动态规划要点 从递归到DP 动态规划要点 两个2个方向 优化的可能性 第1题 合唱队形 N位同学站成一排&#xff0c;音乐老师要请其中的(N-K)位同学出列&#xff0c;使得剩下的K位同学排成合唱队形…

macOS coreAudio 之 AudioQueue 播放本地音频文件

macOS的音频模块使用还是和 iOS有细微差别的。 今天记录是的是 使用 AudioQueue 配合 AudioFile 进行播放macOS 本地音频文件 本文打仓库代码为&#xff1a; JBPlayLocalMusicFile.m CoreAudio 作为Apple音频系统中音频库的集合&#xff0c;今天需要使用到的库为&#xff1a…

Spring Cloud Gateway - 新一代微服务API网关

Spring Cloud Gateway - 新一代微服务API网关 文章目录 Spring Cloud Gateway - 新一代微服务API网关1.网关介绍2.Spring Cloud Gateway介绍3.Spring Cloud Gateway的特性4.Spring Cloud Gateway的三大核心概念5.Gateway工作流程6.Gateway核心配置7.动态路由8.Predicate自定义P…

【腾讯云 Cloud Studio 实战训练营】使用Cloud Studio制作蛋仔派对兑换码工具

目录 &#x1f373;前言&#x1f373;实验介绍&#x1f373;产品介绍&#x1f373;抓包分析&#x1f603;登录分析&#x1f603;&#x1f603;第一步&#xff0c;获取验证码&#x1f603;&#x1f603;第二步&#xff0c;保存验证码&#x1f603;&#x1f603;第三步&#xff0…

山西电力市场日前价格预测【2023-07-24】

日前价格预测 预测明日&#xff08;2023-07-24&#xff09;山西电力市场全天平均日前电价为338.25元/MWh。其中&#xff0c;最高日前电价为377.59元/MWh&#xff0c;预计出现在20: 30。最低日前电价为283.56元/MWh&#xff0c;预计出现在13: 30。 价差方向预测 1&#xff1a;实…

LiveNVR监控流媒体Onvif/RTSP功能-支持无人机、IPC等设备RTMP推流转码分发H5无插件播放也支持GB28181输出

LiveNVR支持无人机、IPC等设备RTMP推流转码分发H5无插件播放也支持GB28181输出 1、无人机推流转国标2、获取RTMP推流地址2.1、RTMP推流地址格式2.2、推流地址示例 2、设备RTMP推流3、配置拉转RTMP3.1、直播流地址格式3.2、直播流地地址示例3.3、通道配置直播流地址 4、配置级联…

精准测试之分布式调用链底层逻辑

目录 前言&#xff1a; ⼀、分布式调⽤链系统概述 分布式架构所带来的问题 分布式链路监控的作用 ⼆、调用链系统的演进 链路监控系统列表 三、调用链系统的底层实现逻辑 调用链系统的本质 调用链基本元素 事件捕捉 事件串联 事件的开始与结束 上传 四、Span 内容…

Stable Diffusion如何生成高质量的图-prompt写法介绍

文章目录 Stable Diffusion使用尝试下效果prompt的编写技巧prompt 和 negative promptPrompt格式Prompt规则细节优化Guidance Scale 总结 Stable Diffusion Stable Diffusion是一个开源的图像生成AI系统,由Anthropic公司开发。它基于 Transformer模型架构,可以通过文字描述生成…

WAIC2023:图像内容安全黑科技助力可信AI发展

目录 0 写在前面1 AI图像篡改检测2 生成式图像鉴别2.1 主干特征提取通道2.2 注意力模块2.3 纹理增强模块 3 OCR对抗攻击4 助力可信AI向善发展总结 0 写在前面 2023世界人工智能大会(WAIC)已圆满结束&#xff0c;恰逢全球大模型和生成式人工智能蓬勃兴起之时&#xff0c;今年参…

Two Days wpf 分享 分页组件

迟来的wpf分享。 目录 一、序言 二、前期准备 三、前端界面 四、后台代码部分 1、先定义些变量后面使用 2、先是按钮事件代码。 首页按钮 上一页按钮 下一页按钮 末尾按钮 画每页显示等数据 每页显示多少条 判断是否为数字的事件 分页数字的点击触发事件 跳转到…

jmeter常用的提取器(正则表达式和JSON提取器)

jmeter常用的后置处理器有两种提取数据&#xff1a; 1、JSON提取器 获取后可以将变量token引用到其他所需要的地方 &#xff08;正则表达式和JSON提取器&#xff09;:2023接口自动化测试框架必会两大神器:正则提取器和Jsonpath提取器_哔哩哔哩_bilibilihttps://www.bilibili.…

JVM运行时数据区——堆内的区域分布

1.堆内的区域分布 堆是运行时数据区最大的一块区域&#xff0c;主要用来存放对象&#xff0c;堆是所有线程公用的&#xff0c;在JVM启动时就被创建&#xff0c;堆的空间是可以调整的&#xff0c;是GC(垃圾回收)的重点区域。 堆的内存空间分区&#xff1a;新生代老年代 新生代…

Rust vs Go:常用语法对比(三)

题图来自When to use Rust and when to use Go[1] 41. Reverse a string 反转字符串 package mainimport "fmt"func Reverse(s string) string { runes : []rune(s) for i, j : 0, len(runes)-1; i < j; i, j i1, j-1 { runes[i], runes[j] runes[j], runes[i]…

【SQL应知应会】表分区(五)• MySQL版

欢迎来到爱书不爱输的程序猿的博客, 本博客致力于知识分享&#xff0c;与更多的人进行学习交流 本文收录于SQL应知应会专栏,本专栏主要用于记录对于数据库的一些学习&#xff0c;有基础也有进阶&#xff0c;有MySQL也有Oracle 分区表 • MySQL版 前言一、分区表1.非分区表2.分区…

【论文】基于GANs的图像文字擦除 ——2010.EraseNet: End-to-End Text Removal in the Wild(已开源)

pytorch官方代码&#xff1a;https://github.com/lcy0604/EraseNet 论文&#xff1a;2010.EraseNet: End-to-End Text Removal in the Wild 网盘提取码&#xff1a;0719 一、图片文字去除效果 图10 SCUT-EnsText 真实数据集的去除 第一列原图带文字、第二列为去除后的标签&a…

RocketMQ分布式事务 -> 最终一致性实现

文章目录 前言事务消息场景代码示例订单服务事务日志表TransactionMQProducerOrderTransactionListener业务实现类调用总结 积分服务积分记录表消费者启动消费者监听器增加积分幂等性消费消费异常 前言 分布式事务的问题常在业务与面试中被提及, 近日摸鱼看到这篇文章, 阐述的…
最新文章