原子性,可见性与有序性

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

原子性

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

以我们在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. 异常:Caused by: java.sql.SQLException: Field 'cust_id' doesn't have a default value

    异常: 由Java.q.L.SqLExpExt引起:字段“CuSTyID”没有默认值 Caused by: java.sql.SQLException: Field 'cust_id' doesn't ...

  2. Jmeter多用户利用集合点瞬压并发测试

    在测试一些限时秒杀类似的接口时,需要模拟多用户同时一瞬间访问接口,我们这里简单模拟多用户同时访问百度. 1.首先打开Jmeter,在测试计划下添加线程组. 2.在线程组下添加HTTP请求. 3.在HT ...

  3. springboot pom.xml记

    本文包括: springboot 基本pom.xml配置 热部署 配置打包插件 maven pom.xml配置详解 1. springboot 基本pom.xml配置 <project xmln ...

  4. win10传奇手册CHM打开无法阅读解决

    今天在阅读传奇的帮助文档时候,突然遇到了一个问题.打开为空白. 如图所示  我这个情况打开的时候会提示 这个时候我们把 打开此文件总是询问 这个对勾 去掉 惊喜有没有. 哈哈 .有问题欢迎大家私信我!

  5. Combo控件失效

    问题:点击combo控件,下拉选项不显示. 解决:注释HandleMessage中的MessageHandler相关语句,正常下拉.

  6. laravel 打印完整sql

    DB::connection()->enableQueryLog(); // 开启QueryLog \App\User::find(1); dump(DB::getQueryLog());

  7. vs C++ scanf 不安全

    项目->属性-> c/c++->预处理器->预处理器定义->加入下面这句: _CRT_SECURE_NO_DEPRECATE

  8. 京东Alpha平台开发笔记系列(一)

    2018京东Alpha开发者大赛是由京东智能面向广大开发者举办的大型语音技能开发比赛,参赛者将通过Skill开放平台开发技能,在实现开发者自身价值的同时,为京东智能活跃用户提供更优质.更便捷.更智能的 ...

  9. opencv2.4.13+python2.7学习笔记--使用 knn对手写数字OCR

    阅读对象:熟悉knn.了解opencv和python. 1.knn理论介绍:算法学习笔记:knn理论介绍 2. opencv中knn函数 路径:opencv\sources\modules\ml\in ...

  10. The META for Mobile terminal

    近来想写一些好玩的手机网页,在这里整理了一下在手机端的meta标签,以免下次忘记,再去网上搜. meta指元素可提供有关页面的元信息(meta-information),比如针对搜索引擎和更新频度的描 ...