`

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编程中,多线程处理是提升程序性能和效率的重要手段,特别是在处理大量数据库数据时。本主题将深入探讨如何使用Java的并发包(java.util.concurrent)来实现多线程对数据库数据的批量处理,包括增、删、改等...

    java 多线程同步

    Java多线程同步是Java编程中关键的并发概念,它涉及到如何在多个线程访问共享资源时保持数据的一致性和完整性。`java.util.concurrent`包是Java提供的一个强大的并发工具库,它为开发者提供了多种线程安全的工具,...

    java多线程经典案例

    通过分析并实践`threadTest`案例,我们可以深入理解Java多线程的原理和使用技巧,为编写高效并发程序打下坚实基础。同时,也要注意多线程编程中的死锁、活锁和饥饿等问题,合理设计线程间的交互,避免出现不可预期的...

    Java多线程机制(讲述java里面与多线程有关的函数)

    Java多线程机制是Java编程中至关重要的一部分,它允许程序同时执行多个任务,提升应用程序的效率和响应性。以下是对各个知识点的详细说明: 9.1 Java中的线程: Java程序中的线程是在操作系统级别的线程基础上进行...

    java多线程Demo

    Java多线程是Java编程中的一个重要概念,它允许程序同时执行多个任务,提高了程序的效率和响应速度。在Java中,实现多线程有两种主要方式:继承Thread类和实现Runnable接口。 1. 继承Thread类: 当我们创建一个新...

    java多线程查询数据库

    综上所述,"java多线程查询数据库"是一个涉及多线程技术、线程池管理、并发控制、分页查询等多个方面的复杂问题。通过理解和掌握这些知识点,我们可以有效地提高数据库操作的效率和系统的响应速度。

    java多线程的讲解和实战

    Java多线程是Java编程中的重要概念,尤其在如今的多核处理器环境下,理解并熟练掌握多线程技术对于提高程序性能和响应速度至关重要。本资料详细讲解了Java多线程的原理,并提供了丰富的实战代码,非常适合Java初学者...

    java多线程导出excel(千万级别)优化

    Java多线程导出Excel是处理大数据量时的一种高效策略,尤其在面对千万级别的数据时。传统的Apache POI库在处理大规模数据时可能会遇到栈溢出(StackOverflowError)和内存溢出(OutOfMemoryError)等问题,因为这些...

    java多线程同步问题

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

    java多线程进度条

    总之,实现Java多线程进度条涉及线程同步、共享数据更新以及UI更新的协调。理解这些核心概念,并根据具体需求选择合适的方法,是构建高效、用户友好进度条的关键。在ProgressTest这个示例项目中,你可能会找到更多...

    Java多线程优化百万级数据

    总结来说,Java多线程优化在处理百万级数据时扮演着关键角色。通过合理地拆分任务、使用线程池、选择合适的并发容器和同步机制,以及优化数据库交互,我们可以显著提升程序的运行效率,改善用户体验。同时,不断监控...

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

    在Java编程环境中,...总之,使用Java线程实现数据库主从同步更新是一种常见且实用的技术手段,它涉及到多线程编程、数据库操作、事务管理等多个方面。理解和掌握这些知识点对于开发高可用性的分布式系统至关重要。

    JAVA多线程实现数据库之间的数据互导、连接池、及多表插入数据库功能

    通过以上知识的学习和实践,开发者可以熟练地在Java环境中实现多线程数据互导,高效使用数据库连接池,以及进行多表插入操作,提升系统的并发处理能力和性能。在实际工作中,还需要根据具体需求和场景,灵活运用这些...

    java 多线程并发实例

    在Java编程中,多线程并发是...总之,Java的多线程并发实例可以帮助我们更好地理解和实践线程控制、同步机制以及经典的设计模式,提升我们的编程能力。通过不断学习和实践,我们可以编写出高效、安全的多线程并发程序。

    JAVAJAVA多线程教学演示系统论文

    《JAVA多线程教学演示系统》是一篇深入探讨JAVA多线程编程的论文,它针对教育领域中的教学需求,提供了一种生动、直观的演示方式,帮助学生更好地理解和掌握多线程技术。这篇论文的核心内容可能包括以下几个方面: ...

    深入浅出 Java 多线程.pdf

    在本文中,我们将深入浅出Java多线程编程的世界,探索多线程编程的基本概念、多线程编程的优点、多线程编程的缺点、多线程编程的应用场景、多线程编程的实现方法等内容。 一、多线程编程的基本概念 多线程编程是指...

    深入学习:Java多线程编程

    《深入学习:Java多线程编程》是一本...通过学习《深入学习:Java多线程编程》,开发者可以提升在复杂并发环境下的编程能力,为开发高并发、高可用的应用打下坚实的基础。这本书无疑是Java开发者进阶必备的参考书之一。

    java多线程设计

    本知识点将深入探讨Java多线程设计以及如何利用“不可变对象”(immutable objects)来避免多线程环境中的非安全问题。 一、Java多线程基础 1. 线程的创建:Java提供了两种创建线程的方式——继承Thread类和实现...

    Java 多线程 PPT

    本文将深入探讨Java多线程的相关概念、线程类和接口的使用,以及线程的同步与互斥。 首先,我们需要理解进程与线程的基本概念。程序是一组静态指令的集合,而进程则是程序在执行过程中的一个实例,拥有独立的内存...

Global site tag (gtag.js) - Google Analytics