`

Java多线程(9):为什么要进行数据同步

阅读更多
  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的原子操作。
分享到:
评论

相关推荐

    java多线程笔记

    Java线程:概念与原理 2 一、操作系统中线程和进程的概念 2 二、Java中的线程 3 三、Java中关于线程的名词解释 3 四、线程的状态转换和生命周期 4 Java线程:创建与启动 7 ...Java线程:线程之间的数据传递 58

    java多线程同步问题

    多线程注意:wait()方法的调用要有判定条件常用 while () obj.wait(timeout, nanos); ... // Perform action appropriate to condition } synchronized会影响共享数据,但对其他语句的执行不会有规律了!

    java 多线程同步

    不客气地说,创建 java.util.concurrent 的目的就是要实现 Collection 框架对数据结构所执行的并发操作。通过提供一组可靠的、高性能并发构建块,开发人员可以提高并发类的线程安全、可伸缩性、性能、可读性和可靠性...

    JAVA 线程实现数据库的主从同步更新

    JAVA代码采用线程来实现数据库的主从同步更新

    java 多线程设计模式 进程详解

    《JAVA多线程设计模式》PDF 下载 《Java线程 高清晰中文第二版》中文第二版(PDF) 前言 第一章 线程简介 Java术语 线程概述 为什么要使用线程? 总结 第二章 Java线程API 通过Thread类创建线程 使用Runable接口...

    多线程导入excel 数据

    java 多线程导入excel数据,预防高并发,线程同步锁,

    Java、Android线程安全与多线程数据同步

    NoHttp核心架构之多线程通信、线程安全、线程同步;synchronized锁,Lock锁;具体讲解请移步:http://blog.csdn.net/yanzhenjie1003/article/details/50992468

    java多线程编程之为什么要进行数据同步

    数据同步就是指在同一时间,只能由一个线程来访问被同步的类变量,当前线程访问完这些变量后,其他线程才能继续访问,下面看一下为什么要进行数据同步

    Java多线程数据同步处理的研究分析.pdf

    Java多线程数据同步处理的研究分析

    Java多线程优化百万级数据

    通过实例给出利用Java多线程优化读取数据库百万级别数据

    Java多线程编程实战之模拟大量数据同步

    主要介绍了Java多线程编程实战之模拟大量数据同步,文中通过示例代码介绍的非常详细,对大家的学习或者工作具有一定的参考学习价值,需要的朋友们下面随着小编来一起学习学习吧

    java并发编程:线程基础

    解释为什么多线程可以提高程序性能和资源利用率。 线程的创建: 详细讲解线程的创建方式,包括继承 Thread 类和实现 Runnable 接口。演示如何通过这些方式来创建和启动线程。 线程的生命周期: 解释线程的生命周期,...

    java多线程编程_java多线程_

    1.讲解了Java多线程的基础, 包括Thread类的核心API的使用。2.讲解了在多线程中对并发访问的控制, 主要就是synchronized的使用, 由于此关键字在使用上非常灵活, 所以书中用了很多案例来介绍此关键字的使用, 为...

    图解java多线程

    本文主要讲java中多线程的使用方法、线程同步、线程数据传递、线程状态及相应的线程函数用法、概述等。首先让我们来了解下在操作系统中进程和线程的区别:

    秒杀多线程第十六篇 多线程十大经典案例之一 双线程读写队列数据

    在《秒杀多线程系列》的前十五篇中介绍多线程的相关概念,多线程同步互斥问题《秒杀多线程第四篇一个经典的多线程同步问题》及解决多线程同步互斥的常用方法——关键段、事件、互斥量、信号量、读写锁。为了让大家...

    Java多线程编程 线程同步机制.docx

    线程安全问题的产生是因为多个线程并发访问共享数据造成的,如果能将多个线程对共享数据的并发访问改为串行访问,即一个共享数据同一时刻只能被一个线程访问,就可以避免线程安全问题。锁正是基于这种思路实现的一种...

    某电信项目多线程同步数据实例

    某电信项目多线程同步数据实例,经生产测试,一分钟同步数据量20W

    JNI 多线程同步机制的源码实现

    设计一个多线程, 并且实现同步, 我理解的多线程需求如下: 1. 线程在Java端启动, 两个线程都调用C的方法 2. 有一个共同的数据, 被C的代码修改, 要求线程能对这个修改做同步, 即线程1

    Java毕业设计-Java多线程与线程安全实践-基于Http协议的断点续传.rar

    Java多线程与线程安全实践-基于Http协议的断点续传.rar 是一个Java毕业设计项目,旨在探讨如何在Java中实现多线程和线程安全,以及如何基于Http协议实现断点续传功能。该项目提供了一个完整的源代码包,可以作为学习...

    java实现多线程购票系统

    模拟电影院的多线程购票系统,使用同步锁机制保证数据的安全,同时使用集合的减法来进行,顾客买票时选购位置

Global site tag (gtag.js) - Google Analytics