java多线程高级-concurrent实战(五)

java多线程高级-concurrent实战(五)

Semaphore

信号量维护了一个信号量许可集。线程可以通过调用acquire()来获取信号量的许可;当信号量中有可用的许可时,线程能获取该许可;否则线程必须等待,直到有可用的许可为止。 线程可以通过release()来释放它所持有的信号量许可.
Semaphore sema = new Semaphore(5);
这里申请了5个信号量,就是如果有100个线程,如果用synchronized,那么它是一个一个排队进来,用lock也是,一个一个排队进来,但是这里申请了5个信号量,就是说可以5个线程一起进来,然后5个线程中有执行完毕的释放信号量后其他线程就可以继续进入。


public class SemaphoreDemo {

    public static void main(String orgs[]) {
        ExecutorService exec = Executors.newCachedThreadPool();
        final Semaphore sema = new Semaphore(5);
        for (int i = 0; i < 10; i++) {
            final int no = i;
            Runnable runnable = new Runnable() {
                public void run() {
                    try {
                        sema.acquire();//获取许可证
                        System.out.println("print start....." + no);
                        Thread.sleep((long) (Math.random() * 1000));
                        sema.release();//访问后,释放资源
                        System.out.println("print end .....----------" + no);
                    } catch (Exception e) {
                        e.printStackTrace();
                    }
                }
            };
            exec.execute(runnable);
        }
        exec.shutdown();
    }
}

输出结果:
print start…..0
print start…..1
print start…..2
print start…..3
print start…..4
print end …..———-4
print start…..5
print end …..———-0
print start…..6
…..
….
0-4线程一起进入了sema.acquire();打印出了日志。

new Semaphore(5)允许几个线程一起执行,如果设置new Semaphore(1)则和lock差不多了,就是应许一个线程进入。

源码分析:

final int nonfairTryAcquireShared(int acquires) {
            //acquires=1;
            for (;;) {
                //获取状态
                //如果new Semaphore(5); available=5;
                int available = getState();
                //每次进入一个线程-1.
                int remaining = available - acquires;
                //当小于0的时候开始做CAS
                if (remaining < 0 ||
                    compareAndSetState(available, remaining))
                    return remaining;
            }
        }

其他代码就不一一分析了,都大同小异,请看AQS源码分析。

CountDownLatch

CountDownLatch是一个同步辅助类,在完成一组正在其他线程中执行的操作之前,它允许一个或多个线程一直等待。
CountDownLatch计数器=0时则继续运行,!=0时则为wait状态。

代码片段:


import java.util.concurrent.CountDownLatch;

class CountService implements Runnable {

    private CountDownLatch downLatch;
    private int temp;

    public CountService(CountDownLatch downLatch, int temp) {
        this.downLatch = downLatch;
        this.temp = temp;
    }

    public void run() {
        System.out.println("countDown次数--:" + temp);
        downLatch.countDown();
    }
}

public class CountDownDome2 {

    private static final int MAX_COUNT = 10;

    public static void main(String args[]) {

        CountDownLatch downLatch = new CountDownLatch(MAX_COUNT);
        for (int i = 0; i < MAX_COUNT; i++) {
            new Thread(new CountService(downLatch, i)).start();
        }
        try {
            downLatch.await();
        } catch (InterruptedException e) {
            e.printStackTrace();
        }
        System.out.println("总算执行完毕了。");

    }
}

输出结果:
countDown次数–:0
countDown次数–:1
countDown次数–:2
countDown次数–:3
countDown次数–:4
countDown次数–:5
countDown次数–:6
countDown次数–:7
countDown次数–:8
countDown次数–:9
总算执行完毕了。
解说:看着很简单,CountDownLatch 就是一个计数器。
1.CountDownLatch downLatch = new CountDownLatch(10);创建10个计数器。
2.downLatch.await();等着倒数10个数,当计数器为0时就可以往下执行了。
3.downLatch.countDown()每执行一次减1.一个线程可以执行多次。每次都会-1.而当计数器为0时,await线程就会被唤醒。开下面的例子

代码段2:


class CountService implements Runnable {

    private CountDownLatch downLatch;
    private int temp;

    public CountService(CountDownLatch downLatch, int temp) {
        this.downLatch = downLatch;
        this.temp = temp;
    }

    public void run() {
        System.out.println("countDown次数--:" + temp);
        downLatch.countDown();
        downLatch.countDown();
        downLatch.countDown();
    }
}

public class CountDownDome2 {

    private static final int MAX_COUNT = 10;

    public static void main(String args[]) {

        CountDownLatch downLatch = new CountDownLatch(MAX_COUNT);
        for (int i = 0; i < 5; i++) {
            new Thread(new CountService(downLatch, i)).start();
        }
        try {
            downLatch.await();
        } catch (InterruptedException e) {
            e.printStackTrace();
        }
        System.out.println("总算执行完毕了。");

    }
}

结果说明:
countDown次数–:0
countDown次数–:2
countDown次数–:1
countDown次数–:3
总算执行完毕了。
countDown次数–:4
解释:一个线程可以downLatch.countDown()多次。只要计数器为0,await就会被唤醒。

代码段3:

public static void main(String args[]) {

        CountDownLatch downLatch = new CountDownLatch(MAX_COUNT);
        for (int i = 0; i < 5; i++) {
            new Thread(new CountService(downLatch, i)).start();
        }
        try {
            downLatch.await();
        } catch (InterruptedException e) {
            e.printStackTrace();
        }

        try {
            downLatch.await();
        } catch (InterruptedException e) {
            e.printStackTrace();
        }
        System.out.println("总算执行完毕了。");

    }

结果说明:
countDown次数–:0
countDown次数–:2
countDown次数–:3
countDown次数–:1
总算执行完毕了。
countDown次数–:4
解释:await执行多次无效。

只要countDownlatch状态=0,await就可以往下执行。所以说countdownlatch的作用就是:CountDownLatch的用途就是一个线程A等待多个线程执行完毕后,线程A才执行开始执行。

代码段4:

public class Player implements Runnable {
    private int id;
    private CountDownLatch begin;
    private CountDownLatch end;

    public Player(int id, CountDownLatch begin, CountDownLatch end) {
        this.id = id;
        this.begin = begin;
        this.end = end;
    }

    public void run() {
        try {
            begin.await();//等着主线程解锁
            Thread.sleep((long) (Math.random() * 100));
            System.out.println("play id=" + id + " arrived");
        } catch (InterruptedException e) {
            e.printStackTrace();
        } finally {
            end.countDown();
        }
    }
}

public class CountDownLatchDemo {

    private static final int PLAYER_AMOUNT = 5;

    public static void main(String[] orgs) {
        CountDownLatch begin = new CountDownLatch(1);

        CountDownLatch end = new CountDownLatch(PLAYER_AMOUNT);

        List<Player> list = new ArrayList<Player>();
        for (int i = 0; i < PLAYER_AMOUNT; i++) {
            list.add(new Player(i + 1, begin, end));
        }

        ExecutorService exe = Executors.newFixedThreadPool(PLAYER_AMOUNT);
        for (Player player : list) {
            exe.execute(player);
        }
        System.out.println("begin......");
        begin.countDown();//计数器-1,当计数器等于0时,阻塞取消
        try {
            //阻塞线程,直到end.countDown()递减为0时,才往下执行,不然一直阻塞
            end.await();//主线程执行到这里等待end的计数器执行完成.如果这里不设置等待,则直接打印end.....
        } catch (InterruptedException e) {
            e.printStackTrace();
        } finally {
            System.out.println("end.......");
        }
        exe.shutdown();

    }

}

解说:
Player类启动了5个线程,但是要等待主线程begin.countDown()解锁,主线程解锁后,发现堵塞了,需要等待Player类中的5个线程解锁。其实就是互相记数,互相解锁。

源码分析:

private static final class Sync extends AbstractQueuedSynchronizer {
        private static final long serialVersionUID = 4982264981922014374L;

        //创建实例时将状态设置为count,倒计时的次数
        Sync(int count) {
            setState(count);
        }

        int getCount() {
            return getState();
        }

        protected int tryAcquireShared(int acquires) {
            return (getState() == 0) ? 1 : -1;
        }

        protected boolean tryReleaseShared(int releases) {
            // Decrement count; signal when transition to zero
            for (;;) {
                int c = getState();
                if (c == 0)
                    return false;
                //每次-1哦。
                int nextc = c-1;
                if (compareAndSetState(c, nextc))
                    return nextc == 0;
            }
        }
    }

await的时候调用tryAcquireShared尝试如果getstate()==0就就锁,如果大于0就执行线程阻塞操作。
CountDownLatch的实现非常简单,其他的地方就不贴出源码了,用的时候不明白自己点进去看看就OK了。

CyclicBarrier

CyclicBarrier是一个同步辅助类,允许一组线程互相等待,直到到达某个公共屏障点 (common barrier point)然后在往下执行。因为该 barrier 在释放等待线程后可以重用,所以称它为循环 的 barrier。

代码片段1:

package cn.thread.first.threadsyn.cyclicbarrier;

import java.util.concurrent.BrokenBarrierException;
import java.util.concurrent.CyclicBarrier;

/**
 * CyclicBarrier,是n个线程等待n个自线程执行完成后
 */
public class CyclicBarrierDemo {

    private static final int THREAD_NUM = 5;

    public static class WorkerThread implements Runnable {
        CyclicBarrier barrier;

        public WorkerThread(CyclicBarrier barrier) {
            this.barrier = barrier;
        }

        public void run() {
            try {
                System.out.println("start id=" + Thread.currentThread().getName());
                //所有线程执行到这里之后不在往下执行,需要等待,等待所有线程都执行到这里然后才能开始往下执行.
                //通知barrier已完成线程
                barrier.await();
                System.out.println("end id=" + Thread.currentThread().getName());
            } catch (InterruptedException e) {
                e.printStackTrace();
            } catch (BrokenBarrierException e) {
                e.printStackTrace();
            }
        }
    }

    public static void main(String args[]) {
        //当await的数量到达了设定的数量后,首先执行该Runnable对象
        CyclicBarrier cb = new CyclicBarrier(THREAD_NUM, new Runnable() {
            public void run() {
                System.out.println("Runnable start...........");
            }
        });


        for (int i = 0; i < THREAD_NUM; i++) {
            new Thread(new WorkerThread(cb), i + ":thread").start();
        }
        //事实证明需要等待,哈哈
        System.out.println("test main线程是否也需要等待子线程执行完,才能执行这里!");


    }

}

输出结果:
start id=0:thread
start id=1:thread
start id=2:thread
start id=3:thread
test main线程是否也需要等待子线程执行完,才能执行这里!
start id=4:thread
Runnable start………..
end id=2:thread
end id=1:thread
end id=4:thread
end id=3:thread
end id=0:thread

结果解说:0-4线程一起执行了,到了await时互相在等待;第四个线程是最后到达的,然后最后一个到达后表示5个线程到齐了,然后0-4自动被解锁,0-4线程又开始一起往下执行了。注意:“Runnable start………..”这里会在5个线程到齐后,先执行Runnable里面的run,然后才5个线程一起往下执行。

代码段2

package cn.thread.first.threadsyn.cyclicbarrier;

import java.util.concurrent.CyclicBarrier;
import java.util.concurrent.ExecutorService;
import java.util.concurrent.Executors;

public class CyclicBarrierTest {

    public static void main(String[] args) {
        ExecutorService exec = Executors.newCachedThreadPool();
        final CyclicBarrier cb = new CyclicBarrier(3);
        for (int i = 0; i < 3; i++) {
            Runnable runnable = new Runnable() {
                public void run() {
                    try {
                        Thread.sleep((long) (Math.random() * 10000));
                        System.out.println("即将到达集合地点1,当前已有 thread id=" + Thread.currentThread().getId() + ".....cb is " + cb.getNumberWaiting() + " ok");
                        if (cb.getNumberWaiting() == 2) {
                            System.out.println("this 1 await thread is ok........");
                        }
                        System.out.println("到达集合地点1 await .......");
                        cb.await();
                        System.out.println("到达集合地点1 end .......");

                        Thread.sleep((long) (Math.random() * 10000));
                        System.out.println("即将到达集合地点2,当前已有 thread id=" + Thread.currentThread().getId() + ".....cb is " + cb.getNumberWaiting() + " ok");
                        //每执行一次,CyclicBarrier都会重新计数,所以会出现这种情况哦
                        if (cb.getNumberWaiting() == 2) {
                            System.out.println("this 2 await thread is ok........");
                        }
                        System.out.println("到达集合地点2 await .......");
                        cb.await();//每执行一次,CyclicBarrier都会重新计数,所以会出现这种情况哦
                        System.out.println("到达集合地点2 end ,,,,,,,,,,,,,,,");

                        Thread.sleep((long) (Math.random() * 10000));
                        System.out.println("即将到达集合地点3,当前已有 thread id=" + Thread.currentThread().getId() + ".....cb is " + cb.getNumberWaiting() + " ok");
                        if (cb.getNumberWaiting() == 2) {
                            System.out.println("this 3 await thread is ok........");
                        }
                        System.out.println("到达集合地点3 await .......");
                        cb.await();
                        System.out.println("到达集合地点3 end  ............................");

                        cb.await();
                        System.out.println("ha ha ha ha ......");
                    } catch (Exception e) {
                        e.printStackTrace();
                    }
                }
            };
            exec.execute(runnable);
        }
        exec.shutdown();
    }
}

说白了就是:创建10个CyclicBarrier,大家一起出发,到了await的时候,必须10个线程一起到达,才能再次出发。就好像10个人一起出发,大家约定到了A站点名,如果10人都到齐了,然后开始一起出发B站,到了B站点名,,以此类推。一个await等于一个站点。

CyclicBarrier的await可以多次调用,每调用一次就是上面说的一个站点。10人到齐后await状态会被重置。

源码分析:

public CyclicBarrier(int parties, Runnable barrierAction) {
        if (parties <= 0) throw new IllegalArgumentException();
        this.parties = parties;//初始化等待总数
        this.count = parties;
        this.barrierCommand = barrierAction;
    }

//调用await时        
private int dowait(boolean timed, long nanos)
        throws InterruptedException, BrokenBarrierException,
               TimeoutException {
        final ReentrantLock lock = this.lock;
        //每个线程进来时,加锁,这里加锁了, 那其他线程怎么进来呢?看下面的trip.await();
        lock.lock();
        try {
            final Generation g = generation;

            if (g.broken)
                throw new BrokenBarrierException();

            if (Thread.interrupted()) {
                breakBarrier();
                throw new InterruptedException();
            }

            int index = --count;//每进来一个线程减1
            if (index == 0) {  //当等于0的时候唤醒所有线程哦。
                boolean ranAction = false;
                try {
                    final Runnable command = barrierCommand;
                    //看看有没有runnable,如果有先执行runnable
                    if (command != null)
                        command.run();
                    ranAction = true;
                    nextGeneration();//唤醒所有线程
                    return 0;
                } finally {
                    if (!ranAction)
                        breakBarrier();
                }
            }

            // loop until tripped, broken, interrupted, or timed out
            for (;;) {
                try {
                    if (!timed)
                        trip.await();//如果大家没有到齐,那么久await,线程挂起,等待。
                    else if (nanos > 0L)
                        nanos = trip.awaitNanos(nanos);
                } catch (InterruptedException ie) {
                    if (g == generation && ! g.broken) {
                        breakBarrier();
                        throw ie;
                    } else {
                        // We're about to finish waiting even if we had not
                        // been interrupted, so this interrupt is deemed to
                        // "belong" to subsequent execution.
                        Thread.currentThread().interrupt();
                    }
                }

                if (g.broken)
                    throw new BrokenBarrierException();

                if (g != generation)
                    return index;

                if (timed && nanos <= 0L) {
                    breakBarrier();
                    throw new TimeoutException();
                }
            }
        } finally {
            lock.unlock();
        }
    }    

private void nextGeneration() {
        // signal completion of last generation
        trip.signalAll();//唤醒所有等待线程
        // set up next generation
        //重置等待计数器,假如初始化是10,--count到=0,这里又把10给赋值回去了。
        //所以可以多次使用await了。
        count = parties;
        generation = new Generation();
    }    

从源码可以看出,当所有线程到齐后自动唤醒所有线程(trip.signalAll()解锁所有线程,count = parties并重置计数器),不用像其他锁一样,需要其他的线程显示的唤醒等待线程。
注意:源码里面的–count,因为最后进来的线程不再需要进入等待了,–count后index=0;直接开始唤醒等待线程;这也说明,最后一个进来的线程没有进入等待队列中。

总结

这里将同时开启10个线程并发访问:MAX_THREAD=10;

  1. new Semaphore(5),表示同时允许5个线程获取许可acquire,获取到许可的线程则可以顺利执行不用线程阻塞,其他5个线程则阻塞在acquire外面;当前面5个线程有释放release()许可证,并唤醒阻塞的线程,而阻塞的线程开始竞争获取acquire,前面5个释放了几个,后面的就可以获取多少个;假如前面5个线程同时释放许可证,则后面5个线程可以同时获取到许可证。
  2. new CountDownLatch(5),这里定义了5个计数器,某个线程执行了await后会被阻塞,前面定义了10个线程,这10个线程里面,需要将计数器downLatch.countDown()最少执行5次才行。执行5次后被await阻塞的线程就能够继续执行了。多次await无效哦
  3. new CyclicBarrier(5),这个有10个并发线程,CyclicBarrier(5)表示,需要凑够5个线程才能往下执行。
    10个线程,将分2波被执行哦。假如new CyclicBarrier(4),必须要凑够4个人才能往下走,10个线程,分为4,4,2。4,4可以往下执行,2将被阻塞,一直等到两外2个线程到达。

注意:

  • CyclicBarrier=5则线程数需要是5的整数倍才行,不然程序会一直被阻塞哦。CyclicBarrier=5,就好像班车一样,满5个人才走,不满5个人不走,如果有10个人,则前面5个人送走后,再送后面5个人;需要等待5个人同时到齐才能发车,5个人必须一起上车,只有当5个人都下车后才能再次上5个人
  • Semaphore=5;10个线程,可以先通过5个线程,其他线程阻塞,5个线程通过后又可以再次进入最多5个线程。和公交一样,来一个人可以走,来两个人也可以走,但是一辆车最多只能上5个人;当车上满5个人其他人需要等待,当车上有1个人下车了,那么就允许外面的1个人上车;如果5个人都下车了则又可以5个人上车了。车最多只能拉5个人,不管来了几个人,车都是在走着的;外面的人需要等待车上面的人下车后才能上车;下一个人上一个人,下2个人上2个人。
  • CountDownLatch=5;await需要倒计时5,4,3,2,1,开始走。await重复使用无效。班车要走了,师傅倒计时,54321。一定要有54321,不然班车不走。一但54321计时结束后就可以走了,再次await也不会停,再次54321也是无效的。师傅只会叫一遍54321

CountDownLatch和CyclicBarrier的区别

  • CountDownLatch的作用是允许1或N个线程等待其他线程完成执行;而CyclicBarrier则是允许N个线程相互等待。
  • CountDownLatch的计数器无法被重置;CyclicBarrier的计数器可以被重置后使用,因此它被称为是循环的barrier。

猜你喜欢

转载自blog.csdn.net/piaoslowly/article/details/81562275