同步工具类

同步工具类

闭锁

闭锁可以延迟线程的进度直到达到其终止状态,闭锁的作用相当于一扇门:在闭锁到达结束状态之前,这扇门一直是关闭的,并且没有任何线程能通过,当达到结束状态时,这扇门会打开并允许所有的线程通过

闭锁的作用:
1. 确保某个计算在其需要的所有资源都被初始化之后才执行
2. 确保某个服务在其依赖的所有其他服务都已经启动之后才启动
3. 等待直到某个操作的所有参与者就绪后再继续执行

1.CountDownLatch

使用

//计数10 的闭锁
CountDownLatch countDownLatch = new CountDownLatch(10);
//线程在闭锁上阻塞
countDownLatch.await();
//计数-1,当计数-10时,闭锁打开,唤醒闭锁上阻塞的所有线程
countDownLatch.countDown();

实现原理
基于AQS实现

    //等待
    public void await() throws InterruptedException {
        sync.acquireSharedInterruptibly(1);
    }

    public final void acquireSharedInterruptibly(int arg)
            throws InterruptedException {
        if (Thread.interrupted())
            throw new InterruptedException();
        if (tryAcquireShared(arg) < 0)
           //见AQS实现
            doAcquireSharedInterruptibly(arg);
    }

     //节点状态为0返回1,其他返回-1
        protected int tryAcquireShared(int acquires) {
            return (getState() == 0) ? 1 : -1;
        }

    //计数减1    
    public void countDown() {
        sync.releaseShared(1);
    }

    //AQS实现
    public final boolean releaseShared(int arg) {
        if (tryReleaseShared(arg)) {
            //顺序释放阻塞在锁上的所有线程
            doReleaseShared();
            return true;
        }
        return false;
    }
    //Countdown实现尝试释放共享锁
        protected boolean tryReleaseShared(int releases) {
            // Decrement count; signal when transition to zero
            for (;;) {
                int c = getState();
                if (c == 0)
                    return false;
                int nextc = c-1;
                if (compareAndSetState(c, nextc))
                //节点状态为0返回true
                    return nextc == 0;
            }
        }

闭锁打开后,所有阻塞线程按照执行await的先后顺序释放

示例:

    /*
     *  统计线程运行的时间
     */
    public long timeTasks(int nThreads,final Runnable task) throws InterruptedException
    {
        final CountDownLatch startGate = new CountDownLatch(1);

        final CountDownLatch endGate = new CountDownLatch(nThreads);

        for (int i = 0; i < nThreads; i++)
        {
             new Thread(
                    () -> {
                        try {
                            startGate.await();
                            try
                            {
                                task.run();
                            }
                            finally {
                                endGate.countDown();
                            }

                        } catch (InterruptedException e) {
                            e.printStackTrace();
                        }

                    }
            ).start();
        }

        long start  = System.nanoTime();
        startGate.countDown();
        endGate.await();
        long end = System.nanoTime();

        return end - start;
    }

2.FutureTask

FutureTask是闭锁的令一种形式,其get方法将会返回task的处理结果,如果task未被处理完成,get操作将会阻塞,直到线程返回或者线程中断

实现原理
FutureTask内部包含一个状态变量state,以及一个节点链表。进行get操作时如果state!=success,其会把当前线程绑定到节点,并把当前线程park掉,后续线程执行get操作时会被同样操作并链接到链表尾部。当task执行完成后,会顺序唤醒所有此链表上park掉的线程

示例:

        FutureTask<String> task = new FutureTask(new Callable() {
            @Override
            public String call() throws Exception {
                //doSomeThing
            }
        });

        //启动线程开始执行task
        new Thread(task).start();

        //启动5个线程在task完成后doSomething
        for(int i = 0; i<5 ; i++)
        {
            new Thread(()->{
                try {
                   //将会阻塞到task完成
                   String result = task.get();
                   //doSomething
                } catch (Exception e) {
                    e.printStackTrace();
                }
            }).start();
        }

    }

信号量

计数信号量用来控制同时访问某个特定资源的操作数量,或者同时执行某个指定操作的数量,计数信号量还可以用来实现某种资源池(C3p0数据库连接池),或者对容器施加边界

1.Semaphore

Semaphore中管理者一组虚拟的许可(permit),许可的初始数量可以通过构造函数来指定,在执行操作时可以首先获得许可(只要还有剩余的许可),并在使用以后释放许可,如果没有许可,那么acquire将阻塞直到有许可(或者直到被中断或者操作
超时),release方法将会返回一个许可给信号量(无关哪个线程释放许可都+1)

实现原理
基于AQS实现

1.acquire实现

/**
 *获取信号量
 */
public void acquire() throws InterruptedException {
        sync.acquireSharedInterruptibly(1);
    }

    //AQS方法,参加AQS
    public final void acquireSharedInterruptibly(int arg)
            throws InterruptedException {
        if (Thread.interrupted())
            throw new InterruptedException();
        if (tryAcquireShared(arg) < 0)
            //尝试获取请求失败,线程进入等待
            doAcquireSharedInterruptibly(arg);
    }

//子类实现,公平锁的tryAcquireShared
protected int tryAcquireShared(int acquires) {
            for (;;) {
                //已存在等待获取请求
                if (hasQueuedPredecessors())
                    return -1;
                int available = getState();
                int remaining = available - acquires;
                //获取请求成功 ,许可-1
                if (remaining < 0 ||
                    compareAndSetState(available, remaining))
                    return remaining;
            }
        }

2.release实现


    public void release() {
        sync.releaseShared(1);
    }

    public final boolean releaseShared(int arg) {
        if (tryReleaseShared(arg)) {
            doReleaseShared();
            return true;
        }
        return false;
    }

    //释放锁成功,唤醒阻塞在队列上的线程,公平情况下顺序唤醒,非公平情况下刚好有线程请求许可的话则被先竞争到,没用执行公平情况下的顺序唤醒
    protected final boolean tryReleaseShared(int releases) {
            for (;;) {
                int current = getState();
                int next = current + releases;
                if (next < current) // overflow
                    throw new Error("Maximum permit count exceeded");
                if (compareAndSetState(current, next))
                    return true;
            }
        }

示例:Semaphore构建有界容器

import java.util.Collections;
import java.util.HashSet;
import java.util.Set;
import java.util.concurrent.Semaphore;

public class BoundedHashSet<T> {
    private final Set<T> set;
    private final Semaphore sem;

    public BoundedHashSet(int bound) {
        this.set = Collections.synchronizedSet(new HashSet<T>());
        this.sem = new Semaphore(bound);
    }

    public boolean add(T o) throws InterruptedException {
        sem.acquire();
        boolean wasAdded = false;
        try {
            wasAdded = set.add(o);

            return wasAdded;
        } finally {
            if (!wasAdded) {
                sem.release();
            }
        }
    }

    public boolean remove(Object o)
    {
        boolean wasRemove = set.remove(o);
        if(wasRemove)
        {
            sem.release();
        }
        return wasRemove;

    }

}

栅栏

栅栏(Barrier)类似于闭锁,它能阻塞一组线程直到某个事件发生,栅栏与闭锁的关键区别在于,所有线程必须同时到达栅栏位置,才能继续执行。

闭锁用于等待事件,而栅栏用于等待其他线程。栅栏用于实现一些协议,例如几个家庭决定在某个地方集合,然后在讨论下一步要做的事情

1.CyclicBarrier

CyclicBarrier可以使一定数量的参与方反复的在栅栏位置汇集,它在并行迭代算法中非常有用:这种算法通常将一个问题分成一系列的相互独立的子问题,当线程到达栅栏位置时将调用await方法,这个方法将阻塞直到所有的线程都到达栅栏位置。如果所有线程都到达了栅栏位置,那么栅栏将打开,此时所有的线程都被释放,而栅栏将被重置以便下次使用,如果对await的调用超时,或者await阻塞的线程被中断,那么栅栏就被认为被打破,所有阻塞的await调用都将终止并抛出BrokenBarrierException.

如果成功的通过栅栏,那么await将会为每个线程返回一个唯一的到达索引号,我们可以利用这些索引来选取产生一个领导线程,并在下一次迭代中由该领导线程执行一些特殊的工作。

CyclicBarrier还可以使你将一个栅栏操作传递给构造函数,这是一个Runnable,当成功通过栅栏时会(在一个子线程中)执行它,但在阻塞线程被释放之前是不能执行的

实现原理
CyclicBarrier里有个计数统计count,每有一个线程进行await,count-1,线程阻塞在reentranLock.conndition上。当count=0时,如果runnable!=null,最后一个线程执行runable,然后执行conndition.notifyAll,唤醒所有的阻塞线程并重置count值

2.Exchanger

实现比较复杂,分析后续补充

功能

Exchanger,从名字上理解就是交换。Exchanger用于在两个线程之间进行数据交换,注意也只能在两个线程之间进行数据交换。线程会阻塞在Exchanger的exchange方法上,直到另外一个线程也到了同一个Exchanger的exchange方法时,二者进行数据交换,然后两个线程继续执行自身相关的代码。

猜你喜欢

转载自blog.csdn.net/rambokitty/article/details/80492731