自旋锁与自适应自旋及JDK中使用自旋的例子

        互斥同步对性能最大的影响是阻塞的实现,挂起线程和恢复线程的操作都需要转入内核态中完成,这些操作给系统的并发性能带来了很大的压力。 同时,虚拟机的开发团队也注意到在许多应用上,共享数据的锁定状态只会持续很短的一段时间,为了这段时间去挂起和恢复线程并不值得。 如果物理机器有一个以上的处理器,能让两个或以上的线程同时并行执行,我们就可以让后面请求锁的那个线程“稍等一下”,但不放弃处理器的执行时间,看看持有锁的线程是否很快就会释放锁。 为了让线程等待,我们只需让线程执行一个忙循环(自旋),这项技术就是所谓的自旋锁。

        自旋锁在JDK 1.4.2中就已经引入,只不过默认是关闭的,可以使用-XX:+UseSpinning参数来开启,在JDK 1.6中就已经改为默认开启了。

        自旋等待不能代替阻塞,且先不说对处理器数量的要求,自旋等待本身虽然避免了线程切换的开销,但它是要占用处理器时间的,因此,如果锁被占用的时间很短,自旋等待的效果就会非常好,反之,如果锁被占用的时间很长,那么自旋的线程只会白白消耗处理器资源,而不会做任何有用的工作,反而会带来性能上的浪费。 因此,自旋等待的时间必须要有一定的限度,如果自旋超过了限定的次数仍然没有成功获得锁,就应当使用传统的方式去挂起线程了。 自旋次数的默认值是10次,用户可以使用参数-XX:PreBlockSpin来更改。

        在JDK 1.6中引入了自适应的自旋锁。 自适应意味着自旋的时间不再固定了,而是由前一次在同一个锁上的自旋时间及锁的拥有者的状态来决定。 如果在同一个锁对象上,自旋等待刚刚成功获得过锁,并且持有锁的线程正在运行中,那么虚拟机就会认为这次自旋也很有可能再次成功,进而它将允许自旋等待持续相对更长的时间,比如100个循环。 另外,如果对于某个锁,自旋很少成功获得过,那在以后要获取这个锁时将可能省略掉自旋过程,以避免浪费处理器资源。 有了自适应自旋,随着程序运行和性能监控信息的不断完善,虚拟机对程序锁的状况预测就会越来越准确,虚拟机就会变得越来越“聪明”了。

自旋在JDK中的使用

在抽象同步器中AbstractQueuedSynchronizer

节点在进入同步队列之后,就进入了一个自旋的过程,每个节点(或者说每个线程)都在自省的观察,看自己是否满足条件并获取到同步状态;当满足条件获取到了同步状态,就可以从这个自旋过程中退出,否则依旧留在这个自旋过程中,并且会阻塞节点的线程。

互斥模式下获取同步状态

//尝试获取同步状态,如果获取失败 && 成功添加节点到同步队列,则进行自我中断
//tryAcquire(arg)大于等于0表示获取同步状态成功,在互斥模式下,只会返回0或1
public final void acquire(int arg) {
     if (!tryAcquire(arg) && acquireQueued(addWaiter(Node.EXCLUSIVE), arg))
            selfInterrupt();
}
//同步队列中的节点自旋获取同步状态
final boolean acquireQueued(final Node node, int arg) {
        boolean failed = true;
        try {
            boolean interrupted = false;
            //自旋
            for (;;) {
                //获取到当前节点的前驱节点
                final Node p = node.predecessor();
                //如果当前节点的前驱节点是head节点(head节点是当前占有同步状态的线程)  && 当前成功获取同步状态
                if (p == head && tryAcquire(arg)) {
                    setHead(node);
                    p.next = null; // help GC
                    failed = false;
                    return interrupted;
                }
                //获取失败后应该阻塞线程 && 成功阻塞线程并检查中断
                if (shouldParkAfterFailedAcquire(p, node) &&
                    parkAndCheckInterrupt())
                    interrupted = true;
            }
        } finally {
            if (failed)
                cancelAcquire(node);
        }
}

private final boolean parkAndCheckInterrupt() {
        LockSupport.park(this);
        return Thread.interrupted();
}

获取共享锁

//尝试获取共享锁
//tryAcquireShared(arg)返回值大于等于0表示获取成功
public final void acquireShared(int arg) {
     //如果获取共享锁失败,则进入doAcquireShared(arg)方法
     if (tryAcquireShared(arg) < 0)
         doAcquireShared(arg);
}

//将该节点放入到同步队列后,进行自旋获取共享锁
private void doAcquireShared(int arg) {
        //将节点加入到同步队列中
        final Node node = addWaiter(Node.SHARED);
        boolean failed = true;
        try {
            boolean interrupted = false;
            for (;;) {
                final Node p = node.predecessor();
                if (p == head) {
                    //尝试获取共享锁
                    int r = tryAcquireShared(arg);
                    if (r >= 0) {//成功获取共享锁
                        setHeadAndPropagate(node, r);
                        p.next = null; // help GC
                        if (interrupted)
                            selfInterrupt();
                        failed = false;
                        return;
                    }
                }
                if (shouldParkAfterFailedAcquire(p, node) &&
                    parkAndCheckInterrupt())
                    interrupted = true;
            }
        } finally {
            if (failed)
                cancelAcquire(node);
        }
}

 

你可能感兴趣的:(JVM)