Java的多线程机制系列:(四)不得不提的volatile及指令重排序(happen-before)

volatile详解和相关知识的延伸 我们为什么要使用volatile,因为系统会把我们所写的局部变量,自动放在cpu的寄存器里,有人就很好奇,为什么会自动放进寄存器里呢?因为读取寄存器里的变量比读取内存里的快,因为系统在某些时候,需要频繁的读取变量,如果这个时候每次都去内存里进行存取操作,就会耗费较多的时间,如果我们能够有效地利用CPU的寄存器,就会显著提升程序的运行效率。... 阅读详情

转自:http://www.cnblogs.com/mengheng/p/3495379.html


一、不得不提的volatile

volatile是个很老的关键字,几乎伴随着JDK的诞生而诞生,我们都知道这个关键字,但又不太清楚什么时候会使用它;我们在JDK及开源框架中随处可见这个关键字,但并发专家又往往建议我们远离它。比如Thread这个很基础的类,其中很重要的线程状态字段,就是用volatile来修饰,见代码

 /* Java thread status for tools,
     * initialized to indicate thread 'not yet started'
     */
 
    private volatile int threadStatus = 0;

如上面所说,并发专家建议我们远离它,尤其是在JDK6的synchronized关键字的性能被大幅优化之后,更是几乎没有使用它的场景,但这仍然是个值得研究的关键字,研究它的意义不在于去使用它,而在于理解它对理解Java的整个多线程的机制是很有帮助的。

1. 例子

先来体会一下volatile的作用,从下面代码开始

   1:  public class VolatileExample extends Thread{
   2:      //设置类静态变量,各线程访问这同一共享变量
   3:      private static boolean flag = false;
   4:      
   5:      //无限循环,等待flag变为true时才跳出循环
   6:      public void run() {while (!flag){};}
   7:      
   8:      public static void main(String[] args) throws Exception {
   9:          new VolatileExample().start();
  10:          //sleep的目的是等待线程启动完毕,也就是说进入run的无限循环体了
  11:          Thread.sleep(100);
  12:          flag = true;
  13:      }
  14:  }

这个例子很好理解,main函数里启动一个线程,其run方法是一个以flag为标志位的无限循环。如果flag为true则跳出循环。当main执行到12行的时候,flag被置为true,按逻辑分析此时线程该结束,即整个程序执行完毕。

执行一下看看是什么结果?结果是令人惊讶的,程序始终也不会结束。main是肯定结束了的,其原因就是线程的run方法未结束,即run方法中的flag仍然为false。

把第3行加上volatile修饰符,即

private static volatile boolean flag = false;

再执行一遍看看?结果是程序正常退出,volatile生效了。

我们再修改一下。去掉volatile关键字,恢复到起始的例子,然后把while(!flag){}改为while(!flag){System.out.println(1);},再执行一下看看。按分析,没有volatile关键字的时候,程序不会执行结束,虽然加上了打印语句,但没有做任何的关键字/逻辑的修改,应该程序也不会结束才对,但执行结果却是:程序正常结束。

有了这些感性认识,我们再来分析volatile的语义以及它的作用。

2.volatile语义

volatile的第一条语义是保证线程间变量的可见性,简单地说就是当线程A对变量X进行了修改后,在线程A后面执行的其他线程能看到变量X的变动,更详细地说是要符合以下两个规则:

  • 线程对变量进行修改之后,要立刻回写到主内存。
  • 线程对变量读取的时候,要从主内存中读,而不是缓存。

要详细地解释这个问题,就不得不提一下Java的内存模型(Java Memory Model,简称JMM)。Java的内存模型是一个比较复杂的话题,属于Java语言规范的范畴,个人水平有限,不能在有限篇幅里完整地讲述清楚这个事,如果要清晰地认识,请学习《深入理解Java虚拟机-JVM高级特性与最佳实践》和《The Java Language Specification, Java SE 7 Edition》,这里简单地引用一些资料略加解释。

Java为了保证其平台性,使Java应用程序与操作系统内存模型隔离开,需要定义自己的内存模型。在Java内存模型中,内存分为主内存和工作内存两个部分,其中主内存是所有线程所共享的,而工作内存则是每个线程分配一份,各线程的工作内存间彼此独立、互不可见,在线程启动的时候,虚拟机为每个内存分配一块工作内存,不仅包含了线程内部定义的局部变量,也包含了线程所需要使用的共享变量(非线程内构造的对象)的副本,即为了提高执行效率,读取副本比直接读取主内存更快(这里可以简单地将主内存理解为虚拟机中的堆,而工作内存理解为栈(或称为虚拟机栈),栈是连续的小空间、顺序入栈出栈,而堆是不连续的大空间,所以在栈中寻址的速度比堆要快很多)。工作内存与主内存之间的数据交换通过主内存来进行,如下图:QQ截图20131228132842

同时,Java内存模型还定义了一系列工作内存和主内存之间交互的操作及操作之间的顺序的规则(这规则比较多也比较复杂,参见《深入理解Java虚拟机-JVM高级特性与最佳实践》第12章12.3.2部分),这里只谈和volatile有关的部分。对于共享普通变量来说,约定了变量在工作内存中发生变化了之后,必须要回写到工作内存(迟早要回写但并非马上回写),但对于volatile变量则要求工作内存中发生变化之后,必须马上回写到工作内存,而线程读取volatile变量的时候,必须马上到工作内存中去取最新值而不是读取本地工作内存的副本,此规则保证了前面所说的“当线程A对变量X进行了修改后,在线程A后面执行的其他线程能看到变量X的变动”。

大部分网上的文章对于volatile的解释都是到此为止,但我觉得还是有遗漏的,提出来探讨。工作内存可以说是主内存的一份缓存,为了避免缓存的不一致性,所以volatile需要废弃此缓存。但除了内存缓存之外,在CPU硬件级别也是有缓存的,即寄存器。假如线程A将变量X由0修改为1的时候,CPU是在其缓存内操作,没有及时回写到内存,那么JVM是无法X=1是能及时被之后执行的线程B看到的,所以我觉得JVM在处理volatile变量的时候,也同样用了硬件级别的缓存一致性原则(CPU的缓存一致性原则参见《Java的多线程机制系列:(二)缓存一致性和CAS》。

volatile的第二条语义:禁止指令重排序。关于指令重排序请参见后面的“指令重排序”章节。这是volatile目前主要的一个使用场景。

3. volatile不能保证原子性

介绍volatile不能保证原子性的文章比较多,这里就不举详细例子了,大家可以去网上查阅相关资料。在多线程并发执行i++的操作结果来说,i加与不加volatile都是一样的,只要线程数足够,一定会出现不一致。这里就其为什么不能保证原子性的原理说一下。

上面提到volatile的两条语义保证了线程间共享变量的及时可见性,但整个过程并没有保证同步(参见《Java的多线程机制系列:(一)总述及基础概念》中对“锁”的两种特性的描述),这是与volatile的使命有关的,创造它的背景就是在某些情况下可以代替synchronized实现可见性的目的,规避synchronized带来的线程挂起、调度的开销。如果volatile也能保证同步,那么它就是个锁,可以完全取代synchronized了。从这点看,volatile不可能保证同步,也正基于上面的原因,随着synchronized性能逐渐提高,volatile逐渐退出历史舞台。

为什么volatile不能保证原子性?以i++为例,其包括读取、操作、赋值三个操作,下面是两个线程的操作顺序2

假如说线程A在做了i+1,但未赋值的时候,线程B就开始读取i,那么当线程A赋值i=1,并回写到主内存,而此时线程B已经不再需要i的值了,而是直接交给处理器去做+1的操作,于是当线程B执行完并回写到主内存,i的值仍然是1,而不是预期的2。也就是说,volatile缩短了普通变量在不同线程之间执行的时间差,但仍然存有漏洞,依然不能保证原子性。

这里必须要提的是,在本章开头所说的“各线程的工作内存间彼此独立、互不可见,在线程启动的时候,虚拟机为每个内存分配一块工作内存,不仅包含了线程内部定义的局部变量,也包含了线程所需要使用的共享变量(非线程内构造的对象)的副本,即为了提高执行效率”并不准确。如今的volatile的例子已经是很难重现,如本文开头时只有在while死循环时才体现出volatile的作用,哪怕只是加了System.out.println(1)这么一小段,普通变量也能达到volatile的效果,这是什么原因呢?原来只有在对变量读取频率很高的情况下,虚拟机才不会及时回写主内存,而当频率没有达到虚拟机认为的高频率时,普通变量和volatile是同样的处理逻辑。如在每个循环中执行System.out.println(1)加大了读取变量的时间间隔,使虚拟机认为读取频率并不那么高,所以实现了和volatile的效果(本文开头的例子只在HotSpot24上测试过,没有在JRockit之类其余版本JDK上测过)。volatile的效果在jdk1.2及之前很容易重现,但随着虚拟机的不断优化,如今的普通变量的可见性已经不是那么严重的问题了,这也是volatile如今确实不太有使用场景的原因吧。

4. volatile的适用场景

并发专家建议我们远离volatile是有道理的,这里再总结一下:

  • volatile是在synchronized性能低下的时候提出的。如今synchronized的效率已经大幅提升,所以volatile存在的意义不大。
  • 如今非volatile的共享变量,在访问不是超级频繁的情况下,已经和volatile修饰的变量有同样的效果了。
  • volatile不能保证原子性,这点是大家没太搞清楚的,所以很容易出错。
  • volatile可以禁止重排序。

所以如果我们确定能正确使用volatile,那么在禁止重排序时是一个较好的使用场景,否则我们不需要再使用它。这里只列举出一种volatile的使用场景,即作为标识位的时候(比如本文例子中boolean类型的flag)。用专业点更广泛的说法就是“对变量的写操作不依赖于当前值且该变量没有包含在其他具体变量的不变式中”,具体参见《Java 理论与实践: 正确使用 Volatile 变量》。

 

二、指令重排序(happen-before)

指令重排序是个比较复杂、觉得有些不可思议的问题,同样是先以例子开头(建议大家跑下例子,这是实实在在可以重现的,重排序的概率还是挺高的),有个感性的认识

/**
 * 一个简单的展示Happen-Before的例子.
 * 这里有两个共享变量:a和flag,初始值分别为0和false.在ThreadA中先给a=1,然后flag=true.
 * 如果按照有序的话,那么在ThreadB中如果if(flag)成功的话,则应该a=1,而a=a*1之后a仍然为1,下方的if(a==0)应该永远不会为真,永远不会打印.
 * 但实际情况是:在试验100次的情况下会出现0次或几次的打印结果,而试验1000次结果更明显,有十几次打印.
 */
public class SimpleHappenBefore {
    /** 这是一个验证结果的变量 */
    private static int a=0;
    /** 这是一个标志位 */
    private static boolean flag=false;
    
    public static void main(String[] args) throws InterruptedException {
        //由于多线程情况下未必会试出重排序的结论,所以多试一些次
        for(int i=0;i<1000;i++){
            ThreadA threadA=new ThreadA();
            ThreadB threadB=new ThreadB();
            threadA.start();
            threadB.start();
            
            //这里等待线程结束后,重置共享变量,以使验证结果的工作变得简单些.
            threadA.join();
            threadB.join();
            a=0;
            flag=false;
        }
    }
    
    static class ThreadA extends Thread{
        public void run(){
            a=1;
            flag=true;
        }
    }
    
    static class ThreadB extends Thread{
        public void run(){
            if(flag){
                a=a*1;
            }
            if(a==0){
                System.out.println("ha,a==0");
            }
        }
    }
}
例子比较简单,也添加了注释,不再详细叙述。
 
什么是指令重排序?有两个层面:
  • 在虚拟机层面,为了尽可能减少内存操作速度远慢于CPU运行速度所带来的CPU空置的影响,虚拟机会按照自己的一些规则(这规则后面再叙述)将程序编写顺序打乱——即写在后面的代码在时间顺序上可能会先执行,而写在前面的代码会后执行——以尽可能充分地利用CPU。拿上面的例子来说:假如不是a=1的操作,而是a=new byte[1024*1024](分配1M空间),那么它会运行地很慢,此时CPU是等待其执行结束呢,还是先执行下面那句flag=true呢?显然,先执行flag=true可以提前使用CPU,加快整体效率,当然这样的前提是不会产生错误(什么样的错误后面再说)。虽然这里有两种情况:后面的代码先于前面的代码开始执行;前面的代码先开始执行,但当效率较慢的时候,后面的代码开始执行并先于前面的代码执行结束。不管谁先开始,总之后面的代码在一些情况下存在先结束的可能。
  • 在硬件层面,CPU会将接收到的一批指令按照其规则重排序,同样是基于CPU速度比缓存速度快的原因,和上一点的目的类似,只是硬件处理的话,每次只能在接收到的有限指令范围内重排序,而虚拟机可以在更大层面、更多指令范围内重排序。硬件的重排序机制参见《从JVM并发看CPU内存指令重排序(Memory Reordering)》

重排序很不好理解,上面只是简单地提了下其场景,要想较好地理解这个概念,需要构造一些例子和图表,在这里介绍两篇介绍比较详细、生动的文章《happens-before俗解》和《深入理解Java内存模型(二)——重排序》。其中的“as-if-serial”是应该掌握的,即:不管怎么重排序,单线程程序的执行结果不能被改变。编译器、运行时和处理器都必须遵守“as-if-serial”语义。拿个简单例子来说,

public void execute(){
    int a=0;
    int b=1;
    int c=a+b;
}

这里a=0,b=1两句可以随便排序,不影响程序逻辑结果,但c=a+b这句必须在前两句的后面执行。

 

从前面那个例子可以看到,重排序在多线程环境下出现的概率还是挺高的,在关键字上有volatile和synchronized可以禁用重排序,除此之外还有一些规则,也正是这些规则,使得我们在平时的编程工作中没有感受到重排序的坏处。

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

正是以上这些规则保障了happen-before的顺序,如果不符合以上规则,那么在多线程环境下就不能保证执行顺序等同于代码顺序,也就是“如果在本线程中观察,所有的操作都是有序的;如果在一个线程中观察另外一个线程,则不符合以上规则的都是无序的”,因此,如果我们的多线程程序依赖于代码书写顺序,那么就要考虑是否符合以上规则,如果不符合就要通过一些机制使其符合,最常用的就是synchronized、Lock以及volatile修饰符。


全局变量和局部变量(static,extern,volatile) 全局变量存储区的生命周期是整个程序的执行时间范围;值得注意的是虽然局部变量的声明周期得到了很大的提升,但他的作用域没有发生任何的改变,还是只能在那个局部的范围内使用。2,一个共享变量(类的成员变量、类的静态成员变量)被 volatile 修饰之后:保证了不同线程对这个变量进行操作时的可见性,即一个线程修改了某个变量的值,这新值对其他线程来说是立即可见的。声明在函数内部的变量叫局部变量,局部变量的作用域是它所在函数内部的所有语句,声明在所有函数外边的变量叫全局变量,它们的作用域是程序中的所有语句。 阅读详情

相关推荐

29讲 | Java内存模型中的happen-before是什么?

Java语言在设计之初就引入了线程的概念,以充分利用现代处理器的计算能力,这既带来了强大、灵活的多线程机制,也带来了线程安全等令人混淆的问题,而Java内存模型(Java Memory Model,JMM)为我们提供了一个在纷乱之中达成一致的指导准则。今天我要问你的问题是,Java内存模型中的happen-before是什么?

牛牛 1193

深入理解volatile关键字

volatile 翻译:不稳定的。 它时一种比synchronized关键字更轻量级的同步机制,访问volatile变量时,不会执行加锁操作。 用在哪? volatile关键字可以修饰在类变量或者实例变量上,不能修饰在方法参数,局部变量,实例常量以及类常量上。 volatile的两个作用 1、保证可见性 一旦一个共享变量(类的成员变量、类的静态成员变量)被volatile修饰之后,那么就说明:保证了不同线程对这个变量进行操作时的可见性,即线程修改了某个变量的值,这线程对其他线程来说是立即可见的。

张家煦的博客 1521

DMA IP核的详解与实现

在异步时钟下,s_axi_lite_aclk时钟小于m_axi_sg_clk时钟小于m_axi_mm2s_aclk时钟或者m_axi_s2mm_aclk。同步时钟,m_axi_mm2s_aclk、m_axi_s2mm_aclk和m_axi_sg_clk使用相同的时钟源,s_axi_lite_aclk采用较低的时钟源,同步时钟,m_axi_mm2s_aclk、m_axi_s2mm_aclk和m_axi_sg_clk使用相同的时钟源,s_axi_lite_aclk采用较低的时钟源。

wangjie36的博客 9689

happen-before原则

happen-before是JMM最核心的概念,所以在了解happen-before原则之前,首先需要了解java的内存模型。 JMM内存模型 &nbsp;&nbsp;&nbsp;&nbsp; java内存模型是共享内存的并发模型,线程之间主要通过读-写共享变量来完成隐式通信。java中的共享变量是存储在内存中的,多个线程由其工作内存,其工作方式是将共享内存中的变量拿出来放在工作内存,操作完成后,...

怪咖小青年的博客 3万+

【深入理解Java原理】Java 内存模型中的 happen-before 是什么?

Java 内存模型中的 happen-before 是什么? Happen-before 关系,是Java 内存模型中保证多线程可见性的机制,也是早期语言规范中含糊可见性概念的一个精确定义。 它的具体表现形式,包括但远不止 synchronized,volatile,lock 操作顺序等方面。 happen-before 原则 程序顺序规则:一个线程内执行的每个操作,都保证 happen-before 后面的操作,这样就保证了程序顺序规则, volatile 变量规则:对于 volatile 变量,对他

王小明的专栏 1044

从宁德新能源面试官视角,拆解Halcon/OpenCV图像处理工程师的硬核技能树(附避坑指南)

本文从宁德新能源面试官视角出发,详细拆解Halcon/OpenCV图像处理工程师的核心技能树,涵盖硬件选型、算法实现和系统工程思维三大能力维度。文章提供工业视觉工程师面试的实战避坑指南,包括硬件配置要点、算法优化技巧及职业发展路径,助力求职者系统提升图像处理专业技能。

weixin_28732953的博客 128

Java内存模型中的happen-before是什么?

Happen-before关系是Java内存模型(JMM)中确保多线程操作可见性的核心机制,它精确定义了操作之间的顺序和内存可见性。JMM通过happen-before规则(如程序顺序、volatile变量、锁操作等)保证多线程程序的正确性和一致性。JMM的引入解决了早期Java内存模型中的模糊性和不一致性问题,确保了程序在不同处理器架构上的可移植性。JMM的实现依赖于内存屏障等技术,通过禁止某些重排序来保证内存可见性。对于开发者而言,理解JMM和happen-before关系有助于编写可靠的多线程程序,避

哀莫大于心死 941

手把手教你使用Segformer训练自己的数据

SegFormer 基于Swin transformer 修改

weixin_38353277的博客 2万+

Java中的volatile

​ volatile是Java提供的一种轻量级的同步机制,在并发编程中,它也扮演着比较重要的角色。同synchronized相比(synchronized通常称为重量级锁),volatile更轻量级,相比使用synchronized所带来的庞大开销,倘若能恰当的合理的使用volatile,自然是美事一桩。为了能比较清晰彻底的理解volatile,我们一步一步来分析。首先来看看如下代码 上面这个例子,模拟在多线程环境里,t1线程对flag共享变量修改的值能否被t2可见,即是否输出 “-----flag被设置为

HGW的博客 2万+

Unity 工具 之 Natcorder 插件实现录频、拍照jpg、生成Gif动图功能(适用移动端)

Unity 工具 之 插件实现录频、拍照jpg、生成Gif动图功能(适用移动端) 目录 Unity 工具 之 插件实现录频、拍照jpg、生成Gif动图功能(适用移动端) 一、简单介绍 二、插件导入使用 三、几个 Demo 演示效果 四、关键代码 五、下载地址 一、简单介绍 Unity 工具类,自己整理的一些游戏开发可能用到的模块,单独独立使用,方便游戏开发。 本节介绍,使用Unity插件,进行屏幕录制,生成图片,gif 动图,以及mp4文件等;能录制场景,也能录制摄像头,多功能录

仙魁XAN 7557

ST7789T3与CST816D驱动全解析,面试技巧第11篇:嵌入式面试场景问答 10 连发。

SPI模式下,SCLK、MOSI、CS、DC、RST为必需引脚;8080模式下需连接D0-D7、WR、RD等信号线。CST816D触摸芯片通常通过I2C接口通信,需连接SCL、SDA及INT(中断)引脚。确保电源引脚(VCC、GND)稳定供电,ST7789T3典型工作电压为3.3V。显示异常时检查时序参数,如SPI时钟频率(建议≤40MHz)、数据建立时间。初始化流程需发送一系列命令序列,包括设置像素格式、扫描方向、帧率控制等。扫描方向(MADCTL)可调整显示旋转角度,需根据实际硬件布局配置。

a967gsvj的博客 374

Java的多线程机制系列:不得不提的volatile及指令重排序(happen-before)

一、不得不提的volatile volatile是个很老的关键字,几乎伴随着JDK的诞生而诞生,我们都知道这个关键字,但又不太清楚什么时候会使用它;我们在JDK及开源框架中随处可见这个关键字,但并发专家又往往建议我们远离它。比如Thread这个很基础的类,其中很重要的线程状态字段,就是用volatile来修饰,见代码 /* Java thread status for tool...

ankan7400的博客 121

硬件之光实战篇--Boost

当开关管处于关闭状态时,由于电感中的电流在短时间内不能瞬间消失,因此它会产生一个电感电动势,在电容器上加上一个高电压,从而使输出电压变高。--------------------------------------------------------分割线----------------------------------------------------------------在应用中,升压芯片需要根据负载电路的需求,选择合适的电感和电容等元器件,以达到所需要的升压倍数和输出电压。

m0_58664890的博客 2101

关于volatile与指令重排序的探讨

我们定义了2个线程,一个用来求和操作,一个用来赋值操作,因为定义的是成员变量,所以代码(1)(2)(3)(4)之间不存在依赖关系,在运行时极可能发生指令重排序,如将(4)在(3)前执行,顺序为(4)(1)(3)(2),这时输出的就是0而不是4,但在很多性能比较好的电脑上,这种重排序情况不易复现。OK,知道了这些内容之后,我们再回头看代码示例2中,增加了volatile关键字后的执行顺序,在赋值线程启动后,执行顺序会变成(3)(4)(1)(2),这时打印的结果就为4啦!原创不易,转载请联系Build哥!

JavaBuild的博客 1480

华为OD机试 - 魔法收积木 - 二进制(Java 新系统 200分)

华为OD机试 新系统 题库疯狂收录中,刷题。

学Java,找哪吒 442

MIKE 21 教程 1.2 网格搭建界面介绍之点线面要素的高阶处理 (Mesh Generator 工具)

MIKE 21 教程 手把手教学系列 1.1 网格搭建界面介绍之点线面要素的高阶处理 (Mesh Generator 工具) ​ 上一篇文章讲解了如何创建基本的点、线、面要素 MIKE 21 教程 1.1 网格搭建界面介绍之点线面的创建 本节来讲一下对点线面要素的高阶处理,主要包括要素修改与打断线(break line)的讲解 ​

Hello Word 4148

不得不提的volatile及指令重排序(happen-before)

一、不得不提的volatile volatile是个很老的关键字,几乎伴随着JDK的诞生而诞生,我们都知道这个关键字,但又不太清楚什么时候会使用它;我们在JDK及开源框架中随处可见这个关键字,但并发专家又往往建议我们远离它。比如Thread这个很基础的类,其中很重要的线程状态字段,就是用volatile来修饰,见代码 /* Java thread status for tools,

yjh1271845364的博客 481

JavaSwing_4.2: JDialog、JOptionPane(对话框)

JDialog,对话框。使用 JDialog 类可以创建自定义有的对话框,或者调用 JOptionPane 中的多个静态方法快速创建各种标准的对话框。JOptionPane是 JavaSwing 内部已实现好的,以静态方法的形式提供调用,能够快速方便的弹出要求用户提供值或向其发出通知的标准对话框。

谢TS的博客 7万+

volatile

一、不得不提的volatile volatile是个很老的关键字,几乎伴随着JDK的诞生而诞生,我们都知道这个关键字,但又不太清楚什么时候会使用它;我们在JDK及开源框架中随处可见这个关键字,但并发专家又往往建议我们远离它。比如Thread这个很基础的类,其中很重要的线程状态字段,就是用volatile来修饰,见代码 /* Java thread status for tools, ...

sxf的博客 1万+

STM32F407中文手册

STM32f407中文手册 本参考手册面向应用开发人员,提供有关使用 STM32F405xx/07xx、STM32F415xx/17xx、 STM32F42xxx 和 STM32F43xxx 微控制器存储器与外设的完整信息。STM32F405xx/07xx、STM32F415xx/17xx、STM32F42xxx 和 STM32F43xxx 构成一个微 控制器系列,各产品具有不同的存储器大小、封装和外设。有关订购信息以及器件的机械与电气特性,请参见数据手册。有关 ARM Cortex™-M4F 内核的信息,请参见《Cortex™-M4F 技术参考手册》。

老版QQ原生表情gif动态

这是老版的QQ表情,不过全是动态的的,Windows照片查看器是不会动的,可以右击用浏览器打开查看,再说一遍是老版的,只是分享给宁愿用老版也要动态的人。

【Java面试笔记:进阶】29.Java内存模型中的happen-before是什么?

JMM旨在解决多线程程序的正确性和可移植性问题,确保Java程序在不同硬件平台和编译器上的行为一致。关系是JMM中保证多线程操作可见性的机制。它不仅保证了操作的执行顺序,还保证了内存读写操作的顺序。规则作用场景典型应用程序顺序规则单线程代码顺序执行基本代码逻辑锁规则同步块内的共享变量访问同步volatile变量规则跨线程状态标志状态标记(如flag线程启动/终止规则线程间数据传递join()传递性规则复合操作的有序性多级依赖的操作链核心原则正确同步(如锁、volatile。

2025博客之星Top81。专注AI工程化与架构实战。从分布式思维到模型部署,用工程化视角为你厘清AI落地的真实路径。 1461

C# WinForm 基于DirectShow.dll编写的程序,可边预览边保存视频,音频和图片

基于微软的DirectShow.dll的二次开发1.自动识别连接的摄像头跟麦克风且可以对其进行选择(默认选中第一个)2.可录制wmv和avi两种格式的视频并且都带有音频,avi可以录制的分辨率和帧数进行选择,wmv默认进行了淸晰度优化如不喜欢可去除代码中有相关提示3.可在预览点开之后进行拍照,也可边录制视频边拍照4.添加了视频和音频的压缩功能(酌情使用)这次二次开发发现C#对DirectShow的相关信息太少,这次做完也不敢藏私共享出来,还望大家指正

上一篇: 常见性能优化策略的总结
下一篇: mysql 用户角色权限表建立
andybbc
博客等级 码龄11年 64粉丝 93原创
评论
成就一亿技术人!
拼手气红包6.0元
还能输入1000个字符
 
 条评论被折叠 查看
添加红包

请填写红包祝福语或标题

个

红包个数最小为10个

元

红包金额最低5元

当前余额3.43元 前往充值 >
需支付:10.00元
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付元
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值