`

Java线程间通信和同步

    博客分类:
  • java
阅读更多

线程之间的通信机制有两种:共享内存和消息传递。

共享内存的并发模型里,线程之间共享程序的公共状态,线程之间通过写-读内存中的公共状态来隐式进行通信。

消息传递的并发模型里,线程之间没有公共状态,线程之间必须通过明确的发送消息来显式进行通信。

 

同步是指程序用于控制不同线程之间操作发生相对顺序的机制。

共享内存并发模型里,同步是显式进行的。程序员必须显式指定某个方法或某段代码需要在线程之间互斥执行。

在消息传递的并发模型里,由于消息的发送必须在消息的接收之前,因此同步是隐式进行的。

 

Java的并发采用的是共享内存模型 

Java内存模型的抽象

在java中,所有实例域、静态域和数组元素存储在堆内存中,堆内存在线程之间共享(“共享变量”代指实例域,静态域和数组元素)。

局部变量(Local variables),方法定义参数(java语言规范称之为formal method parameters)和异常处理器参数(exception handler parameters)不会在线程之间共享,它们不会有内存可见性问题,也不受内存模型的影响。

 

Java线程之间的通信由Java内存模型(本文简称为JMM)控制,JMM决定一个线程对共享变量的写入何时对另一个线程可见。

从抽象的角度来看,JMM定义了线程和主内存之间的抽象关系:

线程之间的共享变量存储在主内存中,每个线程都有一个私有的本地内存,本地内存中存储了该线程以读/写共享变量的副本。

Java内存模型的抽象示意图如下:



 

从上图来看,线程A与线程B之间如要通信的话,必须要经历下面2个步骤:

首先,线程A把本地内存A中更新过的共享变量刷新到主内存中去。

然后,线程B到主内存中去读取线程A之前已更新过的共享变量。

 

本地内存A和B有主内存中共享变量x的副本。假设初始时,这三个内存中的x值都为0。线程A在执行时,把更新后的x值(假设值为1)临时存放在自己的本地内存A中。

当线程A和线程B需要通信时,线程A首先会把自己本地内存中修改后的x值刷新到主内存中,此时主内存中的x值变为了1。

随后,线程B到主内存中去读取线程A更新后的x值,此时线程B的本地内存的x值也变为了1。

从整体来看,这两个步骤实质上是线程A在向线程B发送消息,而且这个通信过程必须要经过主内存。

JMM通过控制主内存与每个线程的本地内存之间的交互,来为java程序员提供内存可见性保证。

 

锁提供了两种主要特性:互斥 和可见性。

互斥即一次只允许一个线程持有某个特定的锁,因此可使用该特性实现对共享数据的协调访问协议,这样,一次就只有一个线程能够使用该共享数据。

可见性要更加复杂一些,它必须确保释放锁之前对共享数据做出的更改对于随后获得该锁的另一个线程是可见的。

—— 如果没有同步机制提供的这种可见性保证,线程看到的共享变量可能是修改前的值或不一致的值,这将引发许多严重问题。

(默认线程退出前,线程本地内存才会同步到主内存,而synchronized保证了互斥和可见性,在退出synchronized块后,就刷回到主内存中,无需退出线程)。

 

Java 语言中的 volatile 变量可以被看作是一种 “程度较轻的 synchronized”;与 synchronized 块相比,volatile 变量所需的编码较少,并且运行时开销也较少,但是它所能实现的功能也仅是 synchronized 的一部分。

 

在JVM 1.2之前,Java的内存模型实现总是从主存读取变量,是不需要进行特别的注意的。而随着JVM的成熟和优化,现在在多线程环境下volatile关键字的使用变得非常重要。

 

在当前的Java内存模型下,线程可以把变量保存在本地内存(比如机器的寄存器)中,而不是直接在主存中进行读写。这就可能造成一个线程在主存中修改了一个变量的值,而另外一个线程还继续使用它在寄存器中的变量值的拷贝,造成数据的不一致。

 

要解决这个问题,只需要把该变量声明为volatile即可,这就指示JVM,这个变量是不稳定的,每次使用它都到主存中进行读取。一般说来,多任务环境下各任务间共享的标志都应该加volatile修饰。

 

Volatile修饰的成员变量在每次被线程访问时,都强迫从共享内存中重读该成员变量的值。而且,当成员变量发生变化时,强迫线程将变化值回写到共享内存。这样在任何时刻,两个不同的线程总是看到某个成员变量的同一个值。

 

Java语言规范中指出:为了获得最佳速度,允许线程保存共享成员变量的私有拷贝,而且只当线程进入或者离开同步代码块时才与共享成员变量的原始值对比。

 

这样当多个线程同时与某个对象交互时,就必须要注意到要让线程及时的得到共享成员变量的变化。

volatile关键字就是提示VM:对于这个成员变量不能保存它的私有拷贝,而应直接与共享成员变量交互。

 

使用建议:在两个或者更多的线程访问的成员变量上使用volatile。当要访问的变量已在synchronized代码块中,或者为常量时,不必使用。

 

由于使用屏蔽掉了VM中必要的代码优化,所以在效率上比较低,因此一定在必要时才使用此关键字。

 

public class VolatileTest{  

  public volatile int a;  

  public void add(int count){  

       a=a+count;  

  }  

}  

 当一个VolatileTest对象被多个线程共享,a的值不一定是正确的,因为a=a+count包含了好几步操作,而此时多个线程的执行是无序的,因为没有任何机制来保证多个线程的执行有序性和原子性。volatile存在的意义是,任何线程对a的修改,都会马上被其他线程读取到,因为直接操作主存,没有线程对工作内存和主存的同步。所以,volatile的使用场景是有限的,在有限的一些情形下可以使用 volatile 变量替代锁。要使 volatile 变量提供理想的线程安全,必须同时满足下面两个条件:

 

1)对变量的写操作不依赖于当前值。

2)该变量没有包含在具有其他变量的不变式中 

volatile只保证了可见性,所以Volatile适合直接赋值的场景,如

public class VolatileTest{  

  public volatile int a;  

  public void setA(int a){  

      this.a=a;  

  }  

}  

 

简单来说,volatile适合这种场景:一个变量被多个线程共享,线程直接给这个变量赋值。这是一种很简单的同步场景,这时候使用volatile的开销将会非常小。

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

相关推荐

    关于Java线程间通信-回调.docx

    总的来说,回调在Java线程间通信中起到桥梁的作用,使得线程能够以非阻塞的方式互相协作,提高了程序的并发性能和响应速度。理解并熟练掌握回调以及其他线程通信机制是Java并发编程的关键,这对于开发高效、稳定的多...

    Java线程间通信的代码示例.zip

    Java线程间通信是多线程编程中的一个重要概念,它涉及到如何在并发执行的线程之间有效地传递信息和协调工作。在Java中,线程间通信主要通过共享内存(如共享变量)和消息传递(如wait(), notify(), notifyAll()等...

    java多线程代码案例(创建线程,主线程,线程优先级,线程组,线程同步,线程间的通信)

    Java提供了多种线程间通信的手段,如`BlockingQueue`、`Future`、`ExecutorService`等。其中,`wait()`, `notify()`, `notifyAll()`是基于对象监视器的通信方式,用于在线程间传递信号。`BlockingQueue`则提供了...

    JAVA100例之实例64 JAVA线程间通讯

    在"JAVA100例之实例64 JAVA线程间通讯"这个主题中,我们将深入探讨Java中实现线程间通信的几种主要方法。 1. **共享数据**:最直观的线程间通信方式是通过共享内存空间,即共享变量。只要对共享变量的操作是线程...

    java线程同步及通信

    2. **线程间通信**: 在多线程环境中,线程之间可能需要交换数据或协调工作。Java提供了一些机制,如`wait()`、`notify()`和`notifyAll()`方法,这些方法存在于`Object`类中,用于线程间的通信。在`Q.java`的`get()...

    Java线程间通信不同步问题原理与模拟实例

    总结来说,Java线程间通信不同步问题可能导致数据不一致性和程序错误,解决这个问题的关键在于正确地使用同步机制,防止多个线程同时访问共享资源。通过理解和实践各种同步策略,开发者可以编写出更加健壮的多线程...

    JAVA实现线程间同步与互斥生产者消费者问题

    2. **wait()和notify()方法**:这些方法位于`Object`类中,可以用于线程间的通信。当一个线程调用`wait()`时,它会释放对象锁并进入等待状态,直到其他线程调用该对象的`notify()`或`notifyAll()`方法将其唤醒。在这...

    Java的多线程-线程间的通信.doc

    - Java 5引入了Lock接口和Condition接口,作为synchronized关键字的替代品,提供了更灵活的线程同步和通信。Lock接口提供了lock(), unlock(), newCondition()等方法,Condition接口则提供了await(), signal()和...

    Java 线程通信示例 源代码

    3. **wait(), notify(), notifyAll() 方法**:这些方法是Object类的成员,用于线程间通信。在线程A调用`wait()`后,它会被放入等待池,释放锁并暂停执行,直到其他线程调用同一对象的`notify()`或`notifyAll()`唤醒...

    Java线程间的通信方式详解

    Java线程间的通信是多线程编程中的重要概念,它涉及到如何协调多个并发执行的线程,确保数据的一致性和正确性。本文将详细介绍两种常见的Java线程通信方式:同步和while轮询。 1. 同步(Synchronized) 同步是Java...

    java线程.pdf

    Java线程间通信主要包括线程间的同步和线程间的协作两部分。常用的通信方法有`wait()`、`notify()`和`notifyAll()`等。 1. **wait()**:使当前线程暂停执行,并释放当前持有的锁。 2. **notify()**:唤醒正在等待该...

    java线程线程安全同步线程

    `wait()`和`notify()`/`notifyAll()`方法用于线程间的通信,使得线程可以在特定条件下释放资源并等待其他线程唤醒;`ReentrantLock`可重入锁提供了更灵活的控制,支持公平锁和非公平锁策略。 线程优先级是调度的...

    java_Thread.rar_java thread runable_thread runable

    Java线程间通信和同步是通过一些内置机制实现的,如: - `synchronized`关键字:用于控制对共享资源的访问,确保同一时刻只有一个线程能执行特定代码块。 - `wait()`, `notify()`, `notifyAll()`:这些方法在`Object...

    Java 线程的学习和使用

    4. Condition接口:与Lock一起使用,可以实现更复杂的线程间通信和同步。 5. volatile和Atomic类:提供原子操作,保证多线程环境下的数据一致性。 线程安全是Java多线程编程中的重要考虑因素,合理的线程管理和同步...

    深入解析Java的线程同步以及线程间通信

    总之,Java的线程同步和线程间通信是多线程编程中的关键概念,通过`synchronized`关键字、同步块和监视器锁,以及`wait()`, `notify()`, `notifyAll()`等方法,可以有效地管理共享资源,避免并发问题,并实现线程间...

    java 多线程同步

    信号和同步工具则提供了线程间通信和协调的机制,确保特定任务的顺序执行或并发限制。 在使用`java.util.concurrent`包时,了解并发基础知识是必要的,包括线程的创建、同步、中断和异常处理。线程的创建可以通过...

    java实现多线程间的通信

    Java 实现多线程间的通信在软件开发中至关重要,特别是在Android和Java应用开发中。本文将探讨线程间通信的概念,分析基于Java的多线程...理解线程同步、资源管理、并发控制和线程间通信是Java开发中不可或缺的技能。

    线程示例(有注释,包括同步,线程间通信)

    通过分析"线程示例(有注释,包括同步,线程间通信)"这个压缩包内的代码,我们可以学习如何在实际项目中创建、管理和同步线程,以及如何通过注释清晰地解释代码逻辑。文件名"T117_Fu_4"可能表示这是系列教程中的第...

Global site tag (gtag.js) - Google Analytics