• java进阶(七)------多线程---多线程操作同一变量


    情况

    首先要肯定的是ThreadLocal和局部变量是线程安全的,静态和实例变量都是不安全的。

    我们常常在系统中会用一些 静态变量 作为 共同的状态标记。

    但在多线程中常常发现这个 变量的增减 会出现错乱 并不是预期中的结果显示。

    例如:

    package test.autorun;
    
    
    public class ShareVar {
    	private static int nCount=0;
    	
    	public  int getnCount() {
    		return nCount;
    	}
    	public  void setnCount(int nCount) {
    		ShareVar.nCount = nCount;
    	}
    	
    
    }
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11
    • 12
    • 13
    • 14
    • 15

    原因

    内存机制中的"副本"概念

    多个线程访问一个成员变量时每个线程都会得到一个该变量的副本在自己的线程的栈中保存、计算以提高速度。但是这样就会有同步的问题了。当一个线程修改了自己栈内副本的值还没有立即将同步到主存中,其他线程再来获取主存中的该变量时就会得到过期数据。

    解决办法

    为了解决这种问题可以使用synchronized对该变量的操作同步,或使用volatile关键字声明该变量为易变对象这样的话每个线程就不会创建副本到自己的栈中而是直接操作主存。

    volatile

    在对象/变量前加上 volatile 。 Volatile修饰的 成员变量 在每次被 线程 访问时,都强迫从 共享内存 中重读该成员变量的值。而且,当 成员变量 发生变化时,强迫线程将变化值回写到 共享内存 。这样在任何时刻,两个不同的线程总是看到某个 成员变量 的同一个值。 Java语言 规范中指出:为了获得最佳速度,允许线程保存共享 成员变量 的私有拷贝,而且只当线程进入或者离开 同步代码块 时才与共享成员变量的原始值对比。这样当多个线程同时与某个对象交互时,就必须要注意到要让线程及时的得到共享 成员变量 的变化。而volatile 关键字 就是提示JVM:对于这个 成员变量 不能保存它的私有拷贝,而应直接与共享成员变量交互。使用建议:在两个或者更多的线程访问的 成员变量 上使用volatile。当要访问的 变量 已在synchronized代码块中,或者为 常量 时,不必使用。由于使用volatile屏蔽掉了JVM中必要的 代码优化 ,所以在效率上比较低,因此一定在必要时才使用此 关键字 。

    package test.autorun;
    
    
    public class ShareVar {
    	private volatile static int nCount=0;
    	
    	public  int getnCount() {
    		return nCount;
    	}
    	public  void setnCount(int nCount) {
    		ShareVar.nCount = nCount;
    	}
    	
    
    }
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11
    • 12
    • 13
    • 14
    • 15

    synchronized

    将对象/变量加上锁 synchronized 修饰。在线程中,使用同步方法或者同步块。

    package test.autorun;
    
    
    public class ShareVar {
    	private  static int nCount=0;
    	
    	public  int getnCount() {
    		return nCount;
    	}
    	public  synchronized void setnCount(int nCount) {
    		ShareVar.nCount = nCount;
    	}
    	
    
    }
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11
    • 12
    • 13
    • 14
    • 15

    TimerTask

    使用带有线程安全的线程。如:继承 TimerTask 类实现线程,用 Timer.schedule 启动线程 。

    选择

    但synchronized同步方法和块的话 损耗资源和性能是较大的,它会在该方法和块中阻塞变成类似于单线程来运作,所有优先考虑volatile

    先自我介绍一下,小编13年上师交大毕业,曾经在小公司待过,去过华为OPPO等大厂,18年进入阿里,直到现在。深知大多数初中级java工程师,想要升技能,往往是需要自己摸索成长或是报班学习,但对于培训机构动则近万元的学费,着实压力不小。自己不成体系的自学效率很低又漫长,而且容易碰到天花板技术停止不前。因此我收集了一份《java开发全套学习资料》送给大家,初衷也很简单,就是希望帮助到想自学又不知道该从何学起的朋友,同时减轻大家的负担。添加下方名片,即可获取全套学习资料哦

  • 相关阅读:
    驱动开发:内核封装WSK网络通信接口
    .NET微信网页开发之网页授权获取用户基本信息
    正则系列之 Unicode 属性转义(Unicode Property Escapes)
    【华为机试真题 JAVA】服务器广播-200
    基于深度学习的车辆检测技术
    FPGA原理与结构——时钟IP核的使用与测试
    小黑准备收拾东西领物资的日常积累:线程与进程
    【超详细demo】Spring Boot 多数据源配置
    【AI应用开发全流程】使用AscendCL开发板完成模型推理
    算法提升:图的启发式搜索算法(A算法、A*算法)
  • 原文地址:https://blog.csdn.net/m0_67401835/article/details/126107818