Java多线程之三volatile与等待通知机制示例

原子性,可见性与有序性

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

原子性

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

以我们在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();
    }
}

原文地址:https://www.cnblogs.com/liyus/p/10015953.html

时间: 2024-11-10 14:39:16

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

Java并发之等待/通知机制

目录 1 前言 1.1 先来段代码放松一下 2 Object wait()/notify() 2.1 一段入门代码 2.2 问题三连击 a.为什么官方说wait() 要放在while里面? b.为什么wait()必须在同步方法/代码块中调用? c.为什么wait(), notify() 和 notifyAll()是定义在Object里面而不是在Thread里面? 2.3 wait(long timeout) 3 Condition await()/signal() 3.1 用Condition进

二 Java利用等待/通知机制实现一个线程池

接着上一篇博客的 一Java线程的等待/通知模型 ,没有看过的建议先看一下.下面我们用等待通知机制来实现一个线程池 线程的任务就以打印一行文本来模拟耗时的任务.主要代码如下: 1  定义一个任务的接口. 1 /* 2 * 任务的接口 3 */ 4 public interface Task { 5 void doSomething(); 6 } 2  实现一个具体的任务. 1 /* 2 * 具体的任务 3 */ 4 public class PrintTask implements Task{

12.详解Condition的await和signal等待通知机制

1.Condition简介 任何一个java对象都天然继承于Object类,在线程间实现通信的往往会应用到Object的几个方法,比如wait(),wait(long timeout),wait(long timeout, int nanos)与notify(),notifyAll()几个方法实现等待/通知机制,同样的, 在java Lock体系下依然会有同样的方法实现等待/通知机制.从整体上来看Object的wait和notify/notifyAll是与对象监视器配合完成线程间的等待/通知机制

超强图文|并发编程【等待/通知机制】就是这个feel~

你有一个思想,我有一个思想,我们交换后,一个人就有两个思想 If you can NOT explain it simply, you do NOT understand it well enough 现陆续将Demo代码和技术文章整理在一起 Github实践精选 ,方便大家阅读查看,本文同样收录在此,觉得不错,还请Star 并发编程为什么会有等待通知机制 上一篇文章说明了 Java并发死锁解决思路 , 解决死锁的思路之一就是 破坏请求和保持条件, 所有柜员都要通过唯一的账本管理员一次性拿到所有

显式锁(四)Lock的等待通知机制Condition

?? 任意一个Java对象,都拥有一组监视器方法(定义在根类Object上),主要包括:wait( ).wait(long timeout).notify().notifyAll()方法:这些方法与关键字synchronized结合使用,可以实现 隐式锁的等待/通知机制.而显示锁Lock也实现了等待/通知机制:Condition接口也提供了类似Object的监视器方法,与Lock配合使用可以实现 显式锁的等待/通知机制,但是两者在使用方式和功能特性有所差别.总得来说,Condition接口更加灵

java多线程关键字volatile的使用

java多线程关键字volatile的作用是表示多个线程对这个变量共享. 如果是只读的就可以直接用,写数据的时候要注意同步问题. 例子: package com.ming.thread.volatiletesttrhead1; /** * volatile 关键字的使用 * volatile 这个关键字的作用就是保持由此关键字修饰的变量在多个线程之间可以看得见 * @author mingge * */ public class volatiletesttrhead extends Thread

内置锁(二)synchronized下的等待通知机制

一.等待/通知机制的简介 线程之间的协作: ??为了完成某个任务,线程之间需要进行协作,采取的方式:中断.互斥,以及互斥上面的线程的挂起.唤醒:如:生成者--消费者模式.或者某个动作完成,可以唤醒下一个线程.管道流已准备等等: 等待/通知机制: ? ?等待/通知机制 是线程之间的协作一种常用的方式之一,在显示锁Lock 和 内置锁synchronized都有对应的实现方式. 等待/通知机制 经典的使用方式,便是在生产者与消费者的模式中使用: 1.生产者负责生产商品,并送到仓库中存储: 2.消费者

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

(本篇主要内容摘自<Java多线程编程核心技术>) volatile关键字的主要作用是保证线程之间变量的可见性. package com.func; public class RunThread extends Thread{ private boolean isRunning = true; // volatile private boolean isRunning = true; public boolean isRunning() { return isRunning; } public

Java多线程--让主线程等待子线程执行完毕

使用Java多线程编程时经常遇到主线程需要等待子线程执行完成以后才能继续执行,那么接下来介绍一种简单的方式使主线程等待. java.util.concurrent.CountDownLatch 使用countDownLatch.await()方法非常简单的完成主线程的等待: public class ThreadWait { public static void main(String[] args) throws InterruptedException { int threadNumber