当前位置: 首页 > 图灵资讯 > 技术篇> 不要重新分配被锁定对象的对象引用

不要重新分配被锁定对象的对象引用

来源:图灵教育
时间:2024-02-25 14:02:35

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; } } throw new EmptyStackException(); } //... }

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

请注意,该代码应由多个线程访问。push 和 pop 每次访问此类共享实例数据时,该方法都是 synchronized 块内完成。这确保了多个线程不能并发访问这个数组,并产生不正确的结果。

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

线程 1 调用 push 方法并获得 intArr 对象的锁。 线程 1 被线程 2 抢先。 线程 2 调用 pop 方法。该方法试图获得当前的线程 1 在 push 方法中持有的相同锁被堵塞。 线程 1 重新获得控制并重新分配数组。intArr 变量现在引用不同的变量。 push 退出并释放其原始方法 intArr 对象的锁。 线程 1 再次调用 push 并获得新的方法 intArr 对象的锁。 线程 1 被线程 2 抢先。 线程 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 对象引用。