简书 賈小強
转载请注明原创出处,谢谢!
一个Java 5中最好的补充是对原子操作的支持类,如AtomicInteger
,AtomicLong
等。这些类帮助你减少复杂的(不必要的)多线程代码,实际上只是完成一些基本操作,如增加或减少多个线程之间的共享的值。这些类在内部依赖于一个名为CAS(Compare and Swap)的算法。在这篇文章中,我将详细讨论这个概念。
乐观悲观锁 (Optimistic and Pessimistic Lock)
传统的锁机制,例如在Java中使用的synchronized关键字,是多线程的悲观锁技术。它要求您首先保证没有其他线程将在某些操作之间进行干扰(即锁定对象)。
这就像是说:“请先把门关上,否则会有其他的骗子进来整理你的东西”。
虽然上面的方法是安全的,它确实有效,但它在性能上对应用程序造成了严重的影响。原因很简单,等待线程不能做任何事情,除非它们也有机会执行被保护的操作。
实际上存在一种更有效性能更好的方式,在本质上是乐观的。在这种方法中,您继续进行更新,希望您能在不受干扰的情况下完成。这种方法依赖于碰撞检测,来确定是否更新时被其他方面的干扰,在这种情况下,操作失败可以重试(或不)。
乐观的方法就像一句老话:“获得原谅比获得许可更容易”,在这里“更容易”意味着“更有效”。
Compare and Swap是这种乐观方法的一个很好例子,我们将在下面讨论。
Compare and Swap算法
该算法将某个内存位置的内容与给定值进行比较(compare),只有当它们相同时,才将内存位置的内容修改为给定的新值。这是作为单个原子操作完成的。原子性保证了新值是在最新值基础上进行计算的;如果该值已被另一个线程同时更新,那么修改会失败。操作的结果必须指明它是否执行替换(swap),这可以通过简单的布尔响应(这个变体通常称为compare-and-set),或者返回从内存位置读取的值(而不是写入它的值)来完成。
比如CAS操作有3个参数:
- 一个内存位置V,其值必须被替换。
- 上一次线程读的旧值A。
- 应在V上写的新值B。
CAS说:“我觉得V应该有值A;如果是,把B放在那里,否则不要改变它,但告诉我,我错了。”CAS乐观的希望的更新成功,同时如果另一个线程从它上次检查后更新了变量,它会检测到失败。
让我们用一个例子明白整个过程。假设V是存储值“10”的内存位置。有多个线程希望增加这个值,并将递增的值用于其他操作。让我们逐步完成整个CAS操作:
- 初始状态。
V = 10, A = 0, B = 0 - 现在线程1首先出现,并将V与它的上次读取的值A进行比较(compare):
V = 10, A = 10, B = 11
if A = V
V = B
else
operation failed
return V
显然,V的值将被替换(swap)为11,即操作成功。
- 然后线程2执行与线程1相同的操作,这个时候内置位置V实际上值已经是11,但线程2上次读取的还是10。
V = 11, A = 10, B = 11
if A = V
V = B
else
operation failed
return V
- 在这种情况下,将V与它的上次读取的值A进行比较(compare),V不等于A,所以不替换值,返回V,而即当前值11。现在线程2,再次用值重试(retry)这个操作:
V = 11, A = 11, B = 12
此时,条件满足并将值递增为12,然后返回给线程2。
总之,当多个线程尝试使用CAS同时更新同一个变量时,其中一个线程会成功更新变量的值,剩下的会失败。但失败者不会受到线程的惩罚。他们可以自由地重试操作或干脆什么也不做。
这样每个线程对值得增加都是实打实的增加了,而不会导致线程1,线程2同时读取V为10,然后线程1增加为11,线程2也自认为增加了,但结果还是11,通过CAS算法避免了消失的递增,解决了明明线程2任务执行了,计数器却没计数的悲剧**
这就是有关Java的原子(atomic)操作简单但重要的概念。
Happy Learning !!
网友评论