基本知识:

可见性:

  可见性是一种复杂的属性,因为可见性中的错误总是会违背我们的直觉。通常,我们无法确保执行读操作的线程能适时地看到其他线程写入的值,有时甚至是根本不可能的事情。为了确保多个线程之间对内存写入操作的可见性,必须使用同步机制。

  可见性,是指线程之间的可见性,一个线程修改的状态对另一个线程是可见的。也就是一个线程修改的结果。另一个线程马上就能看到。比如:用volatile修饰的变量,就会具有可见性。volatile修饰的变量不允许线程内部缓存和重排序,即直接修改内存。所以对其他线程是可见的。但是这里需要注意一个问题,volatile只能让被他修饰内容具有可见性,但不能保证它具有原子性。比如 volatile int a = 0;之后有一个操作 a++;这个变量a具有可见性,但是a++ 依然是一个非原子操作,也就是这个操作同样存在线程安全问题。

  在 Java 中 volatile、synchronized 和 final 实现可见性。

原子性:

  原子是世界上的最小单位,具有不可分割性。比如 a=0;(a非long和double类型) 这个操作是不可分割的,那么我们说这个操作时原子操作。再比如:a++; 这个操作实际是a = a + 1;是可分割的,所以他不是一个原子操作。非原子操作都会存在线程安全问题,需要我们使用同步技术(sychronized)来让它变成一个原子操作。一个操作是原子操作,那么我们称它具有原子性。java的concurrent包下提供了一些原子类,我们可以通过阅读API来了解这些原子类的用法。比如:AtomicInteger、AtomicLong、AtomicReference等。

  在 Java 中 synchronized 和在 lock、unlock 中操作保证原子性。

有序性:

  Java 语言提供了 volatile 和 synchronized 两个关键字来保证线程之间操作的有序性,volatile 是因为其本身包含“禁止指令重排序”的语义,synchronized 是由“一个变量在同一个时刻只允许一条线程对其进行 lock 操作”这条规则获得的,此规则决定了持有同一个对象锁的两个同步块只能串行执行。

下面内容摘录自《Java Concurrency in Practice》:

  下面一段代码在多线程环境下,将存在问题。

+ View code
 1 /**
 2  * @author zhengbinMac
 3  */
 4 public class NoVisibility {
 5     private static boolean ready;
 6     private static int number;
 7     private static class ReaderThread extends Thread {
 8         @Override
 9         public void run() {
10             while(!ready) {
11                 Thread.yield();
12             }
13             System.out.println(number);
14         }
15     }
16     public static void main(String[] args) {
17         new ReaderThread().start();
18         number = 42;
19         ready = true;
20     }
21 }

  NoVisibility可能会持续循环下去,因为读线程可能永远都看不到ready的值。甚至NoVisibility可能会输出0,因为读线程可能看到了写入ready的值,但却没有看到之后写入number的值,这种现象被称为“重排序”。只要在某个线程中无法检测到重排序情况(即使在其他线程中可以明显地看到该线程中的重排序),那么就无法确保线程中的操作将按照程序中指定的顺序来执行。当主线程首先写入number,然后在没有同步的情况下写入ready,那么读线程看到的顺序可能与写入的顺序完全相反。

  在没有同步的情况下,编译器、处理器以及运行时等都可能对操作的执行顺序进行一些意想不到的调整。在缺乏足够同步的多线程程序中,要想对内存操作的执行春旭进行判断,无法得到正确的结论。

  这个看上去像是一个失败的设计,但却能使JVM充分地利用现代多核处理器的强大性能。例如,在缺少同步的情况下,Java内存模型允许编译器对操作顺序进行重排序,并将数值缓存在寄存器中。此外,它还允许CPU对操作顺序进行重排序,并将数值缓存在处理器特定的缓存中。

Volatile原理

  Java语言提供了一种稍弱的同步机制,即volatile变量,用来确保将变量的更新操作通知到其他线程。当把变量声明为volatile类型后,编译器与运行时都会注意到这个变量是共享的,因此不会将该变量上的操作与其他内存操作一起重排序。volatile变量不会被缓存在寄存器或者对其他处理器不可见的地方,因此在读取volatile类型的变量时总会返回最新写入的值。

  在访问volatile变量时不会执行加锁操作,因此也就不会使执行线程阻塞,因此volatile变量是一种比sychronized关键字更轻量级的同步机制。

  当对非 volatile 变量进行读写的时候,每个线程先从内存拷贝变量到CPU缓存中。如果计算机有多个CPU,每个线程可能在不同的CPU上被处理,这意味着每个线程可以拷贝到不同的 CPU cache 中。

  而声明变量是 volatile 的,JVM 保证了每次读变量都从内存中读,跳过 CPU cache 这一步。

当一个变量定义为 volatile 之后,将具备两种特性:

  1.保证此变量对所有的线程的可见性,这里的“可见性”,如本文开头所述,当一个线程修改了这个变量的值,volatile 保证了新值能立即同步到主内存,以及每次使用前立即从主内存刷新。但普通变量做不到这点,普通变量的值在线程间传递均需要通过主内存(详见:Java内存模型)来完成。

  2.禁止指令重排序优化。有volatile修饰的变量,赋值后多执行了一个“load addl $0x0, (%esp)”操作,这个操作相当于一个内存屏障(指令重排序时不能把后面的指令重排序到内存屏障之前的位置),只有一个CPU访问内存时,并不需要内存屏障;(什么是指令重排序:是指CPU采用了允许将多条指令不按程序规定的顺序分开发送给各相应电路单元处理)。

volatile 性能:

  volatile 的读性能消耗与普通变量几乎相同,但是写操作稍慢,因为它需要在本地代码中插入许多内存屏障指令来保证处理器不发生乱序执行。


synchronized:

synchronized是针对修饰代码块,对象方法,或者类对象(类对象就是修饰静态方法或者对象)

java的内置锁:每个java对象都可以用做一个实现同步的锁,这些锁成为内置锁。线程进入同步代码块或方法的时候会自动获得该锁,在退出同步代码块或方法时会释放该锁。获得内置锁的唯一途径就是进入这个锁的保护的同步代码块或方法。
java内置锁是一个互斥锁,这就是意味着最多只有一个线程能够获得该锁,当线程A尝试去获得线程B持有的内置锁时,线程A必须等待或者阻塞,直到线程B释放这个锁,如果B线程不释放这个锁,那么A线程将永远等待下去。
java的对象锁和类锁:java的对象锁和类锁在锁的概念上基本上和内置锁是一致的,但是,两个锁实际是有很大的区别的,对象锁是用于对象实例方法,或者一个对象实例上的,类锁是用于类的静态方法或者一个类的class对象上的。我们知道,类的对象实例可以有很多个,但是每个类只有一个class对象,所以不同对象实例的对象锁是互不干扰的,但是每个类只有一个类锁。但是有一点必须注意的是,其实类锁只是一个概念上的东西,并不是真实存在的,它只是用来帮助我们理解锁定实例方法和静态方法的区别的。

这里涉及到内置锁的一个概念(此概念出自java并发编程实战第二章):对象的内置锁和对象的状态之间是没有内在的关联的,虽然大多数类都将内置锁用做一种有效的加锁机制,但对象的域并不一定通过内置锁来保护。当获取到与对象关联的内置锁时,并不能阻止其他线程访问该对象,当某个线程获得对象的锁之后,只能阻止其他线程获得同一个锁。之所以每个对象都有一个内置锁,是为了免去显式地创建锁对象。
类锁和对象锁是两个不一样的锁,控制着不同的区域,它们是互不干扰的。同样,线程获得对象锁的同时,也可以获得该类锁,即同时获得两个锁,这是允许的。
 
到这里,对synchronized的用法已经有了一定的了解。这时有一个疑问,既然有了synchronized修饰方法的同步方式,为什么还需要synchronized修饰同步代码块的方式呢?而这个问题也是synchronized的缺陷所在。
synchronized的缺陷:当某个线程进入同步方法获得对象锁,那么其他线程访问这里对象的同步方法时,必须等待或者阻塞,这对高并发的系统是致命的,这很容易导致系统的崩溃。如果某个线程在同步方法里面发生了死循环,那么它就永远不会释放这个对象锁,那么其他线程就要永远的等待。这是一个致命的问题。
当然同步方法和同步代码块都会有这样的缺陷,只要用了synchronized关键字就会有这样的风险和缺陷。既然避免不了这种缺陷,那么就应该将风险降到最低。这也是同步代码块在某种情况下要优于同步方法的方面。例如在某个类的方法里面:这个类里面声明了一个对象实例,SynObject so=new SynObject();在某个方法里面调用了这个实例的方法so.testsy();但是调用这个方法需要进行同步,不能同时有多个线程同时执行调用这个方法。这时如果直接用synchronized修饰调用了so.testsy();代码的方法,那么当某个线程进入了这个方法之后,这个对象其他同步方法都不能给其他线程访问了。假如这个方法需要执行的时间很长,那么其他线程会一直阻塞,影响到系统的性能。如果这时用synchronized来修饰代码块:synchronized(so){so.testsy();},那么这个方法加锁的对象是so这个对象,跟执行这行代码的对象没有关系,当一个线程执行这个方法时,这对其他同步方法时没有影响的,因为他们持有的锁都完全不一样。

public class TestSynchronized
{    public void testone()   {    synchronized(this)   {    int i = 5;    while( i-- > 0)   {    System.out.println(Thread.currentThread().getName() + " : " + i);    try   {    Thread.sleep(500);    }   catch (InterruptedException ie)   {    }    }    }    }    public synchronized void testtwo()   {    int i = 5;    while( i-- > 0)   {    System.out.println(Thread.currentThread().getName() + " : " + i);    try   {    Thread.sleep(500);    }   catch (InterruptedException ie)   {    }    }    }    public static void main(String[] args)   {    final TestSynchronized myt2 = new TestSynchronized();    Thread thread1= new Thread(  new Runnable() {  public void run() {  myt2.testone();  }  }, "testone"  );    Thread thread2= new Thread(  new Runnable() {  public void run() { myt2.testtwo();   }  }, "testtwo"  );    thread1.start();;    thread2.start(); } }

不过这里还有一种特例,就是上面演示的例子,对象锁synchronized同时修饰方法和代码块,这时也可以体现到同步代码块的优越性,如果testone方法同步代码块后面有非常多没有同步的代码,而且有一个100000的循环,这导致testone方法会执行时间非常长,那么如果直接用synchronized修饰方法,那么在方法没执行完之前,其他线程是不可以访问testtwo方法的,但是如果用了同步代码块,那么当退出代码块时就已经释放了对象锁,当线程还在执行test1的那个100000的循环时,其他线程就已经可以访问testtwo方法了。

这就让阻塞的机会或者线程更少。让系统的性能更优越。
 
一个类的对象锁和另一个类的对象锁是没有关联的,当一个线程获得A类的对象锁时,它同时也可以获得B类的对象锁。

volatile与synchronized 同步原理基础讲解相关推荐

  1. Java并发编程学习笔记——volatile与synchronized关键字原理及使用

    Java代码在编译后会变成Java字节码,字节码被类加载器加载到JVM里,JVM执行字节码,最终需要转化为汇编指令在CPU上执行,Java中所使用的并发机制依赖于JVM的实现和CPU的指令. 一.vo ...

  2. java基础提升篇:synchronized同步块和volatile同步变量

    初遇 Java语言包含两种内在的同步机制:同步块(或方法)和 volatile 变量.这两种机制的提出都是为了实现代码线程的安全性.其中 Volatile 变量的同步性较差(但有时它更简单并且开销更低 ...

  3. 从 JMM 透析 volatile 与 synchronized 原理

    作者 | 李健青 来源 | 码哥字节(ID:MageByte) 头图 |  CSDN 下载自东方IC 在面试.并发编程.一些开源框架中总是会遇到 volatile 与 synchronized .sy ...

  4. java并发机制的底层实现原理(volatile,synchronized,原子操作)

    目录 volatile的应用 volatile的定义与实现原理 volatile的使用优化 synchronized的实现原理与应用 Java对象头 锁的升级与对比 偏向锁 轻量级锁 锁的优缺点对比 ...

  5. 打工人,从 JMM 透析 volatile 与 synchronized 原理

    点击上方蓝色"方志朋",选择"设为星标" 回复"666"获取独家整理的学习资料! 在面试.并发编程.一些开源框架中总是会遇到 volatil ...

  6. 【多线程学习笔记】sychronized关键字底层原理、sychronized与ReentrantLock、volatile和synchronized

    文章目录 sychronized释义 synchronized关键字最主要的三种使用方式: synchronized底层原理: 同步代码块: 同步方法 当前类的class对象作为锁 锁升级 Synch ...

  7. java 同步锁_Java多线程:synchronized同步锁的使用和实现原理

    作用和用法 在多线程对共享资源进行并发访问方面,JDK提供了synchronized关键字来进行线程同步,实现多线程并发访问的线程安全.synchronized的作用主要体现在三个方面:(1)确保线程 ...

  8. Java 并发编程CAS、volatile、synchronized原理详解

    CAS(CompareAndSwap) 什么是CAS? 在Java中调用的是Unsafe的如下方法来CAS修改对象int属性的值(借助C来调用CPU底层指令实现的): /*** * @param o ...

  9. synchronized同步块和volatile同步变量

    Java语言包含两种内在的同步机制:同步块(或方法)和 volatile 变量.这两种机制的提出都是为了实现代码线程的安全性.其中 Volatile 变量的同步性较差(但有时它更简单并且开销更低),而 ...

最新文章

  1. vsftpd下错误之:500 OOPS
  2. Python知识: scipy signal.chirp用法例
  3. “Imperceptible,Robust,and Targeted Adversaria lExamples for Automatic Speech Recognition”
  4. 关于Eclipes的Logcat无法打印消息的解决办法
  5. AcWing 840. 模拟散列表(散列hash)
  6. python语言要多久_怎么自学python,大概要多久?
  7. java学生信息管理系统
  8. CISP 考试教材《第 2 章 知识域:网络安全监管》知识整理
  9. 飞桨博士会第十一期回顾:人工智能与生命科学的硬核碰撞
  10. 《指弹:风居住的街道》
  11. 谈一谈,自身对技术经理这个职位的理解吧
  12. 【ESP 保姆级教程】疯狂传感器篇 —— 案例:Mega + ESP8266 + MQ2烟雾传感器 + MQ3酒精传感器 + MQ7一氧化碳传感器 + OLED +巴法云平台
  13. JenKins添加Git报错Error performing git command: git ls-remote -h
  14. PKUSC 2018 旅游记
  15. 汇编中esp和ebp在函数栈空间的保存和变化 call的参数和局部变量的关系详解
  16. Android SDK各个版本API的特性及兼容性(Dalvik/ART)
  17. Oracle执行语句跟踪(1)——使用sql trace实现语句追踪
  18. 计算机一级经验分享,计算机一级考试经验
  19. BUUCTF:[CSCCTF 2019 Qual]FlaskLight
  20. [转]PHP编码规范

热门文章

  1. 大话数据结构 01 :顺序线性表
  2. mesh threejs 属性_threeJS创建mesh,创建平面,设置mesh的平移,旋转、缩放、自传、透明度、拉伸...
  3. 机器学习(十)——因子分析
  4. 【机器学习】主题模型
  5. URL与URI的不同
  6. wkhtmltopd 下载安装以及php环境下的使用
  7. java-二分查找树的实现
  8. LUA实现单词替换功能
  9. Ubuntu -- 安装、卸载程序的命令
  10. zookeeper配置