`

对象锁的变化

    博客分类:
  • java
 
阅读更多

          synchronized 关键字锁定对象。对象是在 synchronized 代码内部被锁定的,这一点对此对象以及您对其对象引用所作的更改意味着什么呢?对一个对象作同步处理只锁定该对象。但是,必须注意不要重新分配被锁定对象的对象引用。那么如果这样做会发生什么情况呢?请考虑下面这段代码,它实现了一个 Stack:

      

class Stack 
{ 
  private int stackSize = 10; 
  private int[] intArr = new int[stackSize]; 
  private int index;          //Stack 中的下一个可用位置。 
  public void push(int val) 
  { 
    synchronized(intArr) { 
      //如果已满,则重新分配整数数组(即我们的 Stack)。 
      if (index == intArr.length) 
      { 
        stackSize *= 2; 
        int[] newintArr = new int[stackSize];
        System.arraycopy(intArr, 0, newintArr, 0, intArr.length); 
        intArr = newintArr; 
      } 
        intArr[index] = val;
      index++; 
    } 
  } 
  public int pop() 
  { 
    int retval; 
    synchronized(intArr) { 
      if (index > 0) 
      { 
        retval = intArr[index-1];      //检索值, 
        index--;                      //并使 Stack 减少 1 个值。 
        return retval; 
      } 
    } 
    return 0;
  } 
  //... 
} 
 

 


       这段代码用数组实现了一个 Stack。创建了一个初始大小为 10 的数组来容纳整数值。此类实现了 push 和 pop 方法来模拟 Stack 的使用。在 push 方法中,如果数组中没有更多的空间来容纳压入的值,则数组被重新分配以创建更多的存储空间。(故意没有用 Vector 来实现这个类。 Vector 中不能储存基本类型。)

请注意,这段代码是要由多个线程进行访问的。push 和 pop 方法每次对该类的共享实例数据的访问都是在 synchronized 块内完成的。这样就保证了多个线程不能并发访问此数组而生成不正确的结果。

这段代码有一个主要的缺点。它对整数数组对象作了同步处理,而这个数组被 Stack 类的 intArr 所引用。当 push 方法重新分配此整数数组时,这个缺点就会显露出来。当这种情况发生时,对象引用 intArr 被重新指定为引用一个新的、更大的整数数组对象。请注意,这是在 push 方法的 synchronized 块执行期间发生的。此块针对 intArr 变量引用的对象进行了同步处理。因此,在这段代码内锁定的对象不再被使用。请考虑以下的事件序列:

  1. 线程 1 调用 push 方法并获得 intArr 对象的锁。

  2. 线程 1 被线程 2 抢先。

  3. 线程 2 调用 pop 方法。此方法因试图获取当前线程 1 在 push 方法中持有的同一个锁而阻塞。

  4. 线程 1 重新获得控制并重新分配数组。 intArr 变量现在引用一个不同的变量。

  5. push 方法退出并释放它对原来的 intArr 对象的锁。

  6. 线程 1 再次调用 push 方法并获得新 intArr 对象的锁。

  7. 线程 1 被线程 2 抢先。

  8. 线程 2 获得旧 intArr 对象的对象锁并试图访问其内存。(因为锁是锁定对象的,不是锁定对象的引用)

现在线程 1 持有由 intArr 引用的新对象的锁,线程 2 持有由 intArr 引用的旧对象的锁。因为两个线程持有不同的锁,所以它们可以并发执行 synchronized push 和 pop 方法,从而导致错误。很明显,这不是所希望的结果。

这个问题是因 push 方法重新分配被锁定对象的对象引用而造成的。当某个对象被锁定时,其他线程可能在同一个对象锁上被阻塞。如果将被锁定对象的对象引用重新分配给另一个对象,其他线程的挂起锁则是针对代码中已不再相关的对象的。

您可以这样修正这段代码,去掉对 intArr 变量的同步,而对 push 和 pop 方法进行同步。通过将 synchronized 关键字添加为方法修饰符即可实现这一点。正确的代码如下所示:

    

class Stack 
{ 
  //与前面相同... 
  public synchronized void push(int val) 
  { 
    //如果为空,则重新分配整数数组(即我们的 Stack)。 
    if (index == intArr.length) 
    { 
      stackSize *= 2; 
      int[] newintArr = new int[stackSize]; 
      System.arraycopy(intArr, 0, newintArr, 0, intArr.length); 
      intArr = newintArr; 
    } 
    intArr[index]= val; 
    index++; 
  } 
  public synchronized int pop() 
  { 
    int retval; 
    if (index > 0) 
    { 
      retval = intArr[index-1]; 
      index--; 
      return retval; 
    } 
    throw new EmptyStackException(); 
  } 
} 
 

 

 

       这个修改更改了实际上获取的锁。获取的锁是针对为其调用方法的对象的,而不是锁定 intArr 变量所引用的对象。因为获取的锁不再针对 intArr 所引用的对象,所以允许代码重新指定 intArr 对象引用。

 

 

 

 

      

分享到:
评论

相关推荐

    精通 Hibernate:Java 对象持久化技术详解(第2版).part2

    6.3 Hibernate用对象标识符(OID)来区分对象 6.4 Hibernate的内置标识符生成器的用法  6.4.1 increment标识符生成器  6.4.2 identity标识符生成器  6.4.3 sequence标识符生成器  6.4.4 hilo标识符生成器  ...

    java 偏向锁、轻量级锁及重量级锁synchronized原理.docx

    由于对象头的信息是与对象自身定义的数据没有关系的额外存储成本,因此考虑到JVM的空间效率,Mark Word 被设计成为一个非固定的数据结构,以便存储更多有效的数据,它会根据对象本身的状态复用自己的存储空间,如32...

    精通 Hibernate:Java 对象持久化技术详解(第2版).part4

    6.3 Hibernate用对象标识符(OID)来区分对象 6.4 Hibernate的内置标识符生成器的用法  6.4.1 increment标识符生成器  6.4.2 identity标识符生成器  6.4.3 sequence标识符生成器  6.4.4 hilo标识符生成器  ...

    精通 Hibernate:Java 对象持久化技术详解(第2版).part3

    6.3 Hibernate用对象标识符(OID)来区分对象 6.4 Hibernate的内置标识符生成器的用法  6.4.1 increment标识符生成器  6.4.2 identity标识符生成器  6.4.3 sequence标识符生成器  6.4.4 hilo标识符生成器  ...

    精通 Hibernate:Java 对象持久化技术详解(第2版).part1.rar

    6.3 Hibernate用对象标识符(OID)来区分对象 6.4 Hibernate的内置标识符生成器的用法  6.4.1 increment标识符生成器  6.4.2 identity标识符生成器  6.4.3 sequence标识符生成器  6.4.4 hilo标识符生成器  ...

    超级有影响力霸气的Java面试题大全文档

    wait是Object类的方法,对此对象调用wait方法导致本线程放弃对象锁,进入等待此对象的等待锁定池,只有针对此对象发出notify方法(或notifyAll)后本线程才进入对象锁定池准备获得对象锁进入运行状态。 17、...

    Java面向对象程序设计试题一.doc

    9、哪个关键字可以对对象加互斥锁 ?( ) A transient B synchronized C serialize D static 10、下列哪些语句关于内存回收的说明是正确的? ( ) A 程序员必须创建一个线程来释放内存; B 内存回收程序负责释放...

    gridfs-locks:基于MongoDB的分布式读写锁,旨在让GridFS安全并发访问

    这些变化有三个主要影响: 所有异步回调已从 API 方法参数列表中删除并替换为事件现在可以以更直观的方式观察和处理一组更丰富的异步事件(例如锁到期) 也可以移除已移除资源的锁,以免它们弄乱锁集合安装需要node...

    阿里巴巴,天猫,支付宝面试题

    10. 两个Integer的引用对象传给一个swap方法在方法内部交换引用,返回后,两个引用的值是否会发现变化 11. aop的底层实现,动态代理是如何动态,假如有100个对象,如何动态的为这100个对象代理 12. 是否用过maven ...

    耐力负荷对左室乳头肌及心内膜下组织学组织化学变化的实验研究 (1994年)

    以不同耐力负荷的雄性Wistar大鼠心脏为研究对象,采用酶组织化学、常规组织学的HE.Masson和心肌闰盘特异性染色等方法及电镜技术,对不同耐力负荷下乳头肌及心内膜下心肌组织结构变化与心肌功能的适应性做了观察。...

    CFRunLoop源码

    CFRunLoopRef在创建时通过static CFSpinLock_t loopsLock锁住CFRunLoopRef对象,所以CFRunLoopRef是安全的。 一个runloop包含若干个mode。每个mode又包含若干个Source/Timer/Observer CFRunLoopSourceRef是事件...

    java 面试题 总结

    wait是Object类的方法,对此对象调用wait方法导致本线程放弃对象锁,进入等待此对象的等待锁定池,只有针对此对象发出notify方法(或notifyAll)后本线程才进入对象锁定池准备获得对象锁进入运行状态。 14、Overload...

    SEL继电器振荡闭锁原理分析 (2011年)

    电力系统发生的振荡不是短路,距离保护不应该动作,但是振荡时的电压、电流幅值发生周期性变化,有可能导致距离继电器误动作。针对该问题,为防止保护误动,研究了怎样应用振荡闭锁、解除闭锁和失步跳闸等技术解决该问题...

    Java虚拟机

    5.3.2 升级JDK 1.6的性能变化及兼容问题 5.3.3 编译时间和类加载时间的优化 5.3.4 调整内存设置控制垃圾收集频率 5.3.5 选择收集器降低延迟 5.4 本章小结 第三部分 虚拟机执行子系统 第6章 类文件结构 6.1 ...

    吉林大学软件学院卓班设计模式第一次作业

    使用分类稳定和变化的方法,重新设计类,使得新设计能以扩展代码的方式适应变化 未来可能会增加新的数据成员如mVal3, do1和do2中可能访问mVal1、mVal2、mVal3等 二. 按指定的要求或模式,给出实现。 1. 使用工厂...

    oracle学习文档 笔记 全面 深刻 详细 通俗易懂 doc word格式 清晰 连接字符串

     数据定义语言Data Definition Language(DDL),用来建立数据库、数据对象和定义其列。例如:CREATE、DROP、ALTER等语句。  数据操作语言Data Manipulation Language(DML),用来插入、修改、删除、查询,可以...

    二十三种设计模式【PDF版】

    同样地,面向对象设计员也沿袭一些模式,像“用对象表示状态”和“修饰对象以便 于你能容易地添加/删除属性”等。一旦懂得了模式,许多设计决策自然而然就产生了。 我们都知道设计经验的重要价值。你曾经多少次有过...

    Lec18-多版本并发控制1

    MVCC给数据库中的对象都记录了它的多个版本后,只读的事务就可以在锁的情况下读它所需要的那个版本的致性快照,不受数据库动态变化的影响,且DBMS般使事务的时间戳

    【精品资源】c++高级编程第三版包含中英文版本涵盖源码

    作者:格莱戈尔(Marc Gregoire) 翻译:张永强 C++高级编程(第3版) 是设计和构建C++应用程序的实用指南,代码丰富... ◆ 使用全新标准库功能,例如make_unique、透明函数对象,通过类型寻址的元组、共享mutex和锁类

Global site tag (gtag.js) - Google Analytics