volatile关键字

关键字volatile的主要作用是使变量在多个线程间可见。

public class PrintString {
private boolean isContinue = true;
public boolean isContinue(){
return isContinue;
}
public void setContinue(boolean isContinue){
this.isContinue = isContinue;
}
public void PrintMethod(){
try{
while (isContinue ==true){
System.out.println("Name = "+ Thread.currentThread().getName());
Thread.sleep(1000);
}
} catch (InterruptedException e) {
e.printStackTrace();
}
}
} public class Run {
public static void main(String[] args) {
PrintString printString = new PrintString();
printString.PrintMethod();
System.out.println("i'm stop_ing myself!");
printString.setContinue(false);
}
}

运行结果为:

Name = main
Name = main
Name = main
Name = main
Name = main
Name = main
...

程序开始运行后,根本停不下来,主要原因是main线程的while循环停不下来,导致程序不能执行后续的代码,解决方法当然是使用多线程技术。

解决同步的死循环:

package page_3;

public class PrintString_change implements Runnable{
private boolean isContinue = true;
public boolean isContinue(){
return isContinue;
}
public void setContinue(boolean isContinue){
this.isContinue = isContinue;
}
public void PrintMethod(){
try{
while (isContinue ==true){
System.out.println("Name = "+ Thread.currentThread().getName());
Thread.sleep(1000);
}
} catch (InterruptedException e) {
e.printStackTrace();
}
}
@Override
public void run(){
PrintMethod();
}
} public class Run_change {
public static void main(String[] args) {
PrintString_change printString = new PrintString_change();
new Thread(printString).start();
System.out.println("i'm stop_ing myself!");
printString.setContinue(false);
} }

运行的结果为:

i'm stop_ing myself!
Name = Thread-0

关键字volatile的作用是强制从公共堆栈中取得变量的值,而不是从线程的私有数据栈中取得变量的值?

下面这段内容摘自:风过无痕的博客

先来看一段代码:

package page_4;

public class Task implements Runnable{
boolean running = true;
int i = 0;
@Override
public void run(){
while (running){
i++;
}
} public static void main(String[] args) throws InterruptedException {
Task task = new Task();
Thread th = new Thread(task);
th.start();
Thread.sleep(1000);
task.running=false;
Thread.sleep(1000);
System.out.println(task.i);
System.out.println("线程停止");
}
}

这段代码想要他停止,结果无法停止成功,这个是因为JVM的特性,先来看看java的内存模型,如下图:

java内存分为工作内存和主存
工作内存:即java线程的本地内存,是单独给某个线程分配的,存储局部变量等,同时也会复制主存的共享变量作为本地
的副本,目的是为了减少和主存通信的频率,提高效率。
主存:存储类成员变量等

可见性是指的是线程访问变量是否是最新值。
局部变量不存在可见性问题,而共享内存就会有可见性问题,
因为本地线程在创建的时候,会从主存中读取一个共享变量的副本,且修改也是修改副本,
且并不是立即刷新到主存中去,那么其他线程并不会马上共享变量的修改。 
因此,线程B修改共享变量后,线程A并不会马上知晓,就会出现上述死循环的问题。

解决共享变量可见性问题,需要用volatile关键字修饰。
如代码就不会出现死循环:

    volatile boolean running = true;
int i = 0;
@Override
public void run(){
while (running){
i++;
}
}

那么为什么能解决死循环的问题呢?
可见性的特性总结为以下2点:
1. 对volatile变量的写会立即刷新到主存
2. 对volatile变量的读会读主存中的新值

如此一来,就不会出现死循环了。

为了能更深刻的理解volatile的语义,我们来看下面的时序图,回答这2个问题:

问题1:t2时刻,如果线程A读取running变量,会读取到false,还是等待线程B执行完呢?
答案是否定的,volatile并没有锁的特性。
问题2:t4时刻,线程A是否一定能读取到线程B修改后的最新值
答案是肯定的,线程A会从重新从主存中读取running的最新值。

虽然running变量上没有volatile关键字修饰,但是读和写running都是同步方法

同步块存在如下语义:
1.进入同步块,访问共享变量会去读取主存
2.退出同步块,本地内存对共享变量的修改会立即刷新到主存
因此上述代码不会出现死循环。

写在前面的结论:volatile 并不会有锁的特性

  1. 关键字volatile是线程同步的轻量级实现,所以volatile性能肯定比synchronized要好,并且volatile只能修饰于变量,而synchronized可以修饰方法以及代码块。
  2. 多线程访问volatile不会发生阻塞,而synchronized会出现阻塞
  3. volatile能保证数据的可见性,但不能保证原子性,synchronized可以保证原子性,也可以间接保证可见性,因为它会将私有内存和公共内存的数据做同步(如上面的那个代码案例所示)。
  4. 关键字volatile解决的是变量在多个线程之间的可见性,而synchronized关键字解决的是多线程之间访问资源的同步性。

线程安全包括原子性和可见性两个方面,Java的同步机制都是围绕这两个方面来确保线程安全的。

volatile变量的原子性
我看了很多文章,有些文章甚至是出版的书籍都说volatile不是原子的,
他们举的例子是i++操作,i++本身不是原子操作,是读并写,我这里要讲的原子性
指的是写操作,原子性的特别总结为2点:
1. 对一个volatile变量的写操作,只有所有步骤完成,才能被其它线程读取到。
2. 多个线程对volatile变量的写操作本质上是有先后顺序的。也就是说并发写没有问题。
这样说也许读者感觉不到和非volatile变量有什么区别,我来举个例子:
//线程1初始化User
User user;
user = new User();
//线程2读取user
if(user!=null){
user.getName();
}
在多线程并发环境下,线程2读取到的user可能未初始化完成
具体来看User user = new User的语义:
1:分配对象的内存空间
2:初始化对线
3:设置user指向刚分配的内存地址
步骤2和步骤3可能会被重排序,流程变为
1->3->2
这些线程1在执行完第3步而还没来得及执行完第2步的时候,如果内存刷新到了主存,
那么线程2将得到一个未初始化完成的对象。因此如果将user声明为volatile的,那么步骤2,3
将不会被重排序。
下面我们来看一个具体案例,一个基于双重检查的懒加载的单例模式实现:

这个单例模式看起来很完美,如果instance为空,则加锁,只有一个线程进入同步块
完成对象的初始化,然后instance不为空,那么后续的所有线程获取instance都不用加锁,
从而提升了性能。
但是我们刚才讲了对象赋值操作步骤可能会存在重排序,
即当前线程的步骤4执行到一半,其它线程如果进来执行到步骤1,instance已经不为null,
因此将会读取到一个没有初始化完成的对象。
但如果将instance用volatile来修饰,就完全不一样了,对instance的写入操作将会变成一个原子
操作,没有初始化完,就不会被刷新到主存中。
修改后的单例模式代码如下:

对volatile理解的误区

很多人会认为对volatile变量的所有操作都是原子性的,比如自增i++
这其实是不对的。
看如下代码:

如果i++的操作是线程安全的,那么预期结果应该是i=20000

然而运行的结果是:11349
说明i++存在并发问题
i++语义是i=i+1
分为2个步骤
步骤1:读取i=0
步骤2:计算i+1=1,并重新赋值给i
那么可能存在2个线程同时读取到i=0,并计算出结果i=1然后赋值给i
那么就得不到预期结果i=2。
这个问题说明了2个问题:
1.i++这种操作不是原子操作
2.volatile 并不会有锁的特性

java多线程学习笔记(七)的更多相关文章

  1. Java IO学习笔记七:多路复用从单线程到多线程

    作者:Grey 原文地址:Java IO学习笔记七:多路复用从单线程到多线程 在前面提到的多路复用的服务端代码中, 我们在处理读数据的同时,也处理了写事件: public void readHandl ...

  2. java多线程学习笔记——详细

    一.线程类  1.新建状态(New):新创建了一个线程对象.        2.就绪状态(Runnable):线程对象创建后,其他线程调用了该对象的start()方法.该状态的线程位于可运行线程池中, ...

  3. JAVA多线程学习笔记(1)

    JAVA多线程学习笔记(1) 由于笔者使用markdown格式书写,后续copy到blog可能存在格式不美观的问题,本文的.mk文件已经上传到个人的github,会进行同步更新.github传送门 一 ...

  4. Java多线程学习笔记(一)——多线程实现和安全问题

    1. 线程.进程.多线程: 进程是正在执行的程序,线程是进程中的代码执行,多线程就是在一个进程中有多个线程同时执行不同的任务,就像QQ,既可以开视频,又可以同时打字聊天. 2.线程的特点: 1.运行任 ...

  5. Java多线程学习笔记

    进程:正在执行中的程序,其实是应用程序在内存中运行的那片空间.(只负责空间分配) 线程:进程中的一个执行单元,负责进程汇总的程序的运行,一个进程当中至少要有一个线程. 多线程:一个进程中时可以有多个线 ...

  6. Java多线程学习笔记--生产消费者模式

    实际开发中,我们经常会接触到生产消费者模型,如:Android的Looper相应handler处理UI操作,Socket通信的响应过程.数据缓冲区在文件读写应用等.强大的模型框架,鉴于本人水平有限目前 ...

  7. java jvm学习笔记七(jar包的代码认证和签名)

    欢迎装载请说明出处:http://blog.csdn.net/yfqnihao 前言: 如果你循序渐进的看到这里,那么说明你的毅力提高了,jvm的很多东西都是比较抽像的,如果不找相对应的代码来辅助理解 ...

  8. java 多线程学习笔记

    这篇文章主要是个人的学习笔记,是以例子来驱动的,加深自己对多线程的理解. 一:实现多线程的两种方法 1.继承Thread class MyThread1 extends Thread{ public ...

  9. Java 多线程学习笔记:生产者消费者问题

    前言:最近在学习Java多线程,看到ImportNew网上有网友翻译的一篇文章<阻塞队列实现生产者消费者模式>.在文中,使用的是Java的concurrent包中的阻塞队列来实现.在看完后 ...

  10. Java多线程学习(七)并发编程中一些问题

    本节思维导图: 关注微信公众号:"Java面试通关手册" 回复"Java多线程"获取思维导图源文件和思维导图软件. 多线程就一定好吗?快吗?? 并发编程的目的就 ...

随机推荐

  1. 创建配置中心服务端(Spring Cloud Config)

    创建配置中心服务端 创建好项目后添加配置文件内容 server.port=9004 spring.application.name=spring-cloud-config-server-01 #git ...

  2. [Bzoj3223][Tyvj1729] 文艺平衡树(splay/无旋Treap)

    题目链接:https://www.lydsy.com/JudgeOnline/problem.php?id=3223 平衡树处理区间问题的入门题目,普通平衡树那道题在维护平衡树上是以每个数的值作为维护 ...

  3. Codeforces 1091C (数学)

    题面 传送门 分析 假设k是固定的,那访问到的节点编号就是\(1+(a·k \mod n )\),其中a为正整数. 通过找规律不难发现会出现循环. 通过题目中的图片我们不难发现 只有k=1,2,3,6 ...

  4. [CodeForces 52C]Circular RMQ

    题目传送门 评分:省选/NOI-,难度:普及+/提高 这题真的和RMQ没有半点关系,只需要一个裸的线段树,连pushdown都不需要,只需要两种操作:区间修改和区间求最小值,在回溯时加上标记即可,唯一 ...

  5. Service vs provider vs factory 转自:http://stackoverflow.com/questions/15666048/service-vs-provider-vs-factory

    请看此链接:http://stackoverflow.com/questions/15666048/service-vs-provider-vs-factory

  6. K8S存储相关yaml

    一.ConfigMap 1.使用目录创建 vim game.properties vim ui.properties 在一个文件夹下创建两个文件,通过以下命令创建 kubectl create con ...

  7. win10 中文

    按 WinKey+I 鍵,開啟「設定」對話框,再選取「時間與語言」選項.

  8. Go's Declaration Syntax

    Introduction Newcomers to Go wonder why the declaration syntax is different from the tradition estab ...

  9. gitlab私钥配置

    一.Linux版 1).首先打开linux服务器,输入命令:ls -al ~/.ssh,检查是否显示有id_rsa.pub或者id_dsa.pub存在,如果存在请直接跳至第3步. 2).在bash中输 ...

  10. Django前后端分离跨域请求问题

    一.问题背景 之前使用django+vue进行前后端分离碰到跨域请求问题,跨域(域名或者端口不同)请求问题的本质是由于浏览器的同源策略导致的,当请求的响应不是处于同一个域名和端口下,浏览器不会接受响应 ...