Java并发总结之Java内存模型

2023-11-10

本文主要参考《深入理解Java虚拟机》和《Java并发编程的艺术》对Java内存模型进行简单总结。

一、CPU和缓存一致性

1.CPU高速缓存

为了解决CPU处理速度和内存处理速度不对等的问题,就是在CPU和内存之间增加高速缓存。当程序在运行过程中,会将运算需要的数据从主存复制一份到CPU的高速缓存当中,那么CPU进行计算时就可以直接从它的高速缓存读取数据和向其中写入数据,当运算结束之后,再将高速缓存中的数据刷新到主存当中。

而随着CPU处理能力的不断提升,一层缓存渐渐地无法满足要求了,就衍生出多级缓存。按照数据读取顺序和与CPU结合的紧密程度,CPU缓存可以分为一级缓存(L1),二级缓存(L3),部分高端CPU还具有三级缓存(L3),每一级缓存中所储存的全部数据都是下一级缓存的一部分。这三种缓存的技术难度和制造成本是相对递减的,所以其容量也是相对递增的。在有了多级缓存之后,程序的执行就变成了,当CPU要读取一个数据时,首先从一级缓存中查找,如果没有找到再从二级缓存中查找,如果还是没有就从三级缓存或内存中查找。

单核CPU只含有一套L1,L2,L3缓存,而多核CPU每个核心都含有一套L1,甚至L2缓存,共享L3或者L3和L2缓存。通常单CPU双核的缓存结构如下:
1

2.缓存一致性

在多处理器系统中,每个处理器都有自己的高速缓存,它们共享同一主存(Main Memory),当多个处理器的运算任务都涉及同一块主内存区域时,将可能导致各自的缓存数据不一致,要解决缓存一致性问题,需要各个处理器访问缓存时都遵循缓存一致性协议。

二、Java内存模型(Java Memory Model,JMM)

Java内存模型是由Java虚拟机规范定义的一种符合内存模型规范的,屏蔽了各种硬件和操作系统的访问差异的,实现了Java程序在各种平台下都能达到一致的内存访问效果的规范。从JDK5开始,Java使用新的JSR-133内存模型。

1.主内存与工作内存

Java内存模型的主要目标是定义程序中各个变量的访问规则,即在虚拟机中将变量存储到内存和从内存中取出变量这样底层细节。此处的变量与Java编程时所说的变量不一样,指包括了实例域、静态域和构成数组对象的元素,但是不包括局部变量与方法参数,后者是线程私有的,不会被共享,自然就不会出现竞争问题。

JMM定义了线程和主内存之间的抽象关系:所有变量都存储在主内存(Main Memory)中,每个线程都有一个私有的工作内存(Working Memory),工作内存中存储了被该线程使用到的存储在主内存中的变量的副本拷贝,线程对变量的所有操作都必须在工作内存中进行,不能直接读写主内存中的变量。不同的线程之间无法直接访问对方工作内存中的变量,线程间变量值的传递均需要通过主内存完成。线程间共享变量值的传递过程

  1. 线程1把工作内存1中更新过的变量刷新到主内存中去。
  2. 线程2到主内存中去读取线程1之前已更新过的变量更新到工作内存2中去。

JMM通过控制主内存与每个线程的工作内存之间的交互来提供内存可见性保证。Java内存模型图如下:
2

2.内存间交互操作

主内存与工作内存之间的具体交互协议,即一个变量如何从主内存拷贝到工作内存、如何从工作内存同步到主内存之间的实现细节。Java内存模型定义了以下八种操作来完成,虚拟机实现时必须保证下面提及的每种操作都是原子的、不可再分的(对于double和long类型的变量,load、store、read、write操作在某些平台有例外):

  • lock(锁定):作用于主内存的变量,它把一个变量标识为一条线程独占的状态。
  • unlock(解锁):作用于主内存的变量,它把一个处于锁定状态的变量释放出来,释放后的变量才可以被其他线程锁定。
  • read(读取):作用于主内存的变量,它把一个变量值从主内存传输到线程的工作内存中,以便随后的load动作使用。
  • load(载入):作用于工作内存的变量,它把read操作从主内存中得到的变量值放入工作内存的变量副本中。
  • use(使用):作用于工作内存的变量,它把工作内存中一个变量的值传递给执行引擎,每当虚拟机遇到一个需要使用变量的值的字节码指令时将会执行这个操作。
  • assign(赋值):作用于工作内存的变量,它把一个从执行引擎接收到的值赋给工作内存的变量,每当虚拟机遇到一个给变量赋值的字节码指令时执行这个操作。
  • store(存储):作用于工作内存的变量,它把工作内存中一个变量的值传送到主内存中,以便随后的write操作使用。
  • write(写入):作用于主内存的变量,它把store操作从工作内存中得到的变量的值放入主内存的变量中。

如果要把一个变量从主内存中复制到工作内存,那就要顺寻地执行read和load操作,如果把变量从工作内存同步回主内存,就要顺序地执行store和write操作。Java内存模型只要求上述两个操作必须按顺序执行,而没有保证必须是连续执行。也就是read和load之间,store和write之间可以插入其他指令。

Java内存模型还规定了在执行上述八种基本操作时,必须满足如下规则

  • 不允许read和load、store和write操作之一单独出现。
  • 不允许一个线程丢弃它的最近assign操作,即变量在工作内存中改变了之后必须把该变化同步到主内存中。
  • 不允许一个线程无原因地(没有发生过任何assign操作)把数据从线程的工作内存同步回主内存中。
  • 一个新的变量只能在主内存中诞生,不允许在工作内存中直接使用一个未被初始化(load或assign)的变量。即就是对一个变量实施use和store操作之前,必须先执行过了assign和load操作。
  • 一个变量在同一时刻只允许一条线程对其进行lock操作,lock和unlock必须成对出现。
  • 如果对一个变量执行lock操作,将会清空工作内存中此变量的值,在执行引擎使用这个变量前需要重新执行load或assign操作初始化变量的值。
  • 如果一个变量事先没有被lock操作锁定,则不允许对它执行unlock操作;也不允许去unlock一个被其他线程锁定的变量。
  • 对一个变量执行unlock操作之前,必须先把此变量同步到主内存中(执行store和write操作)。

这8种内存访问操作很繁琐,后文会使用一个等效判断原则,即先行发生(happens-before)原则来确定一个内存访问在并发环境下是否安全。

3.对于long和double类型变量的特殊规则

Java内存模型要求lock、unlock、read、load、assign、use、store、write这8个操作都具有原子性,但是对于64位的数据类型(long和double),在模型中特别定义了一条相对宽松的规定:允许虚拟机将没有被volatile修饰的64位数据的读写操作划分为两次32位的操作来进行,即允许虚拟机实现选择可以不保证64位数据类型的load、 store、read和write这4个操作的原子性,这点就是所谓的long和double的非原子性协定(Nonatomic Treatment of double and long Variables)。

如果在多线程环境中共享一个没有被volatile修饰的可变long或double类型的变量时,并且对该变量的读操作和写操作在不同的线程中执行,那么很可能会读取到某个值的高32位和另一个值的低32位。在多线程程序中使用共享且可变的long和double等类型的变量也是不安全的,除非用关键字volatile来声明它们,或者用锁保护起来。

4.原子性、可见性与有序性

原子性(Atomicity):由Java内存模型来直接保证的原子性变量操作包括read、load、assign、use、store 和write,我们大致可以认为对long和double类型以外基本数据类型的访问读写是具备原子性的。

如果应用场景需要一个更大范围的原子性保证,Java内存模型还提供了lock和unlock操作来满足这种需求,尽管虚拟机未把lock和unlock操作直接开放给用户使用,但是却提供了更高层次的字节码指令monitorentermonitorexit来隐式地使用这两个操作,这两个字节码指令反映到Java代码中就是synchronized关键字,因此在synchronized块之间的操作也具备原子性。

在Java语言中,long和double类型以外的任何类型的变量的写操作都是原子操作。所以对于基本类型和引用类型的写操作(不是指初始化)都是本身具有原子性的。

可见性(Visibility):可见性是指一个线程对共享变量值的修改,能够及时地被其他线程看到。Java内存模型是通过在一个线程在变量修改后将新值同步回主内存,另一个线程在变量被读取前从主内存读后刷新到工作内存这种依赖主内存作为传递媒介的方式来实现可见性的,无论是普通变量还是volatile变量都是如此,普通变量与volatile变量的区别是,volatile的特殊规则保证了新值能立即同步到主内存,以及每次使用前立即从主内存刷新。因此,可以说volatile保证了多线程操作时变量的可见性,而普通变量则不能保证这一点。

除了volatile变量外,synchronizedfinal也可以实现内存可见性。synchronized的可见性是由对一个变量执行unlock操作之前,必须把变量值同步回主内存实现的,final的可见性是指被final关键字修饰的字段在构造器中一旦初始化完成,并且没有发生this逃逸(其它线程通过this引用访问到初始化了一半的对象),那么其它线程就能看见final字段的值。

共享变量:如果一个变量在多个线程的工作内存中都存在副本,那么这个变量就是这几个线程的共享变量。(共享意味着变量可以被多个线程访问)

有序性(Ordering):如果在本线程内观察,所有的操作都是有序的;如果在一个线程中观察另一个线程,所有的操作都是无序的。前半句是指“线程内表现为串行的语义”(Within-Thread As-If-Serial Semantics),后半句是指“指令重排序”现象和“工作内存与主内存同步延迟”现象。

Java语言提供了volatilesynchronized来保证线程之间操作的有序性,volatile关键字通过添加内存屏障的方式来禁止指令重排,即重排序时不能把后面的指令放到内存屏障之前,而synchronized则是由“一个变量在同一个时刻只允许一条线程对其进行lock操作”这条规则决定了持有同一个锁的同步块只能串行地进入。

5.先行发生原则(happens-before)

先行发生原则是Java内存模型中定义的两项操作之间的偏序关系,想要保证一个操作执行的结果对另一个操作可见(无论这两个操作是否在同一个线程中执行),那么这两个操作之间必须满足happens-before关系。如果两个操作之间缺乏happens-before关系,那么JVM可以对它们任意的重排序。

happens-before原则定义如下

  1. 如果一个操作happens-before于另一个操作,那么第一个操作的执行结果将对第二个操作可见,而且第一个操作的执行顺序排在第二个操作之前。
  2. 两个操作之间存在happens-before关系,并不意味着一定要按照happens-before关系指定的顺序来执行。如果重排序之后的执行结果与按照happens-before关系来执行的结果一致,并不影响先行发生原则的正确性,那么这种重排序并不非法,也就是说JMM允许这种重排序。

注意:

  • 1是JMM对程序员的承诺,若A happens-before B,则A的结果对B可见并且A的执行顺序在B之前,这只是JMM对程序员的保证。
  • 2是JMM对编译器和处理器重排序的约束规则,JMM遵循只要不改变程序执行结果(指单线程和正确同步的多线程程序),编译器可以随意优化,而程序员也不关心这两个操作是否被重排序,程序员只关心程序执行时的语义不能被改变,即执行结果不能变,所以happens-before和as-if-serial语义本质上一样。
  • as-if-serial语义保证单线程内程序的执行结果不被改变,happens-before关系保证正确同步的多线程程序的执行结果不被改变。
  • as-if-serial语义给编写单线程程序的程序员创造了一个幻境:单线程程序是按程序的顺序来执行的。happens-before关系给编写正确同步的多线程程序的程序员创造了一个幻境:正确同步的多线程程序是按happens-before指定的顺序来执行的。
  • as-if-serial语义和happens-before这么做的目的,都是为了在不改变程序执行结果的前提下,尽可能地提高程序执行的并行度。

Java内存模型中的先行发生关系

  • 程序次序规则(Program Order Rule):在一个线程内,按照程序代码顺序,书写在前面的操作先行发生于书写在后面的操作。准确地说,应该是控制流顺序而不是程序代码顺序,因为要考虑分支、循环等结构。
  • 管程锁定规则(Monitor Lock Rule):一个unlock操作先行发生于后面对同一个锁的lock操作。 这里必须强调的是同一个锁,而“后面”是指时间上的先后顺序。
  • volatile变量规则(Volatile Variable Rule):对一个volatile变量的写操作先行发生于后面对这个变量的读操作,这里的“后面”同样是指时间上的先后顺序。
  • 线程启动规则(Thread Start Rule):Thread对象的start()方法先行发生于此线程的每一个动作。
  • 线程终止规则(Thread Termination Rule):线程中的所有操作都先行发生于对此线程的终止检测,我们可以通过Thread.join()方法结束、Thread.isAlive()的返回值等手段检测到线程已经终止执行。
  • 线程中断规则(Thread Interruption Rule):对线程interrupt()方法的调用先行发生于被中断线程的代码检测到中断事件的发生,可以通过Thread.interrupted()方法检测到是否有中断发生。
  • 对象终结规则(Finalizer Rule):一个对象的初始化完成(构造函数执行结束)先行发生于它的finalize()方法的开始。
  • 传递性(Transitivity):如果操作A先行发生于操作B,操作B先行发生于操作C,可以得出操作A先行发生于操作C的结论。

注意:

  • 这些先行发生关系无须任何同步器协助就已经存在,可以在编码中直接使用。如果两个操作之间的关系不在此列,并且无法从这些规则推导,它们就缺乏happens-before关系,没有顺序性保障,JVM可以对它们随意地进行重排序。
  • 时间先后顺序与先行发生原则之间基本没有太大的关系,所以我们衡量并发安全问题的时候不要受到时间顺序的干扰,一切必须以先行发生原则为准。

三、重排序

编译器和处理器为了优化程序性能而对指令序列进行重新排序的一种手段。重排序分3种类型

  • 编译器优化重排序(编译器重排序):编译器在不改变单线程程序语义的前提下,可以重新安排语句的执行顺序。
  • 指令级并行重排序(处理器重排序):现代处理器采用了指令级并行技术(Instruction-Level Parallelism,ILP)来将多条指令重叠执行。如果不存在数据依赖性,处理器可以改变语句对应机器指令的执行顺序。
  • 内存系统重排序(处理器重排序):由于处理器使用缓存和读/写缓冲区,这使得加载和存储操作看上去可能是在乱序执行。

从Java源代码到最终实际执行的指令序列,会分别经历下面3种重排序:

源代码 -> 编译器优化重排序 -> 指令级并行重排序 -> 内存系统重排序 -> 最终执行的指令序列

1.处理器重排序

现代的处理器使用写缓冲区来临时保存向内存写入的数据。写缓冲区可以保证指令流水线持续运行,它可以避免由于处理器停顿下来等待向内存写入数据而产生的延迟。同时,通过以批处理的方式刷新写缓冲区,以及合并写缓冲区中对同一内存地址的多次写,可以减少对内存总线的占用。虽然写缓冲区有这么多好处,但每个处理器上的写缓冲区,仅仅对它所在的处理器可见。这个特性会对内存操作的执行顺序产生重要的影响:处理器对内存的读/写操作的执行顺序,不一定与内存实际发生的读/写操作顺序一致。由于现代的处理器都会使用写缓冲区,因此现代的处理器都会允许对写-读操作重排序。常见处理器允许的重排序类型的列表

Load-Load Load-Store Store-Store Store-Load 数据依赖
sparc-TSO N N N Y N
x86 N N N Y N
ia64 Y Y Y Y N
PowerPC Y Y Y Y N
  • sparc-TSO是指以TSO(Total Store Order)内存模型运行时,sparc处理器的特性。
  • x86包括x64及AMD64。
  • 由于ARM处理器的内存模型与PowerPC处理器的内存模型非常类似,本文将忽略它。

2.内存屏障

为了保证内存可见性,java编译器在生成指令序列的适当位置会插入内存屏障指令来禁止特定类型的处理器重排序。JMM对内存屏障指令的分类

屏障类型 指令示例 说明
LoadLoad Barriers Load1; LoadLoad; Load2 确保Load1数据的装载,之前于Load2及所有后续装载指令的装载。
StoreStore Barriers Store1; StoreStore; Store2 确保Store1数据对其他处理器可见(刷新到内存),之前于Store2及所有后续存储指令的存储。
LoadStore Barriers Load1; LoadStore; Store2 确保Load1数据装载,之前于Store2及所有后续的存储指令刷新到内存。
StoreLoad Barriers Store1; StoreLoad; Load2 确保Store1数据对其他处理器变得可见(指刷新到内存),之前于Load2及所有后续装载指令的装载。StoreLoad Barriers会使该屏障之前的所有内存访问指令(存储和装载指令)完成之后,才执行该屏障之后的内存访问指令。

StoreLoad Barriers是一个“全能型”的屏障,它同时具有其他三个屏障的效果。现代的多处理器大都支持该屏障(其他类型的屏障不一定被所有处理器支持)。执行该屏障开销会很昂贵,因为当前处理器通常要把写缓冲区中的数据全部刷新到内存中。

3.数据依赖性

如果两个操作访问同一个变量,且这两个操作中有一个为写操作,此时这两个操作之间就存在数据依赖性。数据依赖分三种类型

名称 代码示例 说明
写后读 a = 1; b = a; 写一个变量之后,再读这个位置
写后写 a = 1; a = 2; 写一个变量之后,再写这个变量
读后写 a = b; b = 1; 读一个变量之后,再写这个变量

上面三种情况,只要重排序两个操作的执行顺序,程序的执行结果将会被改变。编译器和处理器可能会对操作做重排序。编译器和处理器在重排序时,会遵守数据依赖性,编译器和处理器不会改变存在数据依赖关系的两个操作的执行顺序。

注意,这里所说的数据依赖性仅针对单个处理器中执行的指令序列和单个线程中执行的操作,不同处理器之间和不同线程之间的数据依赖性不被编译器和处理器考虑。

4.as-if-serial语义

as-if-serial语义:编译器和处理器为了提高并行度,不管怎么重排序,单线程程序的执行结果不能被改变。编译器、runtime和处理器都必须遵守as-if-serial语义。

为了遵守as-if-serial语义,编译器和处理器不会对存在数据依赖关系的操作做重排序,因为这种重排序会改变执行结果。但是,如果操作之间不存在数据依赖关系,这些操作可能被编译器和处理器重排序。例如以下示例:

double pi = 3.14; // A
double r = 1.0; // B
double area = pi * r * r; // C

示例的数据依赖关系如下:
3
根据图中的依赖关系可知,在最终的指令序列中,C不能重排序在A和B之前,但A和B之间没有依赖关系,编译器和处理器可以重排序A和B之间的执行顺序。下图是该示例的两种执行顺序:
4
as-if-serial语义把单线程程序保护了起来,遵守as-if-serial语义的编译器,runtime和处理器共同为编写单线程程序的程序员创建了一个幻觉:单线程程序是按程序的顺序来执行的。as-if-serial语义使单线程程序员无需担心重排序会干扰他们,也无需担心内存可见性问题,即读线程不一定能看到写线程写入的值。

5.重排序对多线程的影响

在单线程程序中,对存在控制依赖的操作重排序,不会改变执行结果(这也是 as-if-serial语义允许对存在控制依赖的操作做重排序的原因);但在多线程程序中重排序可能会破坏多线程程序的语义,改变程序的执行结果,产生内存可见性问题。例如以下代码:

class ReorderExample {
    int a = 0; 
    boolean flag = false;
    public void writer() {
        a = 1;                   // 1
        flag = true;             // 2
    }
    Public void reader() {
        if (flag) {             // 3
            int i = a * a;      // 4
	        .....
	    }
    }    
}

示例中flag用来标识a是否被写入,假设有两个线程A和B,A执行writer()方法,B执行reader()方法,由于操作1和2,操作3和4之间没有数据依赖性,因此操作1和2,3和4之间可以重排序,如果操作1和2发生重排序,可能会出现以下执行结果:
5
线程A先执行了操作2写入了变量flag,线程B执行操作3,由于条件为真,线程B将执行操作4,会读取变量a,但此时变量a还未被线程A写入。如果操作3和4发生重排序,可能会出现以下执行结果:
6
操作3和4之间存在控制依赖关系,会影响指令序列执行的并行度,编译器和处理器会采取猜测执行的方式克服控制相关性对并行度的影响。以上示例线程B可以提前读取变量a并将计算结果临时保存到重排序缓冲中,当操作3条件为真时,在将结果写入变量i中。根据以上两种情况的结果来看,重排序破坏了多线程程序的语义。

本文内容由网友自发贡献,版权归原作者所有,本站不承担相应法律责任。如您发现有涉嫌抄袭侵权的内容,请联系:hwhale#tublm.com(使用前将#替换为@)

Java并发总结之Java内存模型 的相关文章

随机推荐

  • c++获取当前时间戳,单位是毫秒

    你可以使用 time h 中的 time 函数来获取当前的时间戳 它的返回值是从 1970 年 1 月 1 日 00 00 00 UTC 到现在的时间 以秒为单位 如果你需要以毫秒为单位的时间戳 你可以使用 time 函数的返回值除以 10
  • CentOS 8 官方正式发布了!

    CentOS 8 官方正式发布了 CentOS 完全遵守 Red Hat 的再发行政策 并且致力与上游产品在功能上完全兼容 CentOS 对组件的修改主要是去除 Red Hat 的商标及美工图 该版本还包含全新的 RHEL upstream
  • 数字序列的最大间隔(harsh)

    题目描述 题目描述 请输出数字序列的最大间隔 请使用以下伪随机数生成函数 rand32 生成伪随机数 int seed int rand return seed seed 214013L 2531011L gt gt 16 0x7fff i
  • 《积累》键盘keycode对照表

    字母和数字键的键码值 keyCode 按键 键码 按键 键码 按键 键码 按键 键码 A 65 J 74 S 83 1 49 B 66 K 75 T 84 2 50 C 67 L 76 U 85 3 51 D 68 M 77 V 86 4
  • Mybatis学习笔记6 模糊查询like

    1 模糊 like 模糊查询的实现有两种方式 一是java代码中给查询数据加上 二是在mapper文件sql语句的条件位置加上 需求 查询姓名有 王 的 1 1 java代码中提供要查询的 王 接口方法 List
  • Linux云计算薪资及发展前景,云计算Linux就业方向及前景分析 2019云计算行业发展现状及前景趋势分析...

    云计算 cloud computing 是一种基于因特网的超级计算模式 在远程的数据中心里 成千上万台电脑和服务器连接成一片电脑云 那么 今天我们就来说说云计算就业形势方向及前景和云计算行业发展现状及前景分析 云计算是未来的趋势 有了云平台
  • 迪杰斯特拉(Dijkstra)算法 Java实现(最短路径)

    基本思想 通过Dijkstra计算图G中的最短路径时 需要指定起点vs 即从顶点vs开始计算 此外 引进两个集合S和U S的作用是记录已求出最短路径的顶点 而U则是记录还未求出最短路径的顶点 以及该顶点到起点vs的距离 初始时 S中只有起点
  • [Wikioi 2808][NOIP 1998普及组]二的幂次方---HBNU的童鞋过来看看

    题目描述 Description 任何一个正整数都可以用2的幂次方表示 例如 137 2 7 2 3 2 0 同时约定次方用括号来表示 即a b可表示为a b 由此可知 137可表示为 2 7 2 3 2 0 进一步 7 2 2 2 2 0
  • python+selenium+Chrome options参数的使用

    Chrome Options常用的行为一般有以下几种 禁止图片和视频的加载 提升网页加载速度 添加代理 用于翻墙访问某些页面 或者应对IP访问频率限制的反爬技术 使用移动头 访问移动端的站点 一般这种站点的反爬技术比较薄弱 添加扩展 像正常
  • java-----基本类型包装类

    public class IntegerDemo public static void main String args Integer i1 new Integer 100 根据 int 创建Integer对象 过时 System out
  • Eclipse配置tomcat服务器

    1 首先下载tomcat 下载地址 http maven apache org 下载好后解压至本地磁盘根目录 我是解压至D盘根目录 2 打开Eclipse 进入Window gt Preferences 3 找到Server gt Runt
  • 教妹学Java(五):Java程序在编译和运行时发生了什么

    大家好 我是沉默王二 本篇文章通过我和三妹的对话来谈一谈 Java程序在编译和运行时发生了什么 没见过这么有趣的标题吧 语不惊人死不休 没错 本篇文章的标题就是这么酷炫 接受不了的同学就别点进来看了 所谓好奇心害死猫 能够接受的同学我只能说
  • exchange服务器保留邮件,Exchange服务器之Exchange备份单个用户邮件及清理邮箱数据...

    大家好 为了能够和有疑问的同行者及时沟通 我建立了一个群 615870353 我会免费更新行业信息 并回复大家提出的各种行业问题 问题 需要在Exchange服务器上导出一个用户的邮箱数据 然后再把这个人的邮箱数据清理了 但是不影响账户 方
  • case when then else end的用法

    主要分为两种 简单case函数和搜索case函数 简单case函数 case column when A then a when B then b else 0 end 搜索case函数 case when column A then a
  • 【千律】C++基础:类的构造函数和初始化

    include
  • EISeg——应用于语义分割的自动标注软件

    1 基本介绍 EISeg Efficient Interactive Segmentation 是以RITM及EdgeFlow算法为基础 基于飞桨开发的一个高效智能的交互式分割标注软件 涵盖了通用 人像 遥感 医疗等不同方向的高质量交互式分
  • VS Code配置matlab

    前言 matlab很好地集成了大量数学处理函数 甚至封装了包括信号处理 图像处理 神经网络等在内的方法 但matlab启动慢 没有代码补全 开发环境不友善等缺点常受人诟病 算法编写者往往需要进行大量重复动作 而VS Code是微软推出的一款
  • 独角兽趋势一次一单,这款策略简直让人惊艳,做单准确率高的让人难以置信

    EA类型 趋势独角兽EA 独角兽趋势一次一单可以分为一下几种 长线 中长线 突破型EA 趋势类EA基本都会有止盈止损 而且止盈必须大过止损 这样盈利空间才会大 而且仓位控制要小 单量不能过大 要控制盈亏比 与回撤风险 趋势交易会有很多人会把
  • 前端学习之原生JS实现siblings方法!

    html ul li 1 li li 1 li li 1 li li 1 li li 1 li li 1 li li 1 li li 1 li li 1 li li 1 li li 1 li li 1 li js var oli docum
  • Java并发总结之Java内存模型

    本文主要参考 深入理解Java虚拟机 和 Java并发编程的艺术 对Java内存模型进行简单总结 一 CPU和缓存一致性 1 CPU高速缓存 为了解决CPU处理速度和内存处理速度不对等的问题 就是在CPU和内存之间增加高速缓存 当程序在运行