`
klcwt
  • 浏览: 189469 次
  • 性别: Icon_minigender_1
  • 来自: 武汉
社区版块
存档分类
最新评论

共享数据的访问,其实就是协调同步

阅读更多
为了确保可以在线程之间以受控方式共享数据,Java 语言提供了两个关键字:synchronizedvolatile

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() 方法可能用于等到另一个线程完成。
分享到:
评论

相关推荐

    Linux系统编程之线程同步

    2. 调度随机(意味着数据访问会出现竞争) 3. 线程间缺乏必要的同步机制。 以上3点中,前两点不能改变,欲提高效率,传递数据,资源必须共享。只要共享资源,就一定会出现竞争。只要存在竞争关系,数据就很容易...

    详解c# 线程同步

    前面的文章都是讲创建多线程来实现让我们能够更好的响应应用程序,然而当我们创建了多个线程时,就存在多个线程同时访问一个共享的资源的情况,在这种情况下,就需要我们用到线程同步,线程同步可以防止数据(共享...

    [附源码+电子书]2023全新GO工程师面试总攻略,助力快速斩获offer

    并发安全性是指在并发编程中,多个goroutine对共享资源的访问不会导致数据竞争和不确定的结果。 为了确保并发安全性,可以采取以下措施: 使用互斥锁(Mutex):通过使用互斥锁来保护共享资源的访问,一次只允许一个...

    三峡大学2023操作系统期末复习知识点+打印店习题部分总结

    三峡大学2023操作系统期末复习知识点+打印店习题部分总结 图2-1用户和其他系统结构 (GUI,批处理,命令行)用户页面 系统调用 服务 操作系统 硬件 ...关键在于如何协调读者和写者的访问,以确保数据的一致性和完整性。

    详细描述了Delphi多线程编程,超级简单易懂

    为了在多线程环境中安全地共享数据,Delphi提供了同步机制,如TCriticalSection、TMutex和TSemaphore。这些同步对象帮助开发者避免竞态条件和数据冲突,确保线程间的协调访问。 此外,Delphi还支持动态线程变量,...

    分布式数据库的设计与实现.doc

    在分布式数据库设计中遇到的最主要的一个问题就是数据同步的问题。由于全局数据库 与各部门的数据交换是双向的,各系部需将更新的数据发送到全局数据库,全局数据库 负责整体协调,要向下属单位下发

    操作系统进程间通信五种方式

    共享内存是指两个进程可以访问同一个物理内存位置的机制,可以让多个进程之间通过对共享内存在内存中读写来完成信息交换,因此具有高效性和灵活性,但是也需要开发人员考虑并发控制和同步的问题。 信号量通信 信号...

    并发编程面试题汇总.docx并发编程是指在一个程序中同时执行多个独立的任务或操作的能力 在面试中,常常会问到与并发编程相关的问题

    共享资源和竞态条件:并发编程中,多个线程对共享资源的并发访问可能导致竞态条件和数据不一致的问题。竞态条件指的是多个线程访问共享资源时的执行顺序和时间导致的不确定结果。为了避免竞态条件,需要使用同步机制...

    C#并行编程高级教程:精通.NET 4 Parallel Extensions中文(第3部分)

    ◆讲解命令式数据并行、命令式任务并行、并发集合以及协调数据结构。 ◆描述PLINQ高级声明式数据并行。 ◆讨论如何使用新的Visual Studio 2010并行调试功能来调试匿名方法、任务和线程。 ◆演示如何对数据源进行...

    最新小程序家庭事务管理微信小程序+ssm.zip

    8. **数据同步与备份**:利用云服务实现数据的实时同步和备份,保障信息的安全和可靠性。 整个系统以微信小程序为前端,便于用户随时随地访问和使用;后端采用SSM框架,确保了数据处理的效率和稳定性。这款小程序...

    基于Java的视频会议系统的实现.rar

    客户端-服务器架构:采用客户端-服务器模式,其中服务器负责协调客户端之间的通信和数据传输。 实时通信:系统需要支持实时音视频通信,保证用户之间的流畅交流和低延迟。 多人会议:支持多人同时参与的视频会议,...

    110104010104.rar_MFC多任务_MFC多线程挂起_mfc 多线程_mfc 生产者

    信号量是一个在一定范围内变化的整形数据,用来表示一种临界资源,线程通过信号量的值来确定自己的状态是执行还是挂起,各线程间也是通过信号量机制来协调运行顺序一起完成任务,信号量的用法和互斥的用法很相似,...

    简单介绍SQL Server中的自旋锁

    用闩锁同步多个线程间数据结构访问,在每个共享数据结构前都放置一个闩锁没有意义的。闩锁与此紧密关联:当你不能获得闩锁(因为其他人已经有一个不兼容的闩锁拿到),查询就会强制等待,并进入挂起(SUSPENDED)...

    SpringBoot项目时间管理系统.zip

    8. 数据同步:对于多设备使用者,系统提供数据同步功能,确保在不同设备上都能访问最新的数据。 通过这些功能,SpringBoot项目时间管理系统能够帮助用户有效地管理时间,提高工作效率。它不仅适用于个人时间管理,...

    安全生产信息化平台设计方案.docx

    4 1.1 任务目旳 4 1.2 需求分析 4 2 系统组件设计 4 2.1 组件架构 4 2.2 组件简介 4 3 系统组件阐明 5 3.1 应用物联网感知设备 5 3.2 数据中继服务器 6 3.3 通信服务器组 6 3.4 主(备)服务器组 6 3.5 系统访问终端...

    C#并行编程高级教程:精通.NET 4 Parallel Extensions中文(第一部分)

    ◆讲解命令式数据并行、命令式任务并行、并发集合以及协调数据结构。 ◆描述PLINQ高级声明式数据并行。 ◆讨论如何使用新的Visual Studio 2010并行调试功能来调试匿名方法、任务和线程。 ◆演示如何对数据源进行...

    C#并行编程高级教程:精通.NET 4 Parallel Extensions中文(第2部分)

    ◆讲解命令式数据并行、命令式任务并行、并发集合以及协调数据结构。 ◆描述PLINQ高级声明式数据并行。 ◆讨论如何使用新的Visual Studio 2010并行调试功能来调试匿名方法、任务和线程。 ◆演示如何对数据源进行...

    zookeeper-application:zookeeper应用

    Zookeeper允许分布式进程之间彼此协调,通过一个共享的分级命名空间,它非常像标准的文件系统。 ZooKeeper实现非常重视高性能,高可用性,严格有序的访问。ZooKeeper的性能方面意味着它可以在大型分布式系统中使用。...

    COM与COM+从入门到精通(pdf版本,含源码)

    分布式事务协调器(MSDTC) COM+事务的工作 事务与有状态对象 使用共享属性管理器(SPMSharedProperyManager) 小结 第14章 了解MSMQ 何谓MSMQ MSMQ的好处 MSMQ组件 队列 消息 MSMQ对象模型 MSMQ设置 ...

    java汽车租赁源码-JUC:java.util.concurrentJUC多线程及高并发Demo

    所有的并发问题都可以归结为如何协调对并发状态的访问,可变状态越少,就越容易确保线程安全性。 尽量将域声明为final类型,除非需要他们是可变的。 不可变对象一定是线程安全的。 不可变对象能极大地降低并发编程的...

Global site tag (gtag.js) - Google Analytics