<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); } } |
在履行上面代码的大概后果以下:
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编程]”的内容,如果你对以上该文章内容感兴趣,你可以看看七道奇为您推荐以下文章:
本文地址: | 与您的QQ/BBS好友分享! |