【0】README
0.1) 本文描述转自 core java volume 1, 源代码为原创,旨在理解 java线程同步——竞争条件的荔枝+锁对象 的相关知识;
0.2) for full source code, please visit https://github.com/pacosonTang/core-java-volume/blob/master/chapter14/ThreadFive.java
【1】竞争条件的荔枝
1.1)某银行有若干个账户(我们这里设置为4个),且初始余额均为10000; 账户间存在着转账操作;
Attention):在模拟这个程序的过程中, 不清楚在某一时刻某银行的账户转账多少钱,但是其总金额应该是不变的, 因为我们所做的一切不过是从银行的一个账户转账到另一个账户(干货——无论怎样转账,总金额应该是不变的,始终为40000)。
对上述运行结果的分析(Analysis):
- A1)问题在于这不是一个原子操作, 该指令可能被如下处理:
- step1)将 account[to] 加载到寄存器;
- step2)增加 amount 到 account[to]上;
- step3)将结果写回 account[to];
- A2)现在线程1 执行step1 + step2, 然后CPU切换到执行线程2;
- A2.1)假设线程2修改了account[to]的值;
- A2.2)之后,线程1被唤醒,然后进行 step3;
- A3)那显然, 线程1的step3操作重写了 线程2的写入结果(线程1擦除线程2所做的更新), 所以就出现了最终的总金额不等于 40000的错误结果;
- A4)如果要保持结果的合理性,只需要达到一个目的,就是将对账户余额的访问加以限制,每次只能有一个线程在访问。这样就能保证账户中余额数据的合理性了。
1.2)解决方法:
- 1.2.1)使用同步块(synchronized):在使用同步代码块时候,应该指定在哪个对象上同步,也就是说要获取哪个对象的锁。例如:(干货——无论怎样转账,总金额应该是不变的,始终为40000)。
- 1.2.2)使用同步方法:(干货——无论怎样转账,总金额应该是不变的,始终为40000)。
【2】锁对象
2.1)有两种方法防止代码块受并发访问的干扰:
- 2.1.1) synchronized 关键字, 构造同步块,或是同步方法;(如1.2中解决方法 所示)
- 2.1.2) java 5.0 引入的 ReentrantLock 类;
2.2)用ReentrantLock保护代码块的基本结构
myLock.lock()
try
{}
finally
{
myLock.unlock()
}
- 2.2.1)这一结构确保了 任何时刻只有一个线程进入临界区域, 一旦一个线程封锁了锁对象,其他任何线程都无法通过lock语句。 当其他线程调用 lock 时, 它们会被阻塞,直到第一个线程释放锁对象;
Warning)把解锁操作包括在了 finally 子句中是至关重要的。 如果在临界区的代码抛出异常, 锁必须被释放, 否则, 其他线程将永远阻塞下去;
Attention)
- A1)锁是可重入的, 因为线程可以重复地获得已持有的锁;
- A2)锁保持一个持有计数来跟踪对 lock 方法的嵌套调用, 线程在每一次调用lock 都要调用 unlock 来释放锁;
- A3)由于这一特性, 被一个锁保护起来的代码可以调用另一个使用相同的锁的方法;