关于volatile和同步相关的东西,网上有太多错误和解释不清的东西, 所以查阅相关书籍和文章后总结如下, 如果还是也存在不正确的内容,请一定要指出来, 以免误人子弟:)

1. 原子性与可视性

原子性是指操作不能被线程调度机制中断, 除long和double之外的所有基本类型的读或写操作都是原子操作,注意这里说的读写, 仅指如return i, i = 10, 对于像i++这种操作,包含了读,加1,写指令,所以不是原子操作。 对于long和double的读写,在64位JVM上会把它们当作两个32位来操作,所以不具备原子性。

在定义long和double类型变量时,如果使用volatile来修饰,那么也可以获得原子性,除此以外,volatile与原子性没有直接关系。

可视性,volatile的主要作用就是确保可视性,那么什么是可视性?
    在系统中(多处理器更加明显),对某一变量的修改有时会暂时保存在本地处理器的缓存中,还没有写入共享内存,这时候有另外一个线程读取变量在共享内存的值,那么这个修改对这个线程就是不可视的。
    Volatile修饰的成员变量在每次被线程访问时,都强迫从共享内存中重读该成员变量的值。而且,当成员变量发生变化时,强迫线程将变化值直接写到共享内存。这样在任何时刻,两个不同的线程总是看到某个成员变量的同一个值。 原子操作不一定就有可视性, 比如赋值,i = 10,  如果i没有被特别修饰, 那么因为缓存的原因, 它仍然可能是不可视的

所以原子性和可视性是完全不同的两个概念

2. volatile的应用场景
    详细可以参考java语言架构师Brain Geotz的文章
    Java 中volatile 变量可以被看作是一种 “程度较轻的 synchronized”;与 synchronized 块相比,volatile 优点是所需的编码较少,并且运行时开销也较少, 不会引起线程阻塞。Volatile 变量具有 synchronized 的可视性特性,但是不具备原子特性。
    这就导致Volatile 变量可用于提供线程安全,但是应用场景非常有限,在一些经典java书里,基本都不推荐使用volatile替代synchronized来实现同步,因为风险较大, 很容易出错。
Brain给出的使用volatile实现线程安全的条件:
    对变量的写操作不依赖于当前值。 (count++这种就不行了)
    该变量没有包含在具有其他变量的不变式中(Invariants,例如 “start <=end”)。

我的理解是, 这两个条件都是因为volatile不能提供原子性导致的, 如果多线程执行的一个操作不是原子性的, 使用volatile时就一定要慎重。
    如果满足这两个条件, 多线程执行的操作是原子性的, 那就是可以使用,如:
将 volatile 变量作为状态标志使用

volatile boolean shutdownRequested;
.
...
public void shutdown() { shutdownRequested = true; } //不依赖当前值,原子操作
public void doWork() {   while (!shutdownRequested) {
  // do stuff
  }
}

文章中的其他几种模式, 也都差不多这个意思。

还有一种情况,如果读操作远远超过写操作,可以结合使用内部锁和 volatile 变量来减少公共代码路径的开销。
    结合使用 volatile 和 synchronized 实现 “开销较低的读-写锁”

@ThreadSafe
public class CheesyCounter {   // Employs the cheap read-write lock trick
  
  // All mutative operations MUST be done with the 'this' lock held   @GuardedBy("this") private volatile int value;   public int getValue() { return value; } //使用volatile替代synchronized   public synchronized int increment() {   return value++; }

然而,你可以在读操作中使用 volatile 确保当前值的可见性,因此可以使用锁进行所有变化的操作,使用 volatile 进行只读操作。其中,锁一次只允许一个线程访问值,volatile 允许多个线程执行读操作,因此当使用 volatile 保证读代码路径时,要比使用锁执行全部代码路径获得更高的共享度 —— 就像读-写操作一样。

3. synchronized

class AtomTest implements Runnable {
private volatile int i = 0;
public int getVal() {return i;} public synchronized void inc() {i++; i++;} @Override
public void run() {
while (true) {
inc();
}
}
} public class TestThread { public static void main(String[] args) throws InterruptedException { ExecutorService exec = Executors.newCachedThreadPool();
AtomTest at = new AtomTest();
exec.execute(at);
while (true) {
int val = at.getVal();
if (val % 2 != 0) {
System.out.println(val);
System.exit(0);
}
}
}
}

结果会输出奇数, 退出程序, 原因是getVal读到了inc的中间值。 这种情况只能在getVal方法前加synchronized
    在读取的时候也加锁, 这样在读的时候如果正在写, 那么等待, 所以就不会读到inc的中间值。

关于synchronized值得注意的几个点:

1) 所有对象都含有一个锁,当调用到synchronized(object)块时,先检测obj有没有加锁,如果有, 阻塞, 如果没有, 对object加锁, 执行完后释放锁。

2) synchronized void f() {//...}    等价于  void f() { synchronized(this) {//...} },   在当前对象上加锁
    3) synchronized 提供原子性和可视性, 被它完全保护的变量不需要用volatile

4) synchronized关键字是不能继承的,也就是说,基类的方法synchronized f(){} 在继承类中并不自动是synchronized f(){},而是变成了f(){}。继承类需要你显式的指定它的某个方法为synchronized方法。

注意第一点, 非常重要:虽然sync块可以包裹一段代码,但是锁是加对象上,不是加在代码上,它的工作机制如下:

对于synchronized(obj) {//...}: 先检测obj有没有加锁,如果有, 阻塞, 如果没有, 对obj加锁, 执行块中的代码,完毕后释放锁。这里只检测obj对象上的锁,不关注代码块里的代码或者对象。

所以, 加锁的范围由obj决定,理解了这一点, 下面的很多种情况就会很容易理解:

1. 当两个并发线程访问同一个对象object中的这个相同synchronized(this)同步代码块时,一个时间内针对该对象的操作只能有一个线程得到执行。另一个线程必须等待。

- 如果同一对象已经加锁, 另一线程执行到sync块,检测到有锁挂起。

  2. 然而,另一个线程仍然可以访问该object中的非synchronized(this)同步代码块。

- 非sync代码,不关注对象是否加锁

  3. 当一个线程访问object的一个synchronized(this)同步代码块时,其他线程对该object中所有其它synchronized(this)同步代码块的访问将被阻塞。

- synchronized(this) 只是关注this对象, 只要this已加锁,执行到同一对象中不同方法的sync块时,也会阻塞。

  4. 不同的对象实例的synchronized(this)方法是不相干扰的。也就是说,其它线程照样可以同时访问相同类的另一个对象实例中的synchronized方法。

- 可以同时访问不同对象中的sync块, 原因很简单, 因为synchronized(this),关注的是this对象,不同对象的this是不一样的。

5.  同理,也可以对其他对象加锁,

1) 对于类中的成员对象: private Integer i = new Integer(0);   synchronized(i) {//...}    i创建在堆上,每个对象有一个i,所以效果与synchronized(this)一样。

2) 对于静态成员对象: private static Integer i = new Integer(0);  synchronized(i) {//...}: i创建在静态区,属于类, 所以效果与synchronized(ClassName.class)一样。

  6.对类对象加锁时,对该类的所有对象都起作用:synchronized(ClassName.class) {//...}

最后我在测试代码时,发现对于private Integer i = 0;   synchronized(i) {//...}

锁的效果是全局的,推测可能是Integer对0进程打包时,自动生成的这个对象可能在常量区。 后来查了下资料才发现并非如此:

Integer实现中有一个IntegerCache类,它包含一个静态的Integer数组,在类加载时就将-128 到 127 的Integer对象创建了,并保存在cache数组中,一旦程序调用valueOf 方法,如果i的值是在-128 到 127 之间就直接在cache缓存数组中去取Integer对象。 所以这里的i引用的是整个全局数组里值, 所以锁也是全局的了。。。

如果改成private Integer i = 300,  然后加锁就只在本对象有效了。 原因是i不在缓存范围,所以创建在了堆上。

refer  http://blog.csdn.net/xiaohai0504/article/details/6885137

java中的volatile和synchronized的更多相关文章

  1. Java中的Volatile和synchronized的区别

    Synchronized和Volatile四个不同点: 1.粒度不同,前者锁对象和类 ,后者针对变量2.syn阻塞,volatile线程不阻塞3.syn保证三大特性,volatile不保证原子性4.s ...

  2. java中的volatile关键字

    java中的volatile关键字 一个变量被声明为volatile类型,表示这个变量可能随时被其他线程改变,所以不能把它cache到线程内存(如寄存器)中. 一般情况下volatile不能代替syn ...

  3. 深入理解Java中的volatile关键字

    在再有人问你Java内存模型是什么,就把这篇文章发给他中我们曾经介绍过,Java语言为了解决并发编程中存在的原子性.可见性和有序性问题,提供了一系列和并发处理相关的关键字,比如synchronized ...

  4. java中的Volatile关键字使用

    文章目录 什么时候使用volatile Happens-Before java中的Volatile关键字使用 在本文中,我们会介绍java中的一个关键字volatile. volatile的中文意思是 ...

  5. Java中的ReentrantLock和synchronized两种锁定机制的对比

    问题:多个访问线程将需要写入到文件中的数据先保存到一个队列里面,然后由专门的 写出线程负责从队列中取出数据并写入到文件中. http://blog.csdn.net/top_code/article/ ...

  6. Java中的volatile关键字的功能

    Java中的volatile关键字的功能 volatile是java中的一个类型修饰符.它是被设计用来修饰被不同线程访问和修改的变量.如果不加入volatile,基本上会导致这样的结果:要么无法编写多 ...

  7. 一定要你明白Java中的volatile

    今天Tony来和大家聊聊Java中关键字volatile. 字节码 首先volatile int a = 3;和int a = 3;, 加不加volatile关键字,最终生成的字节码都一样的.有兴趣的 ...

  8. java多线程中的volatile和synchronized

    package com.chzhao; public class Volatiletest extends Thread { private static int count = 0; public ...

  9. java中关键字volatile的作用

    用在多线程,同步变量. 线程为了提高效率,将某成员变量(如A)拷贝了一份(如B),线程中对A的访问其实访问的是B.只在某些动作时才进行A和B的同步.因此存在A和B不一致的情况.volatile就是用来 ...

随机推荐

  1. Life of Pi

    ·when you look into his eyes,you are seeing your own emotionsreflected back at you,nothing else. ·Go ...

  2. 008-python绘制五个五角星

    操纵海龟绘图有着许多的命令,这些命令可以划分为两种:一种为运动命令,一种为画笔控制命令 1. 运动命令: forward(degree)  #向前移动距离degree代表距离 backward(deg ...

  3. 第03章 科学计算库Numpy

    016.Numpy数据结构    关于矩阵运算的库 矩阵 017.Numpy基本操作 判断每一个元素的 018.Numpy矩阵属性 019.Numpy矩阵操作 020.Numpy常用函数 按列拼接就用 ...

  4. ETH功能类

    <?php /** * Ethereum JSON-RPC interface * * See Ethereum API documentation for more information: ...

  5. LUOGU P4149 [IOI2011]Race

    题目描述 给一棵树,每条边有权.求一条简单路径,权值和等于 KKK ,且边的数量最小. 输入输出格式 输入格式: 第一行:两个整数 n,kn,kn,k . 第二至 nnn 行:每行三个整数,表示一条无 ...

  6. fill memset, for小测试

    /*很无聊写着玩玩,后来发现memset效率会比fill高出这么多,可惜一般只用来赋值0,-1......以后可以用fill来偷偷懒了...*/ #include<iostream> #i ...

  7. 话说placeholder

    placeholder 属性提供一种提示(hint),描述输入域所期待的值. 注释:placeholder 属性适用于以下类型的 <input> 标签:text, search, url, ...

  8. Django 使用模板页面,块标签,模型

    1.Django 使用模板页面 Django对于成体系的页面提出了模板继承和模板加载的方式. 1.导入静态页面 2.导入静态文件(css,js,images) 3.修改页面当中的静态地址 1.sett ...

  9. 提升mysql服务器性能(HA MMM MHA MaxScale)

    原文:提升mysql服务器性能(HA MMM MHA MaxScale) 版权声明:皆为本人原创,复制必究 https://blog.csdn.net/m493096871/article/detai ...

  10. 关系数据库理论 ch.6

    6.1 问题的提出 关系模式是一个5元组 R U,D,DOM,F U 属性 D 域 DOM 属性到域的映射 F 依赖 在本章中将关系模式看作 三元组 R U,D 属性-依赖 1NF 每一个分量是不可分 ...