当前位置:七道奇文章资讯编程技术Java编程
日期:2011-01-26 02:54:00  来源:本站整理

<b>Java多线程初学者指南(9):为什么要举行数据同步</b>[Java编程]

赞助商链接



  本文“<b>Java多线程初学者指南(9):为什么要举行数据同步</b>[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);
    }
}

    在履行上面代码的大概后果以下:

= 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的原子操作.


  以上是“<b>Java多线程初学者指南(9):为什么要举行数据同步</b>[Java编程]”的内容,如果你对以上该文章内容感兴趣,你可以看看七道奇为您推荐以下文章:
  • <b>hosts是什么 hosts文件在什么位置 若何改正hosts</b>
  • <b>在 Windows 8 中手动安装语言包</b>
  • <b>五个常见 PHP数据库问题</b>
  • Windows中Alt键的12个高效快速的利用本领介绍
  • <b>MySQL ORDER BY 的实现解析</b>
  • <b>详解MySQL存储历程参数有三种范例(in、out、inout)</b>
  • <b>Win8系统恢复出来经典的开始菜单的办法</b>
  • <b>Win8系统花屏怎么办 Win8系统花屏的办理办法</b>
  • <b>Windows 7系统下无线网卡安装</b>
  • <b>为什么 Linux不需求碎片整理</b>
  • <b>Windows 8中删除账户的几种办法(图)</b>
  • <b>教你如安在win7下配置路由器</b>
  • 本文地址: 与您的QQ/BBS好友分享!
    • 好的评价 如果您觉得此文章好,就请您
        0%(0)
    • 差的评价 如果您觉得此文章差,就请您
        0%(0)

    文章评论评论内容只代表网友观点,与本站立场无关!

       评论摘要(共 0 条,得分 0 分,平均 0 分) 查看完整评论
    Copyright © 2020-2022 www.xiamiku.com. All Rights Reserved .