Spin Lock -- TAS和TTAS

来源:互联网 发布:php判断蜘蛛 编辑:程序博客网 时间:2024/06/05 21:58

TAS采用原子操作更新共享状态,同时添加while循环,保证在无法获得锁的同时,可以重复尝试获取锁(实现自旋),而不是挂起线程。如果使用java的话,则可以使用compareAndSet原子操作。

以下是java的TAS版本:

import java.lang.reflect.Field;import sun.misc.Unsafe;public class TAS {private int state = 0;public static long offset = 0;public static Unsafe unsafe = null;public void lock(){while(!unsafe.compareAndSwapInt(this, offset, 0, 1));}public void unlock(){unsafe.compareAndSwapInt(this, offset, 1, 0);}static {unsafe = getUnsafe();try {offset = unsafe.objectFieldOffset(TAS.class.getDeclaredField("state"));} catch (NoSuchFieldException e) {// TODO Auto-generated catch blocke.printStackTrace();} catch (SecurityException e) {// TODO Auto-generated catch blocke.printStackTrace();}}private static Unsafe getUnsafe() {try {Field field = Unsafe.class.getDeclaredField("theUnsafe");field.setAccessible(true);return (Unsafe) field.get(null);} catch (Exception e) {// TODO Auto-generated catch blocke.printStackTrace();} return null;}}

测试程序:

public class Test {private int a = 0;public TAS tas = new TAS();public void increment(){tas.lock();a++;tas.unlock();System.out.println(a);}public static void main(String[] args) {final Test test = new Test();for(int i = 0; i < 10000; i++){new Thread(new Runnable() {@Overridepublic void run() {test.increment();}}).start();;}}}

根据多次运行的结果可以看到,锁正常运行。

TTAS的锁实现和TAS的实现相类似。来看一看TAS和TTAS的性能对比,


总的来看,TAS的性能非常糟糕,虽然TTAS相比于TAS性能要好一些,但是和理想值的对比还是差了很多。同时两个曲线都比较陡,也就是说随着线程数的增加,锁的性能越来越差。那么什么导致了锁的性能这么差呢?

上面的图给出了理由:

Test&Set()导致了总线上频繁的广播,用于更新各个线程的内存缓存。因此当持有锁的那个线程释放锁的时候,由于总线的繁忙,而导致了延迟。

为了解决这个问题,可以采用Exponential Backoff


所谓backoff就是当线程无法获取锁的时候,进行休眠一定的时间。这个就在无限休眠和自旋等待之间获得了一个平衡。

来看看改进之前和改进之后的性能差别,


可以看到改进之后,明显地改善了性能。当然这么做也是优缺点的,也就是你必须小心的选择休眠的时间。否则会得不偿失。


总体性能并不是TAS和TTAS最大的问题,其最大的问题是可能会导致starve(饥渴)的出现。由于采用while(自旋锁)实现,所以当某个线程释放锁的时候,其他锁获取这个锁的概率是相同的,但是在最坏情况下,某个线程很早就请求锁,但是每次其他线程释放锁它都无法获取到锁,这就使得这个线程的执行时间非常长,导致了饥渴的出现。

使用基于排队的自旋锁就可以避免线程的饥渴。接下来的文章会介绍。

0 0
原创粉丝点击