为了确保可以在线程之间以受控方式共享数据,Java 语言提供了两个关键字:
synchronized 和
volatile。
Synchronized 有两个重要含义:它确保了一次只有一个线程可以执行代码的受保护部分(互斥,mutual exclusion 或者说 mutex),而且它确保了一个线程更改的数据对于其它线程是可见的(更改的可见性)。
如果没有同步,数据很容易就处于不一致状态。例如,
如果一个线程正在更新两个相关值(比如,粒子的位置和速率),而另一个线程正在读取这两个值,有可能在第一个线程只写了一个值,还没有写另一个值的时候,调度第二个线程运行,这样它就会看到一个旧值和一个新值。同步让我们可以定义必须原子地运行的代码块,这样对于其他线程而言,它们要么都执行,要么都不执行。
同步的原子执行或互斥方面类似于其它操作环境中的临界段的概念
确保共享数据更改的可见性
同步可以让我们确保线程看到一致的内存视图。
处理器可以使用高速缓存加速对内存的访问(或者编译器可以将值存储到寄存器中以便进行更快的访问)。在一些多处理器体系结构上,如果在一个处理器的高速缓存中修改了内存位置,没有必要让其它处理器看到这一修改,直到刷新了写入器的高速缓存并且使读取器的高速缓存无效。
这表示在这样的系统上,对于同一变量,在两个不同处理器上执行的两个线程可能会看到两个不同的值!这听起来很吓人,但它却很常见。它只是表示在访问其它线程使用或修改的数据时,必须遵循某些规则。
Volatile 比同步更简单,只适合于控制对基本变量(整数、布尔变量等)的单个实例的访问。当一个变量被声明成 volatile,任何对该变量的写操作都会绕过高速缓存,直接写入主内存,而任何对该变量的读取也都绕过高速缓存,直接取自主内存。这表示所有线程在任何时候看到的 volatile 变量值都相同。
如果没有正确的同步,线程可能会看到旧的变量值,或者引起其它形式的数据损坏
用锁保护的原子代码块
Volatile 对于确保每个线程看到最新的变量值非常有用,但有时我们需要保护比较大的代码片段,如涉及更新多个变量的片段。
同步使用监控器(monitor)或锁的概念,以协调对特定代码块的访问。
每个 Java 对象都有一个相关的锁。同一时间只能有一个线程持有 Java 锁。
当线程进入 synchronized 代码块时,线程会阻塞并等待,直到锁可用,当它可用时,就会获得这个锁,然后执行代码块。当控制退出受保护的代码块时,即到达了代码块末尾或者抛出了没有在 synchronized 块中捕获的异常时,它就会释放该锁。
这样,每次只有一个线程可以执行受给定监控器保护的代码块。从其它线程的角度看,该代码块可以看作是原子的,它要么全部执行,要么根本不执行
简单的同步示例
使用 synchronized 块可以让您将一组相关更新作为一个集合来执行,而不必担心其它线程中断或看到计算的中间结果。以下示例代码将打印“1 0”或“0 1”。如果没有同步,它还会打印“1 1”(或“0 0”,随便您信不信)。
[b]public class SyncExample {
private static lockObject = new Object();
private static class Thread1 extends Thread {
public void run() {
synchronized (lockObject) {
x = y = 0;
System.out.println(x);
}
}
}
private static class Thread2 extends Thread {
public void run() {
synchronized (lockObject) {
x = y = 1;
System.out.println(y);
}
}
}
public static void main(String[] args) {
new Thread1().run();
new Thread2().run();
}[/b]}
这个例子要看到内部中去:当执行其中任何一个方法时候,x和y被赋1或者0,如果这时候另一个线程抢去资源,使用system。out。print(x)时,将和原来的一样
在这两个线程中都必须使用同步,以便使这个程序正确工作
Java 锁定J
ava 锁定合并了一种互斥形式。每次只有一个线程可以持有锁。锁用于保护代码块或整个方法,必须记住是锁的身份保护了代码块,而不是代码块本身,这一点很重要。一个锁可以保护许多代码块或方法。
反之,仅仅因为代码块由锁保护并不表示两个线程不能同时执行该代码块。它只表示如果两个线程正在等待相同的锁,则它们不能同时执行该代码。
在以下示例中,两个线程可以同时不受限制地执行 setLastAccess() 中的 synchronized 块,因为每个线程有一个不同的 thingie 值。因此,synchronized 代码块受到两个正在执行的线程中不同锁的保护。
public class SyncExample {
public static class Thingie {
private Date lastAccess;
public synchronized void setLastAccess(Date date) {
this.lastAccess = date;
}
}
public static class MyThread extends Thread {
private Thingie thingie;
public MyThread(Thingie thingie) {
this.thingie = thingie;
}
public void run() {
thingie.setLastAccess(new Date());
}
}
public static void main() {
Thingie thingie1 = new Thingie(),
thingie2 = new Thingie();
new MyThread(thingie1).start();
new MyThread(thingie2).start();
}
}
同步的方法
创建 synchronized 块的最简单方法是将方法声明成 synchronized。这表示在进入方法主体之前,调用者必须获得锁:
public class Point {
public synchronized void setXY(int x, int y) {
this.x = x;
this.y = y;
}
}
对于普通的 synchronized方法,
这个锁是一个对象,将针对它调用方法。
对于静态 synchronized 方法,这个锁是与 Class 对象相关的监控器,在该对象中声明了方法。
仅仅因为 setXY() 被声明成 synchronized 并不表示两个不同的线程不能同时执行 setXY(),只要它们调用不同的 Point 实例的 setXY() 就可同时执行。对于一个 Point 实例,一次只能有一个线程执行 setXY(),或 Point 的任何其它 synchronized 方法。
大多数类并没有同步
因为同步会带来小小的性能损失,大多数通用类,如 java.util 中的 Collection 类,不在内部使用同步。这表示在没有附加同步的情况下,不能在多个线程中使用诸如 HashMap 这样的类。
通过每次访问共享集合中的方法时使用同步,可以在多线程应用程序中使用 Collection 类。对于任何给定的集合,每次必须用同一个锁进行同步。通常可以选择集合对象本身作为锁。
下一页中的示例类 SimpleCache 显示了如何使用 HashMap 以线程安全的方式提供高速缓存。但是,通常适当的同步并不只是意味着同步每个方法。
Collections 类提供了一组便利的用于 List、Map 和 Set 接口的封装器。您可以用 Collections.synchronizedMap 封装 Map,它将确保所有对该映射的访问都被正确同步。
如果类的文档没有说明它是线程安全的,那么您必须假设它不是
示例:简单的线程安全的高速缓存
如以下代码样本所示,SimpleCache.java 使用 HashMap 为对象装入器提供了一个简单的高速缓存。load() 方法知道怎样按对象的键装入对象。在一次装入对象之后,该对象就被存储到高速缓存中,这样以后的访问就会从高速缓存中检索它,而不是每次都全部地装入它。对共享高速缓存的每个访问都受到 synchronized 块保护。由于它被正确同步,所以多个线程可以同时调用 getObject 和 clearCache 方法,而没有数据损坏的风险。
public class SimpleCache {
private final Map cache = new HashMap();
public Object load(String objectName) {
// load the object somehow
}
public void clearCache() {
synchronized (cache) {
cache.clear();
}
}
public Object getObject(String objectName) {
synchronized (cache) {
Object o = cache.get(objectName);
if (o == null) {
o = load(objectName);
cache.put(objectName, o);
}
}
return o;
}
}
小结
就象程序一样,线程有生命周期:它们启动、执行,然后完成。一个程序或进程也许包含多个线程,而这些线程看来互相单独地执行。
线程是通过实例化 Thread 对象或实例化继承 Thread 的对象来创建的,但在对新的 Thread 对象调用 start() 方法之前,这个线程并没有开始执行。当线程运行到其 run() 方法的末尾或抛出未经处理的异常时,它们就结束了。
sleep() 方法可以用于等待一段特定时间;而 join() 方法可能用于等到另一个线程完成。
分享到:
相关推荐
在多线程编程中,数据同步是一个至关重要的概念,它涉及到如何确保多个线程在并发访问共享资源时能正确地协调工作,避免数据竞争和不一致的状态。标题"多线程数据同步"直指这一核心问题,而描述则具体提到了使用临界...
为了应对这一问题,本研究提出了基于企业服务总线(ESB,Enterprise Service Bus)的共享数据中心解决方案,以实现数据同步。在分析了多种数据同步技术后,我们发现ESB作为中间件能有效集成和协调分布式系统,减少...
本文主要探讨的是基于数据同步的分布式数据共享实现方案。 在分布式系统架构中,数据被划分为多个部分,根据时间、功能、ID等维度进行垂直和水平切分,并在不同的节点上存储。这提升了系统的可扩展性和性能,但同时...
这种技术使得用户能够在不同的设备间无缝地共享和更新数据,从而提高工作效率和生活便利性。数据同步主要涉及到四个关键部分,分别是同步协议、数据模型、同步策略以及安全与隐私保护。 首先,同步协议是实现数据...
在IT行业中,数据同步是一个关键的概念,特别是在分布式系统、客户端-服务器架构以及多设备协作的环境中。本篇文章将深入探讨“tongbu.rar”压缩包所涉及的C#语言中的数据同步技术,以及如何实现客户端和服务器端...
在实际应用中,为了保证数据的一致性和避免冲突,通常会引入互斥量(Mutex)或信号量(Semaphore)等同步机制,确保只有一个进程在任何时候对共享内存进行写操作。 例如,一个进程可能负责填充共享内存的数据,而另...
在Qt框架中,进程间通信(IPC,Inter-Process Communication)和进程同步是两个关键概念,用于在多个独立运行的程序之间交换数据和协调执行。本示例将深入讲解如何利用Qt库中的特性来实现这些功能,特别是通过QFile...
SQL Server 2000 数据同步是数据库管理和高可用性解决方案的关键组成部分,主要目的是确保数据在多个服务器之间的一致性和实时更新。以下将详细解释这一过程涉及的技术、元素和应用场景。 首先,数据同步在SQL ...
- **Mutex类**:这是一个跨进程的同步对象,可以用来协调不同进程中的线程访问共享资源。`Mutex`类通常用于需要更细粒度控制的情况。 示例代码: ```csharp Mutex objMutex = new Mutex(false, "ThreadLock"); ...
在现代军事通信系统中,Link-22数据链技术作为战术通信网络的重要组成部分,其高效和可靠的性能对作战指挥、信息共享与资源协调具有关键性影响。Link-22数据链系统中的时间同步及时隙分配技术,不仅是保障信息准确...
3. 混合共享模式:结合中心化和分布式的特点,部分重要或高频访问的数据集中存储,其余数据分布存储。这种方式兼顾了效率和灵活性。 三、挑战与解决方案 1. 平台异构性:针对不同的操作系统和硬件环境,需要开发...
在数据库、网络服务器、分布式系统等多进程环境中,这些同步机制经常被用来协调不同进程对共享资源的访问,如并发请求处理、数据一致性维护等。 总结来说,互斥锁、条件变量和共享内存是Linux下实现进程间通信的...
OGG安装在节点一的`/ogg`目录,该目录应是一个共享存储,以便于不同节点间的协调。每个实例有自己的数据库路径,例如`p1edadb1`和`p1edadb2`,并且配置了管理端口7809用于监控和管理。 - **目标端**:Kafka集群,...
这样,可以确保在任何时刻只有一个窗口能够访问共享数据。 此外,标签“独立团论坛-从零开始学游戏辅助编程技术.url”可能提示这是一个关于游戏辅助编程的示例。在游戏编程中,窗口同步尤为重要,因为游戏通常包含...
1. 定义一个记录类型或类,用于存储共享数据。 2. 使用`TMemoryMappedFile`创建内存映射文件,分配必要的内存大小。 3. 实现读写共享内存的函数或方法,确保在多线程环境中的同步控制,例如使用`TCriticalSection`或...
- **同步问题**:由于多个进程可以同时访问共享内存,所以必须使用信号量(semaphores)或其他同步机制来防止竞争条件(race conditions)。 - **内存管理**:创建和销毁共享内存需要正确管理,以免内存泄漏或资源...
在多线程环境中,同步是指协调多个线程对共享资源的访问,避免数据竞争和不一致状态。在.NET中,有多种同步技术可供开发者使用,包括锁(Mutex, Monitor, Semaphore)、事件(EventWaitHandle)和条件变量(Monitor....
线程同步机制包括互斥锁(Mutex)、读写锁(Read-Write Locks)、条件变量(Condition Variables)等,确保了对共享数据的正确访问。 **Semaphore(信号量)**: 信号量是一种经典的同步原语,由P(Wait)和V...
在给定的标题“单点和数据同步中间件 jfinal实现”中,我们可以看到一个使用JFinal框架实现的中间件,其主要功能集中在数据同步和单点登录(Single Sign-On, SSO)。 JFinal是基于Java的轻量级Web开发框架,它的...
在这个“Goldengate 12c RAC 到单实例同步--归档在共享设备搭建案例”中,我们将深入探讨如何利用 GoldenGate 的功能,将一个 RAC (Real Application Clusters) 集群环境中的数据同步到单个实例数据库。 RAC 是 ...