`
javaso
  • 浏览: 53225 次
  • 性别: Icon_minigender_1
  • 来自: 广州
社区版块
存档分类
最新评论

探索并发编程(三)------Java存储模型和共享对象[转]

阅读更多

很多程序员对一个共享变量初始化要注意可见性和安全发布(安全地构建一个对象,并其他线程能正确访问)等问题不是很理解,认为Java是一个屏蔽内存细节的平台,连对象回收都不需要关心,因此谈到可见性和安全发布大多不知所云。其实关键在于对Java存储模型,可见性和安全发布的问题是起源于Java的存储结构。

Java存储模型原理

有很多书和文章都讲解过Java存储模型,其中一个图很清晰地说明了其存储结构:

 

由上图可知, jvm系统中存在一个主内存(Main Memory或Java Heap Memory),Java中所有变量都储存在主存中,对于所有线程都是共享的。 每条线程都有自己的工作内存(Working Memory),工作内存中保存的是主存中某些变量的拷贝,线程对所有变量的操作都是在工作内存中进行,线程之间无法相互直接访问,变量传递均需要通过主存完成。

这个存储模型很像我们常用的缓存与数据库的关系,因此由此可以推断JVM如此设计应该是为了提升性能,提高多线程的并发能力,并减少线程之间的影响。

Java存储模型潜在的问题

一谈到缓存, 我们立马想到会有缓存不一致性问题,就是说当有缓存与数据库不一致的时候,就需要有相应的机制去同步数据。同理,Java存储模型也有这个问题,当一个线程在自己工作内存里初始化一个变量,当还没来得及同步到主存里时,如果有其他线程来访问它,就会出现不可预知的问题。另外,JVM在底层设计上,对与那些没有同步到主存里的变量,可能会以不一样的操作顺序来执行指令,举个实际的例子:

public class PossibleReordering {  
    static int x = 0, y = 0;  
    static int a = 0, b = 0;  
    public static void main(String[] args)  
            throws InterruptedException {  
        Thread one = new Thread(new Runnable() {  
            public void run() {  
                a = 1;  
                x = b;  
            }  
        });  
        Thread other = new Thread(new Runnable() {  
            public void run() {  
                b = 1;  
                y = a;  
            }  
        });  
        one.start(); other.start();  
        one.join();   other.join();  
        System.out.println("( "+ x + "," + y + ")");  
    }  

public class PossibleReordering {
    static int x = 0, y = 0;
    static int a = 0, b = 0;
    public static void main(String[] args)
            throws InterruptedException {
        Thread one = new Thread(new Runnable() {
            public void run() {
                a = 1;
                x = b;
            }
        });
        Thread other = new Thread(new Runnable() {
            public void run() {
                b = 1;
                y = a;
            }
        });
        one.start(); other.start();
        one.join();   other.join();
        System.out.println("( "+ x + "," + y + ")");
    }
}
 

由于,变量x,y,a,b没有安全发布,导致会不以规定的操作顺序来执行这次四次赋值操作,有可能出现以下顺序:

 

出现这个问题也可以理解,因为既然这些对象不可见,也就是说本应该隔离在各个线程的工作区内,那么对于有些无关顺序的指令,打乱顺序执行在JVM看来也是可行的。

因此,总结起来,会有以下两种潜在问题:

缓存不一致性
重排序执行
解决Java存储模型潜在的问题

为了能让开发人员安全正确地在Java存储模型上编程,JVM提供了一个happens-before原则,有人整理得非常好,我摘抄如下:

在程序顺序中, 线程中的每一个操作, 发生在当前操作后面将要出现的每一个操作之前.
对象监视器的解锁发生在等待获取对象锁的线程之前.
对volitile关键字修饰的变量写入操作, 发生在对该变量的读取之前.
对一个线程的 Thread.start() 调用 发生在启动的线程中的所有操作之前.
线程中的所有操作 发生在从这个线程的 Thread.join()成功返回的所有其他线程之前.
有了原则还不够,Java提供了以下工具和方法来保证变量的可见性和安全发布:

使用 synchronized来同步变量初始化。此方式会立马把工作内存中的变量同步到主内存中
使用 volatile关键字来标示变量。此方式会直接把变量存在主存中而不是工作内存中
final变量。常量内也是存于主存中
另外,一定要明确只有共享变量才会有以上那些问题,如果变量只是这个线程自己使用,就不用担心那么多问题了

搞清楚Java存储模型后,再来看共享对象可见性和安全发布的问题就较为容易了

共享对象的可见性

当对象在从工作内存同步到主内存之前,那么它就是不可见的。若有其他线程在存取不可见对象就会引发可见性问题,看下面一个例子:

public class NoVisibility {  
    private static boolean ready;  
    private static int number;  
    private static class ReaderThread extends Thread {  
        public void run() {  
            while (!ready)  
                Thread.yield();  
            System.out.println(number);  
        }  
    }  
    public static void main(String[] args) {  
        new ReaderThread().start();  
        number = 42;  
        ready = true;  
    }  

public class NoVisibility {
    private static boolean ready;
    private static int number;
    private static class ReaderThread extends Thread {
        public void run() {
            while (!ready)
                Thread.yield();
            System.out.println(number);
        }
    }
    public static void main(String[] args) {
        new ReaderThread().start();
        number = 42;
        ready = true;
    }
}
 

按照正常逻辑,应该会输出42,但其实际结果会非常奇怪,可能会永远没有输出(因为ready为false),可能会输出0(因为重排序问题导致ready=true先执行)。再举一个更为常见的例子,大家都喜欢用只有set和get方法的pojo来设计领域模型,如下所示:

@NotThreadSafe 
public class MutableInteger {  
    private int value;  
    public int  get() { return value; }  
    public void set(int value) { this.value = value; }  

@NotThreadSafe
public class MutableInteger {
    private int value;
    public int  get() { return value; }
    public void set(int value) { this.value = value; }
}
 

但是,当有多个线程同时来存取某一个对象时,可能就会有类似的可见性问题。

为了保证变量的可见性,一般可以用锁、 synchronized关键字、 volatile关键字或直接设置为final

共享变量发布

共享变量发布和我们常说的发布程序类似,就是说让本属于内部的一个变量变为一个可以被外部访问的变量。发布方式分为以下几种:

将对象引用存储到公共静态域
初始化一个可以被外部访问的对象
将对象引用存储到一个集合里
安全发布和保证可见性的方法类似,就是要同步发布动作,并使发布后的对象可见。

线程安全

其实当我们把这些变量封闭在本线程内访问,就可以从根本上避免以上问题,现实中存在很多例子通过线程封闭来安全使用本不是线程安全的对象,比如:

swing的可视化组件和数据模型对象并不是线程安全的,它通过将它们限制到swing的事件分发线程中,实现线程安全
JDBC Connection对象没有要求为线程安全,但JDBC的存取模式决定了一个Connection只会同时被一个线程使用
ThreadLocal把变量限制在本线程中共享

 

本文来自CSDN博客 http://blog.csdn.net/cutesource/archive/2010/08/01/5780486.aspx

分享到:
评论

相关推荐

    Java并发编程与高并发解决方案-学习笔记-www.itmuch.com.pdf

    本文将基于文档《Java并发编程与高并发解决方案-学习笔记***.pdf》中提供的内容,来详细阐述并发编程和高并发的基本概念、CPU多级缓存与缓存一致性、以及Java内存模型。 ### 并发与高并发概念 在现代多线程编程中...

    Java并发编程与高并发解决方案-学习笔记

    ### Java并发编程与高并发解决方案知识点总结 #### 一、并发与高并发基本概念 ##### 1.1 并发 - **定义**: 指一个程序在同一时刻拥有两个或更多的线程,这些线程可以在单核或多核处理器上运行。 - **单核处理器上...

    JAVA并发编程实践

    根据给定文件的信息“JAVA并发编程实践”以及其描述为“Java并发学习资料”,我们可以从中提炼出关于Java并发编程的一些核心知识点。Java并发编程是Java高级特性之一,它允许开发者编写能够同时执行多个任务的程序,...

    《java 并发编程实战高清PDF版》

    《Java并发编程实战》是一本深入探讨Java平台并发编程的权威指南。这本书旨在帮助开发者理解和掌握在Java环境中创建高效、可扩展且可靠的多线程应用程序的关键技术和实践。它涵盖了从基本概念到高级主题的广泛内容,...

    java虚拟机并发编程

    ### Java虚拟机并发编程知识点详解 #### 一、并发与并行的概念 在讨论Java虚拟机(JVM)中的并发编程之前,我们首先需要理解“并发”与“并行”的概念。 - **并发**:指在同一时间段内,多个任务交替执行。在单核...

    Java并发编程实践.pdf

    ### Java并发编程实践 #### 一、并发编程基础 ##### 1.1 并发与并行的区别 ...通过上述知识点的学习,我们可以更好地理解和掌握Java并发编程的基本原理和技巧,为开发高效稳定的并发应用程序打下坚实的基础。

    01-并发编程之深入理解JMM&并发三大特性(一).pdf

    Java内存模型是Java并发编程的核心,它定义了共享变量在多线程环境中的行为规则和编程时应遵守的内存交互操作。JMM的目的是在保证并发编程的正确性的同时,最大化CPU利用率。 并发和并行是两个密切相关的概念,也是...

    Java并发编程与高并发解决方案-学习笔记.pdf

    Java内存模型是并发编程中一个非常重要的概念,它关系到线程之间的通信以及变量的可见性问题。 JMM的核心概念包括工作内存和主内存,每个线程都有自己的工作内存,用于存储局部变量的副本;主内存则是共享变量存储...

    Java并发编程的艺术

    第三章专门讨论Java内存模型,这是一个对Java并发编程至关重要的主题。Java内存模型定义了程序的不同部分如何共享数据,特别是在多线程环境中。本章详细介绍了内存可见性、原子性和有序性等概念,并探讨了它们如何...

    Java并发编程的艺术.zip

    在Java开发中,尤其是在多核处理器和高并发场景下,理解和掌握并发编程是至关重要的。以下是对书中的主要知识点的详细阐述: 1. **Java并发基础** - **线程**:线程是程序执行的最小单位,Java通过`Thread`类来...

    java并发编程-超级大全整理

    【Java 并发编程-超级大全整理】 Java 并发编程是Java开发中不可或缺的一部分,尤其是在高并发场景下,能够有效提升程序的运行效率。本文将深入探讨Java多线程的相关知识点,帮助开发者理解并掌握如何在Java环境中...

    2023 java并发编程手册

    "2023 Java并发编程手册" ...Java 并发编程手册涵盖了 Java 并发编程的基础知识、线程安全机制、锁机制、原子操作、并发集合、并发编程最佳实践、Java 并发编程框架和 Java 并发编程工具等方面的知识点。

    java并发编程实践的.pptx

    根据提供的文档内容,我们可以归纳并深入探讨Java并发编程的一些核心概念和原理,这些知识点对于理解和实践Java并发编程至关重要。 ### JVM内存模型 JVM内存模型是理解Java并发的基础。主要包含以下几个部分: ##...

    java并发编程常识

    在Java中,并发编程主要依赖于JVM的内存模型、线程安全、可见性、原子操作和内存管理等方面的知识。以下详细说明了在给定文件中提及的java并发编程的各个知识点: JVM内存模型 Java虚拟机(JVM)内存模型定义了Java...

    Java并发编程-并发编程知识点总结.docx

    ### Java并发编程知识点总结 #### 1. 什么是线程? 线程是操作系统能够进行运算调度的最小单位,它被包含在进程之中,是进程中的实际运作单位。程序员可以通过线程进行多处理器编程,利用多线程对运算密集型任务...

    Java并发编程实践 PDF 高清版

    本书的读者是那些具有一定Java编程经验的程序员、希望了解Java SE 5,6在线程技术上的改进和新特性的程序员,以及Java和并发编程的爱好者。 目录 代码清单 序 第1章 介绍 1.1 并发的(非常)简短历史 1.2 线程的...

Global site tag (gtag.js) - Google Analytics