1. 可见性

如果一个线程对共享变量值的修改,能够及时的被其他线程看到,叫做共享变量的可见性。如果一个变量同时在多个线程的工作内存中存在副本,那么这个变量就叫共享变量

2. JMM(java内存模型)

多个线程同时对主内存的一个共享变量进行读取和修改时,首先会读取这个变量到自己的工作内存中成为一个副本,对这个副本进行改动之后,再更新回主内存中变量所在的地方。

(由于CPU时间片是以线程为最小单位,所以这里的工作内存实际上就是指的物理缓存,CPU运算时获取数据的地方;而主内存也就是指的是内存,也就是原始的共享变量存放的位置)

两条规定:

a.线程对共享变量的所有操作必须在工作内存中进行,不能直接操作主内存

b.不同线程间不能访问彼此的工作内存中的变量,线程间变量值的传递都必须经过主内存

如果一个线程1对共享变量x的修改对线程2可见的话,需要经过下列步骤:

a.线程1将更改x后的值更新到主内存

b.主内存将更新后的x的值更新到线程2的工作内存中x的副本

所以,要实现共享变量的可见性必须保证下列两点:

a.线程对工作内存中副本的更改能够及时的更新到主内存上

b.其他线程能够及时的将主内存上共享变量的更新刷新到自己工作内存的该变量的副本上

Java中可以通过synchronized、volatile、java concurrent类来实现共享变量的可见性

3. synchronized实现可见性

synchronized 实际上是对访问修改共享变量的代码块进行加互斥锁,多个线程对synchronized代码块的访问时,某一时刻仅仅有一个线程在访问和修改代码块中的内 容(加锁),其他所有的线程等待该线程离开代码块时(释放锁)才有机会进入synchronized代码块。

所以某一个线程进入synchronized代码块前后,执行过程入如下:

a.线程获得互斥锁

b.清空工作内存

c.从主内存拷贝共享变量最新的值到工作内存成为副本

d.执行代码

e.将修改后的副本的值刷新回主内存中

f.线程释放锁

随后,其他代码在进入synchronized代码块的时候,所读取到的工作内存上共享变量的值都是上一个线程修改后的最新值。

多个线程之间执行共同的代码块(访问修改共享变量),由于线程交叉执行,最终共享变量的最后值可能有多种结果:

public class SynchronizedTest {private boolean ready = false;private int result = 0;private int number = 1;public void write(){ready = true;number = 2;}public void read(){if(ready){result = number * 3;}System.out.println("result is " + result);}private class TestThread extends Thread{private boolean flag;public TestThread(boolean flag){this.flag = flag;}@Overridepublic void run() {// TODO Auto-generated method stubif(flag){write();}else{read();}}}public static void main(String[] args){SynchronizedTest test = new SynchronizedTest();test.new TestThread(true).start();test.new TestThread(false).start();}
}

如上代码,由于两个线程交叉执行,最后result的结果可能是0或者6或者3

共享变量不可见主要有下列原因:

a.线程的交叉执行

b.重排序

c.共享变量未能及时更新

通过使用synchronized可以保证原子性(synchronized代码块内容要么不执行,要执行就保证全部执行完毕)和可见性,修改后的代码为在write和read方法上加synchronized关键字

4. volatile实现可见性(jdk 1.5后)

volatile如何实现可见性?

volatile变量每次被线程访问时,都强迫线程从主内存中重读该变量的最新值,而当该变量发生修改变化时,也会强迫线程将最新的值刷新回主内存中。这样一来,不同的线程都能及时的看到该变量的最新值。

但是volatile不能保证变量更改的原子性:

比 如number++,这个操作实际上是三个操作的集合(读取number,number加1,将新的值写回number),volatile只能保证每一 步的操作对所有线程是可见的,但是假如两个线程都需要执行number++,那么这一共6个操作集合,之间是可能会交叉执行的,那么最后导致number 的结果可能会不是所期望的。

所以对于number++这种非原子性操作,推荐用synchronized:

synchronized(this){number++;
}

如下代码:最后的number的结果不一定是500,有可能是比500小,因为number++不是一个原子性的操作,用volatile不能保证可见性

public class VolatileTest {public static int number = 0;public void increase(){try {Thread.sleep(300);} catch (InterruptedException e) {// TODO Auto-generated catch blocke.printStackTrace();}number++;}/*** @param args*/public static void main(String[] args) {final VolatileTest test = new VolatileTest();for(int i = 0 ; i < 500 ; i++){new Thread(new Runnable() {@Overridepublic void run() {test.increase();}}).start();}//若当期依然有子线程没有执行完毕while(Thread.activeCount() > 1){Thread.yield();//使得当前线程(主线程)让出CPU时间片}System.out.println("number is " + number);}}

对于自增之类的非原子性操作,只能通过如下方式保证可见性:

a. synchronized

b. ReentrantLock

c. AtomicInteger

synchronized修改如下:

public void increase(){try {Thread.sleep(300);} catch (InterruptedException e) {// TODO Auto-generated catch blocke.printStackTrace();}synchronized(this){number++;}}

ReentrantLock修改方式如下:

public class VolatileTest {public static int number = 0;public Lock lock =  new ReentrantLock();public void increase(){try {Thread.sleep(300);} catch (InterruptedException e) {// TODO Auto-generated catch blocke.printStackTrace();}lock.lock();try{number++;//这块的代码实际项目中可能会出现异常,所以要捕获}finally{lock.unlock();//用try finally块保证Unlock一定要执行}}。。。
}

AtomicInteger,一个提供原子操作的Integer的类。在Java语言中,++i和i++操作并不是线程安全的,在使用的时候,不可避免的会用到synchronized关键字。而AtomicInteger则通过一种线程安全的加减操作接口。

修改如下:

package com.mooc.test;import java.util.concurrent.atomic.AtomicInteger;public class VolatileTest {public static AtomicInteger number = new AtomicInteger(0);public void increase(){try {Thread.sleep(300);} catch (InterruptedException e) {// TODO Auto-generated catch blocke.printStackTrace();}number.getAndIncrement();//获得当前值并且加1}/*** @param args*/public static void main(String[] args) {final VolatileTest test = new VolatileTest();for(int i = 0 ; i < 500 ; i++){new Thread(new Runnable() {@Overridepublic void run() {test.increase();}}).start();}//若当期依然有子线程没有执行完毕while(Thread.activeCount() > 1){Thread.yield();//使得当前线程(主线程)让出CPU时间片}System.out.println("number is " + number.get());}}

5. volatile适用情况

a.对变量的写入操作不依赖当前值

比如自增自减、number = number + 5等(不满足)

b.当前volatile变量不依赖于别的volatile变量

比如 volatile_var > volatile_var2这个不等式(不满足)

6. synchronized和volatile比较

a. volatile不需要同步操作,所以效率更高,不会阻塞线程,但是适用情况比较窄

b. volatile读变量相当于加锁(即进入synchronized代码块),而写变量相当于解锁(退出synchronized代码块)

c. synchronized既能保证共享变量可见性,也可以保证锁内操作的原子性;volatile只能保证可见性

Java多线程共享变量控制相关推荐

  1. java 多线程共享变量两类问题_Java并发基础09. 多个线程间共享数据问题

    先看一个多线程间共享数据的问题: 设计四个线程,其中两个线程每次对data增加1,另外两个线程每次对data减少1. 从问题来看,很明显涉及到了线程间通数据的共享,四个线程共享一个 data,共同操作 ...

  2. JAVA多线程共享变量的可见性

    一.简介 我们知道线程在工作的时候有自己的私有内存,工作内存.程序运行的时候从主内存拉取需要的变量到工作内存,处理完再返回主内存.这篇文章总结哪些代码会使线程去主内存拉取变量. 二.volatile ...

  3. Java多线程变量共享与隔离

    文章目录 线程相关 线程的相关API 线程的调度 线程的优先级 方法和变量的线程安全问题 静态方法 非静态方法 静态变量 实例变量 局部变量 变量共享 共享变量线程安全问题 可见性 可见性举例 共享变 ...

  4. java主线程控制子线程_CountDownLatch控制主线程等子线程执行完--Java多线程

    1.[代码]CountDownLatch控制主线程等子线程执行完--Java多线程 package com.sihuatech.common; import java.util.concurrent. ...

  5. Java多线程开发系列之四:玩转多线程(线程的控制2)

    在上节的线程控制(详情点击这里)中,我们讲解了线程的等待join().守护线程.本节我们将会把剩下的线程控制内容一并讲完,主要内容有线程的睡眠.让步.优先级.挂起和恢复.停止等. 废话不多说,我们直接 ...

  6. 一篇文章弄懂Java多线程基础和Java内存模型

    文章目录 一.多线程的生命周期及五种基本状态 二.Java多线程的创建及启动 1.继承Thread类,重写该类的run()方法 2.通过实现Runnable接口创建线程类 3.通过Callable和F ...

  7. java复习系列[2] - Java多线程

    文章目录 JUC thread 线程状态 sleep() 与 wait() 的异同点 wait() 与 阻塞 的异同点 Some Question !!! 有了**sleep()**为什么还要有**w ...

  8. JAVA多线程和并发基础面试题

    多线程和并发问题是Java技术面试中面试官比较喜欢问的问题之一.在这里,从面试的角度列出了大部分重要的问题,但是你仍然应该牢固的掌握Java多线程基础知识来对应日后碰到的问题.(校对注:非常赞同这个观 ...

  9. 万字图解Java多线程,不信你学不会!

    来源:Java面试题精选 前言 java多线程我个人觉得是javaSe中最难的一部分,我以前也是感觉学会了,但是真正有多线程的需求却不知道怎么下手,实际上还是对多线程这块知识了解不深刻,不知道多线程a ...

最新文章

  1. php session_start() 非常慢,PHP session_start()很慢问题分析与解决办法
  2. C++知识点58——类模板(3、类模板的成员模板)
  3. 计算机学院可以表演的小品,中央戏剧学院小品表演真题之单人小品
  4. 开通博客园,新的网络生活的开始
  5. SpringHibernate4
  6. Leetcode每日一题:7.整数反转
  7. PHP学习记录(数学函数库)
  8. 微信第三方平台相关的转发
  9. jsp漂亮的登录界面源码_【案例+源码】详解MVC框架模式及其应用
  10. Webservice常用接口大全
  11. 如何用tomcat发布自己的Java项目
  12. 《老路用得上的商学课》41-45学习笔记
  13. 北航超算运行matlab,计算性能超50万亿次破纪录,北航荣获ASC19世界大学生超算竞赛最高计算性能奖...
  14. python增删改查mysql_python之mysql的增删改查
  15. 电脑病毒怎么彻底清理?你不知道的8个方法
  16. 使用opentelemetry-go操作Jaeger
  17. build.gradle 中compileSdkVersion,minSdkVersion,targetSdkVersion,buildToolsVersion的意思
  18. 数学建模推国一最终省一经验总结
  19. 《Python绝技:运用Python成为顶级黑客》 用Python刺探网络
  20. 价值800的swapidc鸟云模板开源版

热门文章

  1. 跨境电商站外引流?FB必不可少!
  2. 饱和气压与温度的关系_高中物理讲义:固体液体与物态变化第3节《饱和汽与饱和汽压》...
  3. 漫谈广告竞价模式(六)
  4. 读书笔记——计算机组成原理
  5. 深度学习——卷积神经网络CNN
  6. 实用技巧:快速定位Zuul的性能瓶颈
  7. -1-3 java集合框架基础 java集合体系结构 Collection 常用java集合框架 如何选择集合 迭代器 泛型 通配符概念 Properties 集合 迭代器...
  8. react+socket搭建五子棋平台
  9. LeetCode刷题(26)
  10. 游戏王血计算机,【统计】历代主角控血一览