Java并发——线程同步Volatile与Synchronized详解

Java并发——线程同步Volatile与Synchronized详解Java面试——线程同步volatile与synchronized详解0.前言面试时很可能遇到这样一个问题:使用volatile修饰int型变量i,多个线程同时进行i++操作,这样可以实现线程安全吗?提到线程安全、线程同步,我们经常会想到两个关键字:volatile和synchronized,那么这两者有什么区别呢?1.volatile与synchronized介绍volatile是变量修

大家好,又见面了,我是你们的朋友全栈君。

0. 前言

转载请注明出处:Java并发——线程同步Volatile与Synchronized详解_SEU_Calvin的博客-CSDN博客_javasynchronized和volatile

面试时很可能遇到这样一个问题:使用volatile修饰int型变量i多个线程同时进行i++操作,这样可以实现线程安全吗?提到线程安全、线程同步,我们经常会想到两个关键字:volatilesynchronized,那么这两者有什么区别呢?

 

1. volatile修饰的变量具有可见性

volatile是变量修饰符,其修饰的变量具有可见性。

可见性也就是说一旦某个线程修改了该被volatile修饰的变量,它会保证修改的值会立即被更新到主存,当有其他线程需要读取时,可以立即获取修改之后的值。

Java中为了加快程序的运行效率,对一些变量的操作通常是在该线程的寄存器或是CPU缓存上进行的,之后才会同步到主存中,而加了volatile修饰符的变量则是直接读写主存

例子请查看下面的3.1,帮助理解。

2. volatile禁止指令重排 

volatile可以禁止进行指令重排

指令重排是指处理器为了提高程序运行效率,可能会对输入代码进行优化,它不保证各个语句的执行顺序同代码中的顺序一致,但是它会保证程序最终执行结果和代码顺序执行的结果是一致的指令重排序不会影响单个线程的执行,但是会影响到线程并发执行的正确性

程序执行到volatile修饰变量的读操作或者写操作时,在其前面的操作肯定已经完成,且结果已经对后面的操作可见,在其后面的操作肯定还没有进行

例子请查看下面3.2,帮助理解。

3.  synchronized 

synchronized可作用于一段代码或方法,既可以保证可见性,又能够保证原子性。

可见性体现在:通过synchronized或者Lock能保证同一时刻只有一个线程获取锁然后执行同步代码,并且在释放锁之前会将对变量的修改刷新到主存中。

原子性表现在:要么不执行,要么执行到底。

例子请查看下面3.3,帮助理解。

 

2. 总结
(1从而我们可以看出volatile虽然具有可见性但是并不能保证原子性。

2)性能方面,synchronized关键字是防止多个线程同时执行一段代码,就会影响程序执行效率,而volatile关键字在某些情况下性能要优于synchronized。

但是要注意volatile关键字是无法替代synchronized关键字的,因为volatile关键字无法保证操作的原子性。

 

3. volatilesynchronized的使用场景举例(结合第1部分进行理解学习)

3.1 volatile的使用举例

class MyThread extends Thread {           
    private volatile boolean isStop = false;        
    public void run() {    
        while (!isStop) {    
            System.out.println("do something");    
        }    
    }    
    public void setStop() {    
        isStop = true;    
    }          
}  

线程执行run()的时候我们需要在线程中不停的做一些事情,比如while循环,那么这时候该如何停止线程呢?如果线程做的事情不是耗时的,那么只需要使用一个标志即可。如果需要退出时,调用setStop()即可。这里就使用了关键字volatile,这个关键字的目的是如果修改了isStop的值,那么while循环中可以立即读取到修改后的值

如果线程做的事情是耗时的,那么可以使用interrupt方法终止线程 。如果在子线程“睡觉”时被interrupt,那么子线程可以catch到InterruptExpection异常,处理异常后继续往下执行。

 

3.2 volatile的使用举例

//线程1:
context = loadContext();   //语句1  context初始化操作
inited = true;             //语句2
 
//线程2:
while(!inited ){
  sleep()
}
doSomethingwithconfig(context);

因为指令重排序,有可能语句2会在语句1之前执行,可能导致context还没被初始化,而线程2中就使用未初始化的context去进行操作,导致程序出错。

这里如果volatile关键字对inited变量进行修饰,就不会出现这种问题了

 

3.3 必须使用synchronized而不能使用volatile的场景

public class Test {
    public volatile int inc = 0;
    public void increase() {
        inc++;
    }
     
    public static void main(String[] args) {
        final Test test = new Test();
        for(int i=0;i<10;i++){
            new Thread(){
                public void run() {
                    for(int j=0;j<1000;j++)
                        test.increase();
                };
            }.start();
        }
         
        while(Thread.activeCount()>1)  //保证前面的线程都执行完
            Thread.yield();
        System.out.println(test.inc);
    }
}

例子中用new10个线程,分别去调用1000increase()方法,每次运行结果都不一致,都是一个小于10000的数字。自增操作不是原子操作,volatile 是不能保证原子性的。回到文章一开始的例子,使用volatile修饰int型变量i,多个线程同时进行i++操作。比如有两个线程ABvolatile修饰的i进行i++操作,i的初始值是0A线程执行i++时刚读取了i的值0,就切换到B线程了,B线程(从内存中)读取i的值也为0,然后就切换到A线程继续执行i++操作,完成后i就为1了,接着切换到B线程,因为之前已经读取过了,所以继续执行i++操作,最后的结果i就为1了。同理可以解释为什么每次运行结果都是小于10000的数字。
但是使用synchronized对部分代码进行如下修改,就能保证同一时刻只有一个线程获取锁然后执行同步代码。运行结果必然是10000。

public  int inc = 0;
public synchronized void increase() {
        inc++;
}

本文整理参考自:
Java并发编程:volatile关键字解析 – Matrix海子 – 博客园以及warmor的博客

Java并发——线程同步Volatile与Synchronized详解

版权声明:本文内容由互联网用户自发贡献,该文观点仅代表作者本人。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如发现本站有涉嫌侵权/违法违规的内容, 请联系我们举报,一经查实,本站将立刻删除。

发布者:全栈程序员-站长,转载请注明出处:https://javaforall.net/157314.html原文链接:https://javaforall.net

(0)
全栈程序员-站长的头像全栈程序员-站长


相关推荐

  • 关于get请求的长度限制到底是多少?—-一个误区,一个教训

    关于get请求的长度限制到底是多少?—-一个误区,一个教训截至今日之前,我一直因为从某处看到get、post区别中写的:get有长度限制,1024B。很抱歉在未经过个人的检验后,直接奉为正确的定义(也提醒我个人:以后概念理论,还是需要好好验证或求证,要能在繁杂的网络知识中,认真求真,以防以讹传讹!!!)。今日,看到前同事大牛多年前的csdn知识总结,发现原来一直信奉的1024Get请求长度,是错误的。下面把从权威官网的解释复制过来,以做…

    2022年8月24日
    11
  • 启动嵌入式间:资源有限的系统启动

    启动嵌入式间:资源有限的系统启动

    2022年1月10日
    42
  • icem二维非结构网格划分_ICEM_CFD划分六面体结构网格

    icem二维非结构网格划分_ICEM_CFD划分六面体结构网格ICEMCFD是CAE前处理软件,可输出多种网格格式,供Fluent、CFX、Abaqus等CFD软件使用。ICEM有多种几何接口,如CATIA、SolidWorks,SolidEdge等。ICEMCFD中可以生成多重拓扑块的结构和非结构化网格,采用了先进的O-Grid等技术,用户可以方便地在ICEMCFD中对非规则几何形状划出高质量的“O”形、“C”形、“L”形六面体网格。下面将以弯…

    2022年5月9日
    56
  • Java微信公众号开发(附源码!!!)

    Java微信公众号开发(附源码!!!)笔者最近在为一个艺考服务团队开发手机端的服务,由于开发app需要的时间较长,所以选择开发微信公众号。本人比较擅长Java开发,所以本文是基于Java语言的公众号开发。话不多说,直接进入正题。准备工作:一、在微信公众平台申请账号。百度搜索微信公众平台,点击注册,通过邮箱注册成功后会看到如下画面。在这里,选择类型时要注意下。如果你是个人开发的话只能选择订阅号,订阅号没有自定义菜单等…

    2022年5月15日
    47
  • 传感器尺寸、像素、DPI分辨率、英寸、毫米的关系

    传感器尺寸、像素、DPI分辨率、英寸、毫米的关系虽然网上有很多这种资料,但是太过于复杂,每个人的说法都不一样,看的让人云里雾里的,我总结了一下,不知道对不对!1.1英寸=25.4mm2.传感器尺寸:传感器的尺寸是指传感器的大小,一般描述大小有两种形式,以IMX386感光元件为例,其传感器尺寸1/2.9英寸,是指传感器对角线为1/2.9英寸;还可以描述成传感器尺寸4.97mm×6.2mm,是指水平(竖直)长(宽)为4.97(6.2)m…

    2022年6月13日
    55
  • 好用的在线客服系统PHP源码(开源代码+终身使用+安装教程)

    好用的在线客服系统PHP源码(开源代码+终身使用+安装教程)​在线客服系统是一套交互式沟通工具,采用PHP+MYSQL开发。高性能,不卡顿。使用它可以迅速缩小你的选择范围,联系多个供应商、客户等,也可以给你的企业一个关于用户体验的重大影响。

    2025年8月7日
    4

发表回复

您的邮箱地址不会被公开。 必填项已用 * 标注

关注全栈程序员社区公众号