原子性,可见性与有序性

在多线程中,线程同步的时候一般需要考虑原子性,可见性与有序性

原子性

原子性定义:一个操作或者多个操作在执行过程中要么全部执行完成,要么全部都不执行,不存在执行一部分的情况。

以我们在Java代码中经常用到的自增操作i++为例,i++实际上并不是一步操作,而是首先对i的值加一,然后将结果再赋值给i。在单线程中不会存在问题,但如果在多线程中我们考虑这样一个情况:i是一个共享变量,初始值为0,假设线程一以执行到某一步正好进行自增操作i++,刚好对i进行了加一但是还没将值重新赋给i,此时当前线程被cpu挂起,而另一个线程二开始执行,刚好也对i进行了一个赋值操作i=10;,等线程一重新执行后会将i自增后的值1赋给i,此时相当于覆盖了线程二的赋值操作。此时将会产生线程不安全的情况。

可见性

多个线程同时访问一个共享的变量的时候,每个线程的工作内存有这个变量的一个拷贝,变量本身还是保存在共享内存(堆)中。所以并不是每一次一个线程修改了值后其他线程都可以立即取到修改后的值。可见性是指当其他的线程访问同一个变量时,当一个线程修改了这个变量的值,其他线程也能够立即看得到修改的值。

有序性

有序性是指程序的执行严格按照我们写的代码的顺序进行执行。

指令重排

一般情况下,CPU和编译器为了提升程序执行的效率,会按照一定的规则允许对指令进行优化,即调整实际指令运行的顺序。指令重排不会对单线程的程序造成任何不利的影响,但是多线程环境下将会产生一些影响。指令重排的前提条件是指令调整后不会影响单线程程序的执行:

int i = 2; //statement 1
int j = 1;//statement 2 int k = i*j;//statement 3

在上面的代码中对于语句1和语句2相互之间没有任何依赖,所以可能发生指令重排,但是语句3和语句1,2都有关系,所以语句3一定是在语句1和语句2之后执行的。所以单线程情况下是绝对不会出现问题的。但是对于多线程可能就发生只是初始化了语句1或者语句2就执行语句3了。

要保证在多线程下线程安全,这三大性质都是必须要保证的,而一旦其中一项无法保证那么不是线程安全的。前面的synchronized关键字就是实现了这三大特性的。

volatile

虽然已经有了synchronized关键字保证了线程安全需要的三大特性,但是在JDK1.8优化synchronized之前,synchronized关键字都是一个重量级的锁,对程序的效率有着比较大的影响。在java中还有一个synchronized关键字的轻量级的实现-volatile关键字。volatile关键字是在JDK1.5之后重新被重用的一个关键字,它可以保证上诉三大特性中的有序性和可见性,但是不能保证原子性,所以它实际上是线程不安全的。

保证可见性

出现可见性的原因在于私有栈帧中的值和公共堆中的共享值不同得问题。

当一个线程在修改普通变量时,其他线程不能立刻看到修改后的值,如果此时有其他线程读取该变量的值,实际上读到的是没有修改的值。

volatile关键字作用在于当要使用时强制从主内存中读取值,保证每次读取的都是公共内存中的值。

防止指令重排

内存屏障也称为内存栅栏或栅栏指令,是一种屏障指令,它使CPU或编译器对屏障指令之前和之后发出的内存操作执行一个排序约束。 这通常意味着在屏障之前发布的操作被保证在屏障之后发布的操作之前执行。

volatile关键字功能的实现既是通过内存屏障完成的,当使用volatile关键字修饰的变量进行读写是便会加上内存屏障来保证设计变量的操作顺序执行,需要注意的是其和synchronized关键字的同步是不一样的。

为什么不是原子性的?

实际上volatile关键字保证的事所有线程从主存中取到的值是最新的,但是多个线程修改了改变量的值并不会通知其他线程,除非其他线程再次从主存中取值。

在以上阶段中,比如存在两个线程且两个线程都已经加载了变量count的值,这时线程一将count修改为10,线程二将值修改为20,但是两个线程之间并不知道对方都改了值,而最终写到主存的值也是后写入的那一个,即始终都一个线程修改的值被覆盖,所以其并不是原子性的。在涉及到多线程操作共享变量是还是应该加锁进行操作。

synchronized和volatile关键字的比较

  1. volatile关键字并不是同步操作,其在多线程访问下不会进行阻塞,而synchronized关键字会发阻塞。
  2. volatile关键字能保证有序性和可见性,但不能保证原子性。synchronized三种特性都能保证,所以synchronized是线程同步的,而volatile不是。
  3. volatile是线程同步的轻量级实现,所以效率较之synchronized要高。

等待通知机制

在多线程程序中可能会存在多个程序相互配合完成一项功能,这是就需要线程之间进行通信,在一个线程的工作完成后通知后续线程工作。通常情况下我们可以在一个线程中进行一个while循环操作,设置一个标志flag,当该线程的前置线程完成后修改flag,后面的while得到这个标志后知道自身需要开始工作了,跳出循环。但是这种方法的劣势在于while循环使得该线程一个需要处于运行中,同时当多个线程相互之间都需要进行通信时会使得程序变得极其复杂。为了解决这个问题,有人提出了一种等待通知机制。

等待通知机制是利用JDK中提供的API中的wait()notify/notifyAll()方法来进行实现(实际上Lock类中的方法也能实现),wait()方法是使得当前线程进入等待队列中,notify/notifyAll()是将等待的线程唤醒。

等待方

  1. 获取对象锁
  2. 如果条件不满足,调用对象的wait方法,被通知后依然要检查条件是否满足
  3. 条件满足以后,才能执行相关的业务逻辑
Synchronized(对象){
While(条件不满足){
对象.wait()
}
// do your working
}

通知方

  1. 获得对象的锁;
  2. 改变条件;
  3. 通知所有等待在对象的线程
Synchronized(对象){
业务逻辑处理,改变条件
对象.notify/notifyAll
}

实例

public class User {
private int age = 30; public int getAge() {
return age;
} public void setAge(int age) {
this.age = age;
} /**
* 1、获取对象锁
* 2、如果条件不满足,调用对象的wait方法,被通知后依然要检查条件是否满足
* 3、条件满足以后,才能执行相关的业务逻辑
*/
public synchronized void waitAge(){
System.out.println("age is " + this.age);
while(this.age >= 20){
//条件不满足
try {
System.out.println("current thread is waiting");
wait();
} catch (InterruptedException e) {
e.printStackTrace();
}
}
//满足条件后执行
System.out.println("current thread" + Thread.currentThread().getName() + " age is " + this.age);
} /**
* 1、 获得对象的锁;
* 2、 改变条件;
* 3、 通知所有等待在对象的线程
*/
public synchronized void changeAge(){
//修改条件
this.age = new Random().nextInt(20);
System.out.println("inform all thread");
//这里使用notifyAll()是因为notify()方法无法指定唤醒某一个线程,notify()的唤醒是随机的
//notifyAll()唤醒所有等待线程
notifyAll();
} }

测试类:

public class WaitAndInform extends Thread{

    private static User user = new User();

    @Override
public void run() {
user.waitAge();
} public static void main(String[] args) throws InterruptedException {
for(int i=0;i<=4;i++){
new WaitAndInform().start();
}
Thread.sleep(1000);
//修改条件 唤醒其他线程
user.changeAge();
}
}

Java多线程之三volatile与等待通知机制示例的更多相关文章

  1. java 多线程:线程通信-等待通知机制wait和notify方法;(同步代码块synchronized和while循环相互嵌套的差异);管道通信:PipedInputStream;PipedOutputStream;PipedWriter; PipedReader

    1.等待通知机制: 等待通知机制的原理和厨师与服务员的关系很相似: 1,厨师做完一道菜的时间不确定,所以厨师将菜品放到"菜品传递台"上的时间不确定 2,服务员什么时候可以取到菜,必 ...

  2. 【Java并发基础】使用“等待—通知”机制优化死锁中占用且等待解决方案

    前言 在前篇介绍死锁的文章中,我们破坏等待占用且等待条件时,用了一个死循环来获取两个账本对象. // 一次性申请转出账户和转入账户,直到成功 while(!actr.apply(this, targe ...

  3. Java并发编程实战 05等待-通知机制和活跃性问题

    Java并发编程系列 Java并发编程实战 01并发编程的Bug源头 Java并发编程实战 02Java如何解决可见性和有序性问题 Java并发编程实战 03互斥锁 解决原子性问题 Java并发编程实 ...

  4. java多线程系列(三)---等待通知机制

    等待通知机制 前言:本系列将从零开始讲解java多线程相关的技术,内容参考于<java多线程核心技术>与<java并发编程实战>等相关资料,希望站在巨人的肩膀上,再通过我的理解 ...

  5. 《java多线程编程核心技术》不使用等待通知机制 实现线程间通信的 疑问分析

    不使用等待通知机制 实现线程间通信的 疑问分析 2018年04月03日 17:15:08       ayf 阅读数:33 编辑 <java多线程编程核心技术>一书第三章开头,有如下案例: ...

  6. Java并发编程(04):线程间通信,等待/通知机制

    本文源码:GitHub·点这里 || GitEE·点这里 一.概念简介 1.线程通信 在操作系统中,线程是个独立的个体,但是在线程执行过程中,如果处理同一个业务逻辑,可能会产生资源争抢,导致并发问题, ...

  7. Java Concurrency - wait & notify, 等待通知机制

    生产者消费者问题是一个常见的多线程同步案例:一组生产者线程和一组消费者线程共享一个初始状态为空.大小为 N 的缓冲区.只有当缓冲区没满的时候,生产者才能把消息放入缓冲区,否则必须等待:只有缓冲区不空的 ...

  8. Java并发之等待/通知机制

    目录 1 前言 1.1 先来段代码放松一下 2 Object wait()/notify() 2.1 一段入门代码 2.2 问题三连击 a.为什么官方说wait() 要放在while里面? b.为什么 ...

  9. Java并发读书笔记:线程通信之等待通知机制

    目录 synchronized 与 volatile 等待/通知机制 等待 通知 面试常问的几个问题 sleep方法和wait方法的区别 关于放弃对象监视器 在并发编程中,保证线程同步,从而实现线程之 ...

随机推荐

  1. [C#.net]获取文本文件的编码,自动区分GB2312和UTF8

    昨天生产突然反馈上传的结果查询出现了乱码,我赶紧打开后台数据库,发现果真有数据变成了乱码.这个上传程序都运行3个多月了,从未发生乱码现象,查看程序的运行日志,发现日志里的中文都变成了乱码,然后对比之前 ...

  2. SAS 创建新变量

    SAS  创建新变量 在对SAS数据集进行处理时,经常需要根据原有变量或变量值生成新变量.根据要实现功能的不同,SAS提供了多种方法,例如通过数据集选项RENAME=(RENAME语句).赋值语句.求 ...

  3. 85、int 、NSInteger、NSUInteger、NSNumber的区别和联系

    NSNumber是NSValue的一个子类,它是一个对象来存储数字值包括bool型,它提供了一系列的方法来存储char a signed or unsigned char, short int, in ...

  4. 深入理解java虚拟机(一)-----java内存区域以及内存溢出异常

    概述 Java语言的一个非常重要的特点就是与平台的无关性.而使用Java虚拟机是实现这一特点的关键.一般的高级语言如果要在不同的平台上运行,至少需要编译成不同的目标代码.而引入Java语言虚拟机后,J ...

  5. 消除blur属性的边框

    直接设置样式为:  outline:none <!DOCTYPE html> <html lang="en"> <head> <meta ...

  6. MySQL定时器

    MySQL的定时器是一个很有用的功能,有时候需要数据库自动根据时间进行一些必要的操作,此时定时器就派上了用场了. 一.查看MySQL版本号 select version(); 二.查看event的状态 ...

  7. hightopo自己用开源的方案重构一遍

    经过一年多的学习吧前面路上的坑基本算踩过一遍了 所以下面计划吧hightopo网站上的demo用自己的方式重新写一遍

  8. mysql的一点小错误

    当使用sql语句时,字段记得使用``反向单引号,而不是单引号

  9. Windows 10 IoT Core 17127 for Insider 版本更新

    昨天,微软发布了Windows 10 IoT Core 17127 for Insider 版本更新,本次更新只修正了一些Bug,没有发布新的特性.相比于17120,修复了一个已知的问题. 一些已知的 ...

  10. navicat 几个 可用的东西

    1.常用的 表格 一启动 就进入的某某连接某某数据库某某表 2. 结构 比对(菜单栏 “工具里面”) 3.数据对比 同上 4.保持连接 5.全局查询 在工具中查找 ------在数据库或模式中查找