`

对象锁的变化

    博客分类:
  • 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 对象引用。

 

 

 

 

      

分享到:
评论

相关推荐

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

    Mark Word是一个动态变化的数据结构,用于存储对象的HashCode、分代年龄、锁状态标志等信息。在32位JVM中,根据对象的状态,Mark Word可能有多种不同的存储形式。 在无锁状态下,Mark Word存储对象的普通信息。当...

    SQL server 中锁机制详解

    乐观锁是在数据变化时才加锁,并发性高,前提是假定用户冲突是很小的。 在 SQL Server 中,锁机制分为四个级别:表级、盘区级、页级、行级。锁的分类有共享锁、更新锁、排它锁、意图锁、系统锁等。 共享锁(S):...

    hibernate乐观锁和悲观锁学习

    只有在更新数据时,才会检查数据是否自上次读取后发生了变化。在Hibernate中,通常通过在实体类的映射文件中设置`optimistic-lock`属性来实现乐观锁,比如设置为`version`,这将利用数据库的版本字段来检测并发冲突...

    自定义图形锁View

    在onDraw()中,通常会使用Canvas对象进行绘图操作,如画线、画圆等,以实现图形锁的连线效果。 二、触摸事件处理 为了使用户能够通过触摸屏幕来移动小圆点并连接路径,MyLock需要处理触摸事件。在Android中,这是...

    08 领域对象的生命周期 102-116.rar

    6. **版本控制**:在并发环境中,为了防止多个用户同时修改同一对象导致的数据冲突,领域对象可能会引入版本号机制,如乐观锁或悲观锁。 7. **领域事件**:领域事件是领域驱动设计中的一个概念,当领域对象发生重要...

    android仿S4阳光解锁

    通过设置动画的时间、变化范围和重复模式,使得光束按照预定路径移动。 三、手势识别 1. TouchEvent处理:在Activity或View中重写onTouchEvent()方法,监听用户的触摸事件。当用户按下屏幕时记录初始坐标,然后在...

    Oracle 锁机制问题详解

    - **共享锁**:许多会话可以同时获得相同对象上的共享锁。在一条记录上设置共享锁并无意义,因为锁定记录的目的就是为了不允许其他会话更改它。共享锁主要应用于整个表,以防止另一个会话获得该表上的排他锁。在表上...

    C#仿手机九宫格解锁

    - 为了模拟手机触摸屏的视觉效果,按钮需要有相应的触摸反馈,如按下时的颜色变化、边框高亮等。这可以通过自定义按钮的`OnPaint`事件,使用`Graphics`对象绘制按钮状态来实现。 3. **事件处理**: - 每个数字...

    【转】谈谈 JVM 内部锁升级过程(csdn)————程序.pdf

    在锁升级的过程中,锁的状态变化为:无锁状态(Mark Word中表示为001)->偏向锁状态(Mark Word中表示为101,JDK 1.6后默认开启,关闭偏向锁使用-XX:-UseBiasedLocking参数)->轻量级锁状态(Mark Word中表示为00)-...

    Java synchronized锁升级jol过程详解

    Java synchronized锁升级jol过程详解 Java synchronized锁升级jol过程详解是Java并发编程中一个重要的概念,了解锁升级过程对Java...通过示例代码可以详细了解锁升级过程,并且使用jol可以演示锁对象头的变化过程。

    delphi版本的门锁接口

    Delphi是一款强大的Windows应用开发工具,以其高效的编译器和面向对象的Pascal语法而闻名。 这个接口库可能包含了一系列的类、函数和方法,用于建立和管理与门锁设备的通信。以下是一些可能包含的关键知识点: 1. ...

    ibm数据库管理资料db2资源锁管理

    子系统级对象的锁是指对数据库中的子系统(如存储过程、触发器等)施加的锁。这类锁有助于确保这些对象在执行过程中的一致性。 #### 二十一、CLAIMS和DRAINS CLAIMS和DRAINS是DB2中用于管理锁的两个概念。CLAIMS...

    java锁各种核心整理

    Synchronized同步锁是Java中最基本的锁机制,它可以作用于方法、对象实例或静态方法上。当synchronized作用于方法时,它锁定的是调用这个方法的对象实例;当作用于静态方法时,它锁定的是类对象。因此,静态方法锁...

    android仿ipone滑动解锁控件

    2. **触摸事件处理**:在`onTouchEvent()`方法中,我们可以通过`MotionEvent`对象获取到手指的坐标变化。当用户按下屏幕(ACTION_DOWN)时,记录起始坐标;当手指在屏幕上移动(ACTION_MOVE)时,更新当前坐标;当...

    精通 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标识符生成器  ...

    多线程面向对象电梯设计

    例如,可以添加一个**状态机对象**来跟踪电梯的不同状态(空闲、上行、下行、开门、关门等),并根据状态变化触发相应的行为。 总结来说,"多线程面向对象电梯设计"项目是一个综合运用OOP和多线程技术的实例,它...

    手势解锁DEMO

    - 反馈机制:当用户绘制路径时,可以显示临时线条,用户完成手势后消失,同时提供视觉反馈(如颜色变化)来确认成功或失败。 - 错误处理:处理连续尝试失败的情况,如设置最大尝试次数,超时重置等。 - 安全性...

    Redis 分布式锁使用

    使用Redisson创建一个分布式锁对象,然后调用`lock()`方法获取锁: ```java RLock lock = redisson.getLock("myLock"); try { lock.lock(); // 执行需要锁保护的操作 } finally { lock.unlock(); // 释放锁 } ``...

Global site tag (gtag.js) - Google Analytics