并发编程

并发编程之内存可见性

并发编程之内存可见性

在上篇线程安全中,我们已经知道需要使用锁来同步管理对可变状态的访问操作。今天我们来看下并发编程的内存可见性问题。

同步代码块除了实现原子性或者临界区之外,其还保证了内存可见性,即保证其他线程可以看到状态的变化结果。

    private  static boolean stop =false;
    private static int number = 0;

    public static class ReaderThread extends Thread
    {
        public void run()
        {
            while(!stop)
            {
                Thread.yield();
            }
            System.out.println(number);
        }
    }

    public static void main(String[] args)
    {
        new ReaderThread().start();
        number=42;
        stop=true;
    }

在代码中,主线程和读线程都将访问stop和number。主线程启动读线程并将number设置为42、stop设置为true。读线程一直循环知道发现stop为true停止

并输出number的值。由于代码没有足够的同步机制,以及可能会存在指令重排序,所以最终可能会输出42,也可能会输出0,也有可能会一直循环无法停下来。

注意:

在没有同步的情况下,编译器、处理器、运行时等都可能对操作执行顺序进行调整,因此无法对内存的操作顺序进行判定。

由于cpu多级缓存的存在,多线程共享状态的修改涉及到各级缓存、内存之间的同步问题。

一、失效数据

在上边的例子中,读线程可能会读取stop变量失效的值,从而导致程序无限循环而无法停止下来。

失效的数据可能导致引用失效、意外的异常、非法的数据、死循环等问题。

例如下边的 UnSafeInteger类并不是线程安全的,可能导致一个线程set之后,另外一个线程get的时候可能得不到最新的值。

package com.codeartist;

public class UnSafeInteger {

    private int value;

    public  int get()
    {
        return this.value;
    }

    public void set(int value)
    {
        this.value = value;
    }
}

我们可以通过对set和get使用同一个锁进行同步,这样就可以防止get到失效的值。

package com.codeartist;

public class SyncInteger {

    private int value;

    public synchronized  int get()
    {
        return this.value;
    }

    public synchronized void set(int value)
    {
        this.value = value;
    }
}

二、非原子的64位操作和Volatile

失效的数据最终读取的还是之前线程设置的值,由于变量的读取和写入操作都是原子性的,所以绝大多数的变量都可以保证这个最低安全性。

读取和写入64位的long和double变量的操作会分解为两个32位的操作。在多个线程写入和读取时可能导致读取某个值的高32位和另外一个值的低32位。

java和.NET都提供了一种弱的同步机制,即volatile变量,访问volatile变量不会加锁导致阻塞。

volatile通过确保不会对共享状态变量施加的操作进行重排序,也不会将其缓存到cpu不可见的地方,从而保证了共享状态的内存可见性。

volatile变量值确保可见性,并不能确保操作的原子性,所以其只适合作为简单的标志判断操作是否完成、中断等。

只有满足以下条件的时候,才应该考虑使用volatile

1.对该变量的操作不依赖变量的当前值,比如自增、自减都是不适合使用的。

2.该变量不会与其他状态变量一起作为不变性条件。

3.访问该变量不需要加锁进行同步。

三、锁定与可见性

加锁可以确保某个线程以一种可预测的方式查看其他线程的执行结果,即下图中,当线程A获取M锁执行同步代码块的时看到的变量x的值,待锁释放之后,线程B也可以看到。

只要保证读取和写入操作使用同一个锁同步,即可以保证操作的互斥性,也可以保证所有线程都能看到共享变量的最新值的内存可见性。

分类: 并发编程

时间: 2024-10-17 16:42:06

并发编程的相关文章

Java并发编程:Concurrent锁机制解析

.title { text-align: center } .todo { font-family: monospace; color: red } .done { color: green } .tag { background-color: #eee; font-family: monospace; padding: 2px; font-size: 80%; font-weight: normal } .timestamp { color: #bebebe } .timestamp-kwd

VS C++ 并发编程

1.VS2012及以上版本,支持C++11 thread类的并发编程. 相关材料可以参考博客:http://www.cnblogs.com/rangozhang/p/4468754.html 2.但对其之前的版本,可采用以下方式,实现类成员函数创建子线程实现并发. 首先需实现线程类的run函数,故定义了线程类的头文件和其对应的函数实现,具体如图1,2所示: 图1 线程类的头文件 图2 线程类的实现文件 注意到继承的DerivedThread类,只需将并发执行的函数写在其对应的run()函数内即可

Java并发编程:Callable、Future和FutureTask(转)

Java并发编程:Callable.Future和FutureTask 在前面的文章中我们讲述了创建线程的2种方式,一种是直接继承Thread,另外一种就是实现Runnable接口. 这2种方式都有一个缺陷就是:在执行完任务之后无法获取执行结果. 如果需要获取执行结果,就必须通过共享变量或者使用线程通信的方式来达到效果,这样使用起来就比较麻烦. 而自从Java 1.5开始,就提供了Callable和Future,通过它们可以在任务执行完毕之后得到任务执行结果. 今天我们就来讨论一下Callabl

[笔记][Java7并发编程实战手册]3.2 资源的并发访问控制Semaphore信号量

[笔记][Java7并发编程实战手册]系列目录 简介 本文学习信号量Semaphore机制. Semaphore 本质是一个共享锁 内部维护一个可用的信号集,获取信号量之前需要先申请获取信号数量:用完之后,则需要释放信号量:如果不释放,那么其他等待线程则一直阻塞直到获取信号量或则被中断为止 本人的理解是:互斥锁是同一时间只能一个线程访问,而在这里,是同一时间允许获取到了信号量的线程并发访问,而没有获取到信号量的则必须等待信号量的释放: 将信号量初始化为 1,使得它在使用时最多只有一个可用的许可,

【Java并发编程】之七:使用synchronized获取互斥锁的几点说明

 在并发编程中,多线程同时并发访问的资源叫做临界资源,当多个线程同时访问对象并要求操作相同资源时,分割了原子操作就有可能出现数据的不一致或数据不完整的情况,为避免这种情况的发生,我们会采取同步机制,以确保在某一时刻,方法内只允许有一个线程. 采用synchronized修饰符实现的同步机制叫做互斥锁机制,它所获得的锁叫做互斥锁.每个对象都有一个monitor(锁标记),当线程拥有这个锁标记时才能访问这个资源,没有锁标记便进入锁池.任何一个对象系统都会为其创建一个互斥锁,这个锁是为了分配给线程的,

Java 并发编程之图形界面应用程序及死锁问题

不知道为什么这本书还要讲一个界面应用程序,Java的界面做的很糟糕,效率低下,而且界面是java的弱项,可能是因为这里边是有一些并发编程的知识吧. 为什么GUI是单线程的 无论是Swing还是AWT都是单线程的.但它不仅限于在java中,在Qt,NexiStep,macOs CoCoa X windows以及其它环境中的GUI框架都是单线程的,许多人都曾经尝试编写多线程的GUI框架,但最终都由于竞态条件和死锁导致的稳定性问题而又重新回到单线程的事件队列模型:采用一个专门的线程从队列中抽取事件,并

并发编程基础之生产者消费者模式

一:概念 生产者消费者模式是java并发编程中很经典的并发情况,首先有一个大的容器,生产者put元素到 容器中,消费者take元素出来,如果元素的数量超过容器的容量时,生产者不能再往容器中put元素 ,处于阻塞状态,如果元素的数量等于0,则消费者不能在从容器中take数据,处于阻塞状态. 二:示例 /** * */ package com.hlcui.main; import java.util.LinkedList; import java.util.concurrent.ExecutorSe

Java并发编程 Volatile关键字解析

volatile关键字的两层语义 一旦一个共享变量(类的成员变量.类的静态成员变量)被volatile修饰之后,那么就具备了两层语义: 1)保证了不同线程对这个变量进行操作时的可见性,即一个线程修改了某个变量的值,这新值对其他线程来说是立即可见的. 2)禁止进行指令重排序. 根据volatile的语义,我们可以看到,volatile主要针对的是并发三要素(原子性,可见性和有序性)中的后两者有实际优化作用. 可见性: 线程本身并不直接与主内存进行数据的交互,而是通过线程的工作内存来完成相应的操作.

Python 3 并发编程多进程之队列(推荐使用)

Python 3 并发编程多进程之队列(推荐使用) 进程彼此之间互相隔离,要实现进程间通信(IPC),multiprocessing模块支持两种形式:队列和管道,这两种方式都是使用消息传递的. 可以往队列里放任意类型的数据 创建队列的类(底层就是以管道和锁定的方式实现): 1 Queue([maxsize]):创建共享的进程队列,Queue是多进程安全的队列,可以使用Queue实现多进程之间的数据传递. 参数介绍: 1 maxsize是队列中允许最大项数,省略则无大小限制. 方法介绍: 1.主要

Java并发编程:volatile关键字解析(转)

volatile这个关键字可能很多朋友都听说过,或许也都用过.在Java 5之前,它是一个备受争议的关键字,因为在程序中使用它往往会导致出人意料的结果.在Java 5之后,volatile关键字才得以重获生机. volatile关键字虽然从字面上理解起来比较简单,但是要用好不是一件容易的事情.由于volatile关键字是与Java的内存模型有关的,因此在讲述volatile关键之前,我们先来了解一下与内存模型相关的概念和知识,然后分析了volatile关键字的实现原理,最后给出了几个使用vola