volatile

volatile是一种轻量同步机制。请看例子

MyThread25类

public class MyThread25 extends Thread{
private boolean isRunning = true; public boolean isRunning()
{
return isRunning;
} public void setRunning(boolean isRunning)
{
this.isRunning = isRunning;
} public void run()
{
System.out.println("进入run了");
while (isRunning == true){}
System.out.println("线程被停止了");
} public static void main(String[] args) throws InterruptedException { MyThread25 mt = new MyThread25();
mt.start();
Thread.sleep(1000);
mt.setRunning(false);
System.out.println("已设置为false"); }
}

输出结果如下

进入run了
已设置为false

为什么程序始终不结束?说明mt.setRunning(false);没有起作用。

这里我们说下Java内存模型(JMM)

java虚拟机有自己的内存模型(Java Memory Model,JMM),JMM可以屏蔽掉各种硬件和操作系统的内存访问差异,以实现让java程序在各种平台下都能达到一致的内存访问效果。

JMM定义了线程和主内存之间的抽象关系:共享变量存储在主内存(Main Memory)中,每个线程都有一个私有的本地内存(Local Memory),本地内存保存了被该线程使用到的主内存的副本,线程对变量的所有操作都必须在本地内存中进行,而不能直接读写主内存中的变量。这三者之间的交互关系如下

出现上述运行结果的原因是,主内存isRunning = true, mt.setRunning(false)设置主内存isRunning = false,本地内存中isRunning仍然是true,线程用的是本地内存,所以进入了死循环。

在isRunning前加上volatile

private volatile boolean isRunning = true;

输出结果如下

进入run了
已设置为false
线程被停止了

volatile不能保证原子类线程安全

先看例子

MyThread26_0类,用volatile修饰num

public class MyThread26_0 extends Thread {
public static volatile int num = 0;
//使用CountDownLatch来等待计算线程执行完
static CountDownLatch countDownLatch = new CountDownLatch(30); @Override
public void run() {
for(int j=0;j<1000;j++){
num++;//自加操作
}
countDownLatch.countDown();
} public static void main(String[] args) throws InterruptedException {
MyThread26_0[] mt = new MyThread26_0[30];
//开启30个线程进行累加操作
for(int i=0;i<mt.length;i++){
mt[i] = new MyThread26_0();
}
for(int i=0;i<mt.length;i++){
mt[i].start();
}
//等待计算线程执行完
countDownLatch.await();
System.out.println(num);
}
}

输出结果如下

25886

理论上,应该输出30000。原子操作表示一段操作是不可分割的,因为num++不是原子操作,这样会出现线程对过期的num进行自增,此时其他线程已经对num进行了自增。

num++分三步:读取、加一、赋值。

结论:

volatile只会对单个的的变量读写具有原子性,像num++这种复合操作volatile是无法保证其原子性的

解决方法:

用原子类AtomicInteger的incrementAndGet方法自增

public class MyThread26_1 extends Thread {
//使用原子操作类
public static AtomicInteger num = new AtomicInteger(0);
//使用CountDownLatch来等待计算线程执行完
static CountDownLatch countDownLatch = new CountDownLatch(30); @Override
public void run() {
for(int j=0;j<1000;j++){
num.incrementAndGet();//原子性的num++,通过循环CAS方式
}
countDownLatch.countDown();
} public static void main(String []args) throws InterruptedException {
MyThread26_1[] mt = new MyThread26_1[30];
//开启30个线程进行累加操作
for(int i=0;i<mt.length;i++){
mt[i] = new MyThread26_1();
}
for(int i=0;i<mt.length;i++){
mt[i].start();
}
//等待计算线程执行完
countDownLatch.await();
System.out.println(num);
}
}

输出结果如下

30000

原子类方法组合使用线程不安全

例子如下

ThreadDomain27类

public class ThreadDomain27 {
public static AtomicInteger aiRef = new AtomicInteger(); public void addNum()
{
System.out.println(Thread.currentThread().getName() + "加了100之后的结果:" + aiRef.addAndGet(100));
aiRef.getAndAdd(1);
}
}

MyThread27类

public class MyThread27 extends Thread{
private ThreadDomain27 td; public MyThread27(ThreadDomain27 td)
{
this.td = td;
} public void run()
{
td.addNum();
} public static void main(String[] args)
{
try
{
ThreadDomain27 td = new ThreadDomain27();
MyThread27[] mt = new MyThread27[5];
for (int i = 0; i < mt.length; i++)
{
mt[i] = new MyThread27(td);
}
for (int i = 0; i < mt.length; i++)
{
mt[i].start();
}
Thread.sleep(1000);
System.out.println(ThreadDomain27.aiRef.get());
}
catch (InterruptedException e)
{
e.printStackTrace();
}
}
}

输出结果如下

Thread-2加了100之后的结果:100
Thread-3加了100之后的结果:200
Thread-0加了100之后的结果:302
Thread-1加了100之后的结果:403
Thread-4加了100之后的结果:504
505

理想的输出结果是100,201,302...,因为addAndGet方法和getAndAdd方法构成的addNum不是原子操作。

解决该问题只需要在addNum加上synchronized关键字。

输出结果如下

Thread-1加了100之后的结果:100
Thread-0加了100之后的结果:201
Thread-2加了100之后的结果:302
Thread-3加了100之后的结果:403
Thread-4加了100之后的结果:504
505

结论:

volatile解决的是变量在多个线程之间的可见性,但是无法保证原子性。

synchronized不仅保障了原子性外,也保障了可见性。

volatile和synchronized比较

先看实例,使用volatile是什么效果

CountDownLatch保证10个线程都能执行完成,当然你也可以在System.out.println(test.inc);之前使用Thread.sleep(xxx)

public class MyThread28 {
//使用CountDownLatch来等待计算线程执行完
static CountDownLatch countDownLatch = new CountDownLatch(10);
public volatile int inc = 0;
public void increase() {
inc++;
} public static synchronized void main(String[] args) throws InterruptedException {
final MyThread28 test = new MyThread28();
for(int i=0;i<10;i++){
new Thread(){
public void run() {
for(int j=0;j<1000;j++)
test.increase();
countDownLatch.countDown(); }
}.start();
}
countDownLatch.await();
System.out.println(test.inc);
} }

运行结果如下

9677

每次运行结果都不一致。刚才我已经解释过,这里我再解释一遍。

使用volatile修饰int型变量i,多个线程同时进行i++操作。比如有两个线程A和B对volatile修饰的i进行i++操作,i的初始值是0,A线程执行i++时从本地内存刚读取了i的值0(i++不是原子操作),就切换到B线程了,B线程从本地内存中读取i的值也为0,然后就切换到A线程继续执行i++操作,完成后i就为1了,接着切换到B线程,因为之前已经读取过了,所以继续执行i++操作,最后的结果i就为1了。同理可以解释为什么每次运行结果都是小于10000的数字。

解决方法:

使用synchronized关键字

public class MyThread28 {
//使用CountDownLatch来等待计算线程执行完
static CountDownLatch countDownLatch = new CountDownLatch(10);
public int inc = 0;
public synchronized void increase() {
inc++;
} public static synchronized void main(String[] args) throws InterruptedException {
final MyThread28 test = new MyThread28();
for(int i=0;i<10;i++){
new Thread(){
public void run() {
for(int j=0;j<1000;j++)
test.increase();
countDownLatch.countDown(); }
}.start();
}
countDownLatch.await();
System.out.println(test.inc);
} }

输出结果如下

10000

synchronized不管是否是原子操作,它能保证同一时刻只有一个线程获取锁执行同步代码,会阻塞其他线程。

结论:

volatile只能用在变量,synchronized可以在变量、方法上使用。

volatile不会造成线程阻塞,synchronized会造成线程阻塞。

volatile效率比synchronized高。

Java多线程(三):volatile的更多相关文章

  1. java 多线程三

    java 多线程一 java 多线程二 java 多线程三 java 多线程四 注意到 java 多线程一 中 MyThread2 运行结果出现0.-1,那是因为在操作共享数据时没有加锁导致. 加锁的 ...

  2. java多线程关键字volatile的使用

    java多线程关键字volatile的作用是表示多个线程对这个变量共享. 如果是只读的就可以直接用,写数据的时候要注意同步问题. 例子: package com.ming.thread.volatil ...

  3. 从零开始学习Java多线程(三)

    本文主要对Java多线程同步与通信以及相关锁的介绍. 1 .Java多线程安全问题 Java多线程安全问题是实现并发最大的问题,可以说多线程开发其实就是围绕多线程安全问题开发,涉及之深,不是简简单单一 ...

  4. java多线程三之线程协作与通信实例

    多线程的难点主要就是多线程通信协作这一块了,前面笔记二中提到了常见的同步方法,这里主要是进行实例学习了,今天总结了一下3个实例: 1.银行存款与提款多线程实现,使用Lock锁和条件Condition. ...

  5. Java多线程——<三>简单的线程执行:Executor

    一.概述 按照<Java多线程——<一><二>>中所讲,我们要使用线程,目前都是显示的声明Thread,并调用其start()方法.多线程并行,明显我们需要声明多个 ...

  6. java多线程(三)-Executors实现的几种线程池以及Callable

    从java5开始,类库中引入了很多新的管理调度线程的API,最常用的就是Executor(执行器)框架.Executor帮助程序员管理Thread对象,简化了并发编程,它其实就是在 提供了一个中间层, ...

  7. Java多线程编程——volatile关键字

    (本篇主要内容摘自<Java多线程编程核心技术>) volatile关键字的主要作用是保证线程之间变量的可见性. package com.func; public class RunThr ...

  8. JAVA多线程三种实现方式

    JAVA多线程实现方式主要有三种:继承Thread类.实现Runnable接口.使用ExecutorService.Callable.Future实现有返回结果的多线程.其中前两种方式线程执行完后都没 ...

  9. Java 多线程 三种实现方式

    Java多线程实现方式主要有三种:继承Thread类.实现Runnable接口.使用ExecutorService.Callable.Future实现有返回结果的多线程.其中前两种方式线程执行完后都没 ...

  10. java多线程三种方式

    java多线程都有几种方式 有三种: (1)继承Thread类,重写run函数 创建: class xx extends Thread{ public void run(){ Thread.sleep ...

随机推荐

  1. 使用WPF实现3D场景[二]

    原文:使用WPF实现3D场景[二] 在上一篇的文章里我们知道如何构造一个简单的三维场景,这次的课程我将和大家一起来研究如何用代码,完成对建立好了的三维场景的观察. 首先看一下DEMO的界面:     ...

  2. 分享一下Oracle 10g和Toad for Oracle的安装步骤

    三年前用过Oracle,单纯的“用过”,主要就是说对数据库的一些操作,还不包含创建一些存储过程之类的,所以对Oracle仅仅只是了解一点儿,因为当时那家公司里面,数据库里面的东西都是那些顾问负责的,再 ...

  3. cross-compile-openssl-windows.sh,cross-compile-curl-windows.sh,cross-compile-zlib-windows.sh,build-zlib-visual-studio-2015-cli.bat

    https://gist.github.com/artynet build zlib with Visual Studio CLI toolhttps://gist.github.com/artyne ...

  4. WPF 四种尺寸单位

    原文:WPF 四种尺寸单位 像素 px 默认单位可以省略 厘米cm 英寸 in 点 pt 1in = 96px 1cm=96/2.42px 1pt=96/72px

  5. C++该typeid和dynamic_cast

    1.typeid在没有虚拟函数的(不相关的动态绑定),typeid它只返回操作对象的实际类型 2.typeid涉及到动态联编问题时(使用基类指针p或者引用p操作派生类对象),typeid(p)返回基类 ...

  6. 在 Swift 中实现单例方法

    我们通常在进行开发的时候,会用到一个叫做 单例模式 的东西.相信大家也都对这种模式非常熟悉了.而且单例的使用在平时的开发中也非常频繁. 比如我们常用到的 NSUserDefaults.standard ...

  7. Centos上通过shell脚本实现数据库备份和还原

    最近有个这样的需求,通过shell脚本实现数据库备份还原,最后通过网上查询自己测试实现,将脚本分享给大家 1.数据库备份脚本 #!/bin/bash ds=`` list=`date +%Y`/`da ...

  8. This problem will occur when running in 64 bit mode with the 32 bit Oracle client components installed.

    Attempt to load Oracle client libraries threw BadImageFormatException. This problem will occur when ...

  9. JavaScript严格模式分析

    简要:严格模式(strict mode)是JavaScript在ES5里面新增的编码模式,只要一行代码 就可开启,可谓 非常简单了,而它对于 我们的编码来说到底有什么不同呢? 一. 严格模式的目的? ...

  10. ELINK离线编程器版本说明

    ELINK离线编程器版本详情,ELinkPROG版本与固件版本须匹配使用! 编程器支持芯片详细列表参见  https://www.cnblogs.com/raswin/p/9303300.html