`

java多线程 -- 初学者指南(二):为什么要进行数据同步

阅读更多

Java中的变量分为两类:局部变量和类变量。局部变量是指在方法内定义的变量,如在run方法中定义的变量。对于这些变量来说,并不存在线程之间 共享的问题。因此,它们不需要进行数据同步。类变量是在类中定义的变量,作用域是整个类。这类变量可以被多个线程共享。因此,我们需要对这类变量进行数据 同步。

 

数据同步就是指在同一时间,只能由一个线程来访问被同步的类变量,当前线程访问完这些变量后,其他线程才能继续访问。这里说的访问是指有写操作的访问,如果所有访问类变量的线程都是读操作,一般是不需要数据同步的。

那如果不对共享的类变量进行数据同步,会发生什么情况呢?我们先看看下面的代码会发生什么样的事情:

package test;

public class MyThread extends Thread
{
    public static int n = 0;

    public void run()
    {
        int m = n;
        yield();
        m++;
        n = m;
    }
    public static void main(String[] args) throws Exception
    {
        MyThread myThread = new MyThread ();
        Thread threads[] = new Thread[100];
        for (int i = 0; i < threads.length; i++)
            threads[i] = new Thread(myThread);
        for (int i = 0; i < threads.length; i++)
            threads[i].start();
        for (int i = 0; i < threads.length; i++)
            threads[i].join();
        System.out.println("n = " + MyThread.n);
    }
}

  在执行上面代码的可能结果如下:

     n = 59

 

看到这个结果,可能很多读者会感到奇怪。这个程序明明是启动了100个线程,然后每个线程将静态变量n加1。最后使用join方法使这100个线程都运行完后,再输出这个n值。按正常来讲,结果应该是n = 100。可偏偏结果小于100。

其实产生这种结果的罪魁祸首就是我们经常提到的“脏数据”。而run方法中的yield()语句就是产生“脏数据”的始作俑者(不加yield语句 也可能会产生“脏数据”,但不会这么明显,只有将100改成更大的数,才会经常产生“脏数据”,在本例中调用yield就是为了放大“脏数据”的效果)。 yield方法的作用是使线程暂停,也就是使调用yield方法的线程暂时放弃CPU资源,使CPU有机会来执行其他的线程。为了说明这个程序如何产生 “脏数据”,我们假设只创建了两个线程:thread1和thread2。由于先调用了thread1的start方法,因此,thread1的run方 法一般会先运行。当thread1的run方法运行到第一行(int m = n;)时,将n的值赋给m。当执行到第二行的yield方法后,thread1就会暂时停止执行,而当thread1暂停时,thread2获得了CPU 资源后开始运行(之前thread2一直处于就绪状态),当thread2执行到第一行(int m = n;)时,由于thread1在执行到yield时n仍然是0,因此,thread2中的m获得的值也是0。这样就造成了thread1和thread2 的m获得的都是0。在它们执行完yield方法后,都是从0开始加1,因此,无论谁先执行完,最后n的值都是1,只是这个n被thread1和 thread2各赋了一遍值。这个过程如下图如示:



 

也许有人会问,如果只有n++,会产生“脏数据”吗?答案是肯定的。那么n++只是一条语句,又如何在执行过程中将CPU交给其他的线程呢?其实这 只是表面现象,n++在被Java编译器编译成中间语言(也叫做字节码)后,并不是一条语言。让我们看看下面的Java代码将会被编译成什么样的Java 中间语言。

Java源代码

public void run()
{
    n++;
}

被编译后的中间语言代码
  001  public void run()
  002  {
  003      aload_0         
  004      dup             
  005      getfield
  006      iconst_1        
  007      iadd            
  008      putfield       
  009      return          
  010  }

 

大家可以看到在run方法中只有n++一条语句,而在编译后,却有7条中间语言语句。我们并不需要知道这些语句的功能是什么,只看一下第005、 007和008行语句。在005行是getfield,根据它的英文含义可知是要得到某个值,因为这里只有一个n,所以毫无疑问,是要得到n的值。而在 007行的iadd也不难猜测是将这个得到的n值加1。在008行的putfield的含义我想大家可能已经猜出来了,它负责将这个加1后的n再更新回类 变量n。说到这,可能大家还有一个疑惑,执行n++时直接将n加1不就行了,为什么要如此费周折。其实这里涉及到一个Java内存模型的问题。

Java的内存模型分为主存储区和工作存储区。主存储区保存了Java中所有的实例。也就是说,在我们使用new来建立一个对象后,这个对象及它内 部的方法、变量等都保存在这一区域,在MyThread类中的n就保存在这个区域。主存储区可以被所有线程共享。而工作存储区就是我们前面所讲的线程栈, 在这个区域里保存了在run方法以及run方法所调用的方法中定义的变量,也就是方法变量。在线程要修改主存储区中的变量时,并不是直接修改这些变量,而 是将它们先复制到当前线程的工作存储区,在修改完后,再将这个变量值覆盖主存储区的相应的变量值。

在了解了Java的内存模型后,就不难理解为什么n++也不是原子操作了。它必须经过一个拷贝、加1和覆盖的过程。这个过程和在MyThread类 中模拟的过程类似。大家可以想象,如果在执行到getfield时,thread1由于某种原因被中断,那么就会发生和MyThread类的执行结果类似 的情况。要想彻底解决这个问题,就必须使用某种方法对n进行同步,也就是在同一时间只能有一个线程操作n,这也称为对n的原子操作。

转自:http://blog.csdn.net/kjfcpua/article/details/9837521

  • 大小: 48.3 KB
分享到:
评论

相关推荐

    Java多线程初学者指南(7):向线程传递数据的三种方法.docx )

    Java 多线程初学者指南之向线程传递数据的三种方法 在多线程编程中,向线程传递数据是一项非常重要的任务。与传统的同步开发模式不同,在多线程异步开发模式下,数据的传递和返回方式有很大的区别。因此,本文将...

    Java多线程初学者指南

    Java多线程初学者指南是一份详尽的教育资源,涵盖了多线程编程的基本概念和实践技巧,适合初学者深入理解这一重要技术。本指南通过12篇文档,逐步引导学习者掌握Java多线程的核心知识。 首先,让我们从基础开始,...

    头歌java多线程基础-Java多线程基础详解与实战指南

    内容概要:本文详细介绍了Java多线程的基础...其他说明:本文通过实际案例和代码示例,深入浅出地讲解了Java多线程的核心知识点,有助于初学者快速入门并进阶。建议在学习过程中亲自编写并调试代码,以加深理解和记忆。

    java-Thread-study-summary.zip_java 多线程

    本资料“java-Thread-study-summary.zip”提供了一个深入学习Java多线程的综合指南,特别适合初学者进行实践操作。 1. **线程的创建** - 继承`Thread`类:创建一个新的类,该类继承自`Thread`,然后重写`run()`...

    Java多线程初学者指南:线程的生命周期.pdf

    本文将深入探讨Java多线程中的线程生命周期,以及如何控制线程的状态转换。 线程的生命周期通常包括五个基本状态:新建(New)、就绪(Runnable)、运行(Running)、阻塞(Blocked)和终止(Terminated)。下面...

    Mark Watson Java Programming 10-Minute Solutions

    - **多线程编程**:线程创建与管理、同步机制、线程池等。 ##### 4. 代码复用与模块化 - **设计模式**:单例模式、工厂模式、观察者模式等经典设计模式的应用场景。 - **模块化开发**:将大型项目分解为可独立开发...

    《java开发宝典-陈丹丹、李银龙》源代码+课件PPT

    《Java开发宝典》是陈丹丹和李银龙两位专家合著的一本关于Java编程的权威指南,这本书深入浅出地介绍了Java编程语言的核心概念和技术。提供的源代码和课件PPT为读者提供了丰富的实践资源,帮助学习者更好地理解和...

    Pro-Java-FX-2-A-Definitive-Guide-to-Rich-Clients-with-Java-Technology

    第六章《Collections and Concurrency》专注于JavaFX中处理集合数据的方法,以及如何利用多线程技术提高程序性能。 - **集合框架**:这一节介绍了JavaFX中用于处理集合数据的API。 - **并发编程**:这部分详细讲解...

    想学java,新手学java,怎样才能学好java,java视频教程,零基础学习java--java学习指导文档.doc

    对于初学者来说,了解 Java 的基础知识至关重要。这不仅能够帮助理解后续更复杂的概念,还能为将来深入学习 Java 打下坚实的基础。 ##### 1. Java 简介 - **历史背景**:Java 由 Sun Microsystems 在 1995 年发布...

    Java多线程基础学习指南:原理、实现与实战

    内容概要:本文详细介绍了Java多线程的基本概念、实现方式、线程控制方法、线程同步、线程池及其应用。首先解释了线程的概念及其优势,接着讲述了如何通过继承Thread类、实现Runnable接口和使用Callable接口来创建多...

    JAVA多线程模型详解

    本文将深入探讨Java多线程模型的相关知识点,包括线程与进程的区别、线程的实现原理、线程的创建方法以及线程的阻塞与唤醒机制等,旨在为初学者提供一个清晰的多线程概念理解和使用指南。 一、线程与进程的区别 在...

    Java-beginners-advice.rar_beginners

    Java初学者指南 Java是一种广泛使用的面向对象的编程语言,以其跨平台的特性、丰富的类库和强大的功能而闻名。对于新手来说,学习Java可能会感到有些挑战,但通过正确的指导和实践,任何人都可以掌握它。以下是一些...

    Java Programming 24-Hour Trainer

    《Java编程24小时训练营》是一本非常适合Java初学者的教材。本书通过精心设计的课程安排,帮助读者在有限的时间内掌握Java编程的基础知识及进阶技能。本书不仅涵盖了Java的基本语法和面向对象编程的核心概念,还深入...

    MCA 多线程&高并发.zip

    《MCA多线程&高并发》...本教程覆盖了多线程与高并发的基础理论、实践技巧以及优化策略,适合Java初学者和有一定经验的开发者深入学习,通过学习,你将具备处理复杂并发问题的能力,提升你的编程技能和项目实战水平。

    java项目开发实战

    根据提供的文件信息,“Java项目开发实战”这一主题主要聚焦于Java项目的实际开发过程与技巧,适合初学者作为入门指南来了解Java项目开发的基本流程和技术要点。下面将从多个角度深入探讨这一主题涉及的关键知识点。...

    java初学者精髓!

    Java初学者精华指南 在Java编程领域,学习之路往往始于基础,然后逐步深入到复杂的框架应用。本指南针对Java初学者,将引导你从简单的控制流构造,如"for"循环,逐步过渡到企业级开发中常用的Spring、Struts和...

    Java软件开发实战 Java基础与案例开发详解 1-4 java SE环境安装和配置 共11页.pdf

    ### Java软件开发实战知识点梳理 #### 一、Java SE环境安装和配置 1. **什么是JDK** - **定义**:JDK(Java Development ...以上内容覆盖了从Java环境搭建到高级特性的详细介绍,为初学者提供了全面的Java学习指南。

Global site tag (gtag.js) - Google Analytics