文档章节

CyclicBarrier让多线程齐步走

王孟君
 王孟君
发布于 2016/11/06 09:42
字数 3923
阅读 90
收藏 2

从JDK 1.5之后,在java.util.concurrent包下引入了好多的处理多线程的工具类,本文首先会介绍CyclicBarrier辅助工具类,其次将用CyclicBarrier工具类来完成一个实例,最后将给出CyclicBarrier和CountDownLatch的几点比较。 

之前关于CountDownLatch的博文,请参考使用CountDownLatch协调子线程

以前在<<编写高质量代码-改善Java程序的151个建议>>一书中看到有一节的标题是“CyclicBarrier让多线程齐步走”,觉得这标题挺不错的,所以在写这篇博文的时候也采用了这个名字。 

CyclicBarrier工具类介绍

CyclicBarrier描述

CyclicBarrier是一个同步辅助工具类,它允许一组线程相互等待,直到到达一个公共的栏栅点。

CyclicBarrier对于那些包含一组固定大小线程,并且这些线程必须不时地相互等待的程序非常有用。之所以将其称之为循环的Barrier是因为该Barrier在等待的线程释放之后可以重用。 

CyclicBarrier 支持一个可选的 Runnable 命令,在一组线程中的最后一个线程到达之后(但在释放所有线程之前),该命令只在每个屏障点运行一次。若在继续所有参与线程之前更新共享状态,此屏障操作 很有用。 

上面的介绍来自于CyclicBarrier类的注释。 

/**
 * A synchronization aid that allows a set of threads to all wait for
 * each other to reach a common barrier point.  CyclicBarriers are
 * useful in programs involving a fixed sized party of threads that
 * must occasionally wait for each other. The barrier is called
 * [i]cyclic[/i] because it can be re-used after the waiting threads
 * are released.
 *
 * <p>A <tt>CyclicBarrier</tt> supports an optional {@link Runnable} command
 * that is run once per barrier point, after the last thread in the party
 * arrives, but before any threads are released.
 * This [i]barrier action[/i] is useful
 * for updating shared-state before any of the parties continue.
 */

CyclicBarrier工具类相关类图

 CyclicBarrier采用ConditionLock来完成线程之间的同步。

相关的类图是CyclicBarrier类内容如下:

CyclicBarrier类源代码

/*
 * @(#)CyclicBarrier.java	1.12 06/03/30
 *
 * Copyright 2006 Sun Microsystems, Inc. All rights reserved.
 * SUN PROPRIETARY/CONFIDENTIAL. Use is subject to license terms.
 */

package java.util.concurrent;
import java.util.concurrent.locks.*;

/**
 * A synchronization aid that allows a set of threads to all wait for
 * each other to reach a common barrier point.  CyclicBarriers are
 * useful in programs involving a fixed sized party of threads that
 * must occasionally wait for each other. The barrier is called
 * [i]cyclic[/i] because it can be re-used after the waiting threads
 * are released.
 *
 * <p>A <tt>CyclicBarrier</tt> supports an optional {@link Runnable} command
 * that is run once per barrier point, after the last thread in the party
 * arrives, but before any threads are released.
 * This [i]barrier action[/i] is useful
 * for updating shared-state before any of the parties continue.
 *
 * <p><b>Sample usage:</b> Here is an example of
 *  using a barrier in a parallel decomposition design:
 * <pre>
 * class Solver {
 *   final int N;
 *   final float[][] data;
 *   final CyclicBarrier barrier;
 *
 *   class Worker implements Runnable {
 *     int myRow;
 *     Worker(int row) { myRow = row; }
 *     public void run() {
 *       while (!done()) {
 *         processRow(myRow);
 *
 *         try {
 *           barrier.await();
 *         } catch (InterruptedException ex) {
 *           return;
 *         } catch (BrokenBarrierException ex) {
 *           return;
 *         }
 *       }
 *     }
 *   }
 *
 *   public Solver(float[][] matrix) {
 *     data = matrix;
 *     N = matrix.length;
 *     barrier = new CyclicBarrier(N,
 *                                 new Runnable() {
 *                                   public void run() {
 *                                     mergeRows(...);
 *                                   }
 *                                 });
 *     for (int i = 0; i < N; ++i)
 *       new Thread(new Worker(i)).start();
 *
 *     waitUntilDone();
 *   }
 * }
 * </pre>
 * Here, each worker thread processes a row of the matrix then waits at the
 * barrier until all rows have been processed. When all rows are processed
 * the supplied {@link Runnable} barrier action is executed and merges the
 * rows. If the merger
 * determines that a solution has been found then <tt>done()</tt> will return
 * <tt>true</tt> and each worker will terminate.
 *
 * <p>If the barrier action does not rely on the parties being suspended when
 * it is executed, then any of the threads in the party could execute that
 * action when it is released. To facilitate this, each invocation of
 * {@link #await} returns the arrival index of that thread at the barrier.
 * You can then choose which thread should execute the barrier action, for
 * example:
 * <pre>  if (barrier.await() == 0) {
 *     // log the completion of this iteration
 *   }</pre>
 *
 * <p>The <tt>CyclicBarrier</tt> uses an all-or-none breakage model
 * for failed synchronization attempts: If a thread leaves a barrier
 * point prematurely because of interruption, failure, or timeout, all
 * other threads waiting at that barrier point will also leave
 * abnormally via {@link BrokenBarrierException} (or
 * {@link InterruptedException} if they too were interrupted at about
 * the same time).
 *
 * <p>Memory consistency effects: Actions in a thread prior to calling
 * {@code await()}
 * [url=package-summary.html#MemoryVisibility]<i>happen-before</i>[/url]
 * actions that are part of the barrier action, which in turn
 * <i>happen-before</i> actions following a successful return from the
 * corresponding {@code await()} in other threads.
 *
 * @since 1.5
 * @see CountDownLatch
 *
 * @author Doug Lea
 */
public class CyclicBarrier {
    /**
     * Each use of the barrier is represented as a generation instance.
     * The generation changes whenever the barrier is tripped, or
     * is reset. There can be many generations associated with threads
     * using the barrier - due to the non-deterministic way the lock
     * may be allocated to waiting threads - but only one of these
     * can be active at a time (the one to which <tt>count</tt> applies)
     * and all the rest are either broken or tripped.
     * There need not be an active generation if there has been a break
     * but no subsequent reset.
     */
    private static class Generation {
        boolean broken = false;
    }

    /** The lock for guarding barrier entry */
    private final ReentrantLock lock = new ReentrantLock();
    /** Condition to wait on until tripped */
    private final Condition trip = lock.newCondition();
    /** The number of parties */
    private final int parties;
    /* The command to run when tripped */
    private final Runnable barrierCommand;
    /** The current generation */
    private Generation generation = new Generation();

    /**
     * Number of parties still waiting. Counts down from parties to 0
     * on each generation.  It is reset to parties on each new
     * generation or when broken.
     */
    private int count;

    /**
     * Updates state on barrier trip and wakes up everyone.
     * Called only while holding lock.
     */
    private void nextGeneration() {
        // signal completion of last generation
        trip.signalAll();
        // set up next generation
        count = parties;
        generation = new Generation();
    }

    /**
     * Sets current barrier generation as broken and wakes up everyone.
     * Called only while holding lock.
     */
    private void breakBarrier() {
        generation.broken = true;
	count = parties;
        trip.signalAll();
    }

    /**
     * Main barrier code, covering the various policies.
     */
    private int dowait(boolean timed, long nanos)
        throws InterruptedException, BrokenBarrierException,
               TimeoutException {
        final ReentrantLock lock = this.lock;
        lock.lock();
        try {
            final Generation g = generation;

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

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

           int index = --count;
           if (index == 0) {  // tripped
               boolean ranAction = false;
               try {
		   final Runnable command = barrierCommand;
                   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();
                    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();
        }
    }

    /**
     * Creates a new <tt>CyclicBarrier</tt> that will trip when the
     * given number of parties (threads) are waiting upon it, and which
     * will execute the given barrier action when the barrier is tripped,
     * performed by the last thread entering the barrier.
     *
     * @param parties the number of threads that must invoke {@link #await}
     *        before the barrier is tripped
     * @param barrierAction the command to execute when the barrier is
     *        tripped, or {@code null} if there is no action
     * @throws IllegalArgumentException if {@code parties} is less than 1
     */
    public CyclicBarrier(int parties, Runnable barrierAction) {
        if (parties <= 0) throw new IllegalArgumentException();
        this.parties = parties;
        this.count = parties;
        this.barrierCommand = barrierAction;
    }

    /**
     * Creates a new <tt>CyclicBarrier</tt> that will trip when the
     * given number of parties (threads) are waiting upon it, and
     * does not perform a predefined action when the barrier is tripped.
     *
     * @param parties the number of threads that must invoke {@link #await}
     *        before the barrier is tripped
     * @throws IllegalArgumentException if {@code parties} is less than 1
     */
    public CyclicBarrier(int parties) {
        this(parties, null);
    }

    /**
     * Returns the number of parties required to trip this barrier.
     *
     * @return the number of parties required to trip this barrier
     */
    public int getParties() {
        return parties;
    }

    /**
     * Waits until all {@linkplain #getParties parties} have invoked
     * <tt>await</tt> on this barrier.
     *
     * <p>If the current thread is not the last to arrive then it is
     * disabled for thread scheduling purposes and lies dormant until
     * one of the following things happens:
     * [list]
     * <li>The last thread arrives; or
     * <li>Some other thread {@linkplain Thread#interrupt interrupts}
     * the current thread; or
     * <li>Some other thread {@linkplain Thread#interrupt interrupts}
     * one of the other waiting threads; or
     * <li>Some other thread times out while waiting for barrier; or
     * <li>Some other thread invokes {@link #reset} on this barrier.
     * [/list]
     *
     * <p>If the current thread:
     * [list]
     * <li>has its interrupted status set on entry to this method; or
     * <li>is {@linkplain Thread#interrupt interrupted} while waiting
     * [/list]
     * then {@link InterruptedException} is thrown and the current thread's
     * interrupted status is cleared.
     *
     * <p>If the barrier is {@link #reset} while any thread is waiting,
     * or if the barrier {@linkplain #isBroken is broken} when
     * <tt>await</tt> is invoked, or while any thread is waiting, then
     * {@link BrokenBarrierException} is thrown.
     *
     * <p>If any thread is {@linkplain Thread#interrupt interrupted} while waiting,
     * then all other waiting threads will throw
     * {@link BrokenBarrierException} and the barrier is placed in the broken
     * state.
     *
     * <p>If the current thread is the last thread to arrive, and a
     * non-null barrier action was supplied in the constructor, then the
     * current thread runs the action before allowing the other threads to
     * continue.
     * If an exception occurs during the barrier action then that exception
     * will be propagated in the current thread and the barrier is placed in
     * the broken state.
     *
     * @return the arrival index of the current thread, where index
     *         <tt>{@link #getParties()} - 1</tt> indicates the first
     *         to arrive and zero indicates the last to arrive
     * @throws InterruptedException if the current thread was interrupted
     *         while waiting
     * @throws BrokenBarrierException if [i]another[/i] thread was
     *         interrupted or timed out while the current thread was
     *         waiting, or the barrier was reset, or the barrier was
     *         broken when {@code await} was called, or the barrier
     *         action (if present) failed due an exception.
     */
    public int await() throws InterruptedException, BrokenBarrierException {
        try {
            return dowait(false, 0L);
        } catch (TimeoutException toe) {
            throw new Error(toe); // cannot happen;
        }
    }

    /**
     * Waits until all {@linkplain #getParties parties} have invoked
     * <tt>await</tt> on this barrier, or the specified waiting time elapses.
     *
     * <p>If the current thread is not the last to arrive then it is
     * disabled for thread scheduling purposes and lies dormant until
     * one of the following things happens:
     * [list]
     * <li>The last thread arrives; or
     * <li>The specified timeout elapses; or
     * <li>Some other thread {@linkplain Thread#interrupt interrupts}
     * the current thread; or
     * <li>Some other thread {@linkplain Thread#interrupt interrupts}
     * one of the other waiting threads; or
     * <li>Some other thread times out while waiting for barrier; or
     * <li>Some other thread invokes {@link #reset} on this barrier.
     * [/list]
     *
     * <p>If the current thread:
     * [list]
     * <li>has its interrupted status set on entry to this method; or
     * <li>is {@linkplain Thread#interrupt interrupted} while waiting
     * [/list]
     * then {@link InterruptedException} is thrown and the current thread's
     * interrupted status is cleared.
     *
     * <p>If the specified waiting time elapses then {@link TimeoutException}
     * is thrown. If the time is less than or equal to zero, the
     * method will not wait at all.
     *
     * <p>If the barrier is {@link #reset} while any thread is waiting,
     * or if the barrier {@linkplain #isBroken is broken} when
     * <tt>await</tt> is invoked, or while any thread is waiting, then
     * {@link BrokenBarrierException} is thrown.
     *
     * <p>If any thread is {@linkplain Thread#interrupt interrupted} while
     * waiting, then all other waiting threads will throw {@link
     * BrokenBarrierException} and the barrier is placed in the broken
     * state.
     *
     * <p>If the current thread is the last thread to arrive, and a
     * non-null barrier action was supplied in the constructor, then the
     * current thread runs the action before allowing the other threads to
     * continue.
     * If an exception occurs during the barrier action then that exception
     * will be propagated in the current thread and the barrier is placed in
     * the broken state.
     *
     * @param timeout the time to wait for the barrier
     * @param unit the time unit of the timeout parameter
     * @return the arrival index of the current thread, where index
     *         <tt>{@link #getParties()} - 1</tt> indicates the first
     *         to arrive and zero indicates the last to arrive
     * @throws InterruptedException if the current thread was interrupted
     *         while waiting
     * @throws TimeoutException if the specified timeout elapses
     * @throws BrokenBarrierException if [i]another[/i] thread was
     *         interrupted or timed out while the current thread was
     *         waiting, or the barrier was reset, or the barrier was broken
     *         when {@code await} was called, or the barrier action (if
     *         present) failed due an exception
     */
    public int await(long timeout, TimeUnit unit)
        throws InterruptedException,
               BrokenBarrierException,
               TimeoutException {
        return dowait(true, unit.toNanos(timeout));
    }

    /**
     * Queries if this barrier is in a broken state.
     *
     * @return {@code true} if one or more parties broke out of this
     *         barrier due to interruption or timeout since
     *         construction or the last reset, or a barrier action
     *         failed due to an exception; {@code false} otherwise.
     */
    public boolean isBroken() {
        final ReentrantLock lock = this.lock;
        lock.lock();
        try {
            return generation.broken;
        } finally {
            lock.unlock();
        }
    }

    /**
     * Resets the barrier to its initial state.  If any parties are
     * currently waiting at the barrier, they will return with a
     * {@link BrokenBarrierException}. Note that resets [i]after[/i]
     * a breakage has occurred for other reasons can be complicated to
     * carry out; threads need to re-synchronize in some other way,
     * and choose one to perform the reset.  It may be preferable to
     * instead create a new barrier for subsequent use.
     */
    public void reset() {
        final ReentrantLock lock = this.lock;
        lock.lock();
        try {
            breakBarrier();   // break the current generation
            nextGeneration(); // start a new generation
        } finally {
            lock.unlock();
        }
    }

    /**
     * Returns the number of parties currently waiting at the barrier.
     * This method is primarily useful for debugging and assertions.
     *
     * @return the number of parties currently blocked in {@link #await}
     */
    public int getNumberWaiting() {
        final ReentrantLock lock = this.lock;
        lock.lock();
        try {
            return parties - count;
        } finally {
            lock.unlock();
        }
    }
}

 CyclicBarrier工具类的使用案例 

案例描述

CyclicBarrier可以让所有线程都处于等待状态(阻塞),然后在满足条件的情况下继续执行。

使用CyclicBarrier模拟几个小组出去游玩的场景,如:

几个小组包一辆车去旅游,一天行程包括上午小组自由活动和下午自由活动。

各个小组早上自由活动,但是11点半大巴车上集合,然后吃饭并赶赴下一个景区。 

各个小组下午自由活动,但是要5点半大巴车上集合,然后一起回去。 

代码与测试

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

/**
 * 
 * @author wangmengjun
 *
 */
public class TeamGroup implements Runnable {

	private final CyclicBarrier barrier;

	private int groupNumber;

	/**
	 * @param barrier
	 * @param groupNumber
	 */
	public TeamGroup(CyclicBarrier barrier, int groupNumber) {
		this.barrier = barrier;
		this.groupNumber = groupNumber;
	}

	public void run() {
		try {
			print();
			barrier.await();
		} catch (InterruptedException e) {
			// TODO Auto-generated catch block
			e.printStackTrace();
		} catch (BrokenBarrierException e) {
			// TODO Auto-generated catch block
			e.printStackTrace();
		}
	}

	private void print() {
		System.out.println(String.format("第%d组完成该地景点浏览,并回到集合点", groupNumber));
	}

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

public class CyclicBarrierTest {

	private static final int THREAD_SLEEP_MILLIS = 6000;

	/** 旅游小数的个数 */
	private static final int NUMBER_OF_GROUPS = 6;

	/** 观光是否结束的标识 */
	private static boolean tourOver = false;

	public static void main(String[] args) {

		ExecutorService service = Executors
				.newFixedThreadPool(NUMBER_OF_GROUPS);

		CyclicBarrier cb = new CyclicBarrier(NUMBER_OF_GROUPS, new Runnable() {

			public void run() {
				/*
				 * 如果一天的游玩结束了,大家可以坐大巴回去了... ...
				 */
				if (isTourOver()) {
					System.out.println("各个小组都集合到大巴上,准备回家.. ...");
				}

			}
		});

		System.out.println("用CyclicBarrier辅助工具类模拟旅游过程中小组集合::");

		/**
		 * 上午各个小组自由活动,然后在某个点,比如11点半集合到大巴上。
		 */
		tourInTheMorning(service, cb);
		sleep(THREAD_SLEEP_MILLIS);

		/**
		 * 调用reset方法,将barrier设置到初始化状态。
		 * 
		 */
		cb.reset();

		/**
		 * 下午各个小组自由活动,然后在某个点,比如11点半集合到大巴上。
		 */
		tourInTheAfternoon(service, cb);

		/**
		 * 下午小组集合完毕后,一天的观光就结束了,将标志位记为true;
		 */
		tourOver = true;

		sleep(THREAD_SLEEP_MILLIS);
		service.shutdown();

	}

	/**
	 * @return the tourOver
	 */
	public static boolean isTourOver() {
		return tourOver;
	}

	/**
	 * @param tourOver
	 *            the tourOver to set
	 */
	public static void setTourOver(boolean tourOver) {
		CyclicBarrierTest.tourOver = tourOver;
	}

	private static void tourInTheMorning(ExecutorService service,
			final CyclicBarrier cb) {
		System.out.println("早上自由玩... ... ");
		for (int groupNumber = 1; groupNumber <= NUMBER_OF_GROUPS; groupNumber++) {
			service.execute(new TeamGroup(cb, groupNumber));
		}
	}

	private static void tourInTheAfternoon(ExecutorService service,
			final CyclicBarrier cb) {
		System.out.println("下午自由玩... ... ");
		for (int groupNumber = 1; groupNumber <= NUMBER_OF_GROUPS; groupNumber++) {
			service.execute(new TeamGroup(cb, groupNumber));
		}
	}

	private static void sleep(long millis) {
		try {
			Thread.sleep(millis);
		} catch (InterruptedException e) {
			// TODO Auto-generated catch block
			e.printStackTrace();
		}
	}
}

某次运行的结果如下:

用CyclicBarrier辅助工具类模拟旅游过程中小组集合::
早上自由玩... ... 
第2组完成该地景点浏览,并回到集合点
第1组完成该地景点浏览,并回到集合点
第5组完成该地景点浏览,并回到集合点
第3组完成该地景点浏览,并回到集合点
第6组完成该地景点浏览,并回到集合点
第4组完成该地景点浏览,并回到集合点
下午自由玩... ... 
第2组完成该地景点浏览,并回到集合点
第4组完成该地景点浏览,并回到集合点
第3组完成该地景点浏览,并回到集合点
第6组完成该地景点浏览,并回到集合点
第1组完成该地景点浏览,并回到集合点
第5组完成该地景点浏览,并回到集合点
各个小组都集合到大巴上,准备回家.. ...

CyclicBarrier与CountDownLatch比较

相同点

两者都是用于线程同步的辅助工具类,都提供了await方法来达到线程等待。 

不同点

从类的实现上看 

CountDownLatch通过一个继承AbstractQueuedSynchronizer的内部类Sync来完成同步。 

CyclicBarrier通过Condition和Lock来完成同步。

从类的用途上看

 CountDownLatch: 一个或者是一部分线程,等待另外一部线程都完成操作。 

CyclicBarrier: 所有线程互相等待完成。 

从适合场合来看

CountDownLatch中计数是不能被重置的。

如果需要一个可以重置计数的版本,需要考虑使用CyclicBarrie。

CountDownLatch适用于一次同步当使用CountDownLatch时,任何线程允许多次调用countDown(). 那些调用了await()方法的线程将被阻塞,直到那些没有被阻塞线程调用countDown()使计数到达0为止。 

 

相反CyclicBarrier适用于多个同步点。

例如:一组正在运算的线程,在进入下一个阶段计算之前需要同步。 

 

与CountDownLatch不同,一个处于某个阶段的线程调用了await()方法将会被阻塞,直到所有属于这个阶段的线程都调用了await()方法为止。 

在CyclicBarrier中,如果一个线程由于中断,失败或者超时等原因,过早地离开了栅栏点,那么所有在栅栏点等待的其它线程也会通过BrokenBarrierException或者IterupedException异常地离开。 

 

从关注点上来看

使用CountDownLatch时,它关注的一个线程或者多个线程需要在其它在一组线程完成操作之后,在去做一些事情。比如:服务的启动等。

CyclicBarrier更加关注的是公共的栅栏点(Common Barrier point),关注的是这个点上的同步。这个点之前之后的事情并不需要太多的关注。比如:一个并行计算需要分几个阶段完成,在一个阶段完成进入到下一个阶段之前,需要同步,这时候CyclicBarrie很适合。 

由于知识的原因,上述例子以及CountDownLatch和CyclicBarrier的比较上会存在不足,如果有问题请大家指正,也希望大家能够提供两者其它方面的不同之处,一起学习分享。

 

© 著作权归作者所有

王孟君

王孟君

粉丝 226
博文 94
码字总数 221044
作品 0
杭州
高级程序员
私信 提问
CountDownLatch、CyclicBarrier 的对比

CountDownLatch、CyclicBarrier 都可以用于: 在多线程异步执行过程中,执行预定的流程后唤醒 指定的线程 进行相应的操作. 区别: 1,CountDownLatch 唤醒过一次,就废弃了;CyclicBarrier可以...

yuanqy
2016/11/11
69
0
用ExecutorService&cyclicBarrier&countDownLatch实现...

这个博客介绍的不错:http://my.oschina.net/jielucky/blog/157946 http://my.oschina.net/adwangxiao/blog/110188 我再顺着这个博客往下写: 赛马是个不错的多线程场景,包括所有赛马都准备...

P&H
2013/09/30
90
0
【死磕Java并发】-----J.U.C之并发工具类:CyclicBarrier

此篇博客所有源码均来自JDK 1.8 CyclicBarrier,一个同步辅助类,在API中是这么介绍的: 它允许一组线程互相等待,直到到达某个公共屏障点 (common barrier point)。在涉及一组固定大小的线程...

chenssy
2017/04/13
0
0
多线程结果合并计算CyclicBarrier

一、前言 前段时间去斗鱼面试被刷一直耿耿于怀,没办法自己太过菜鸡 ( Ĭ ^ Ĭ )。记得当时面试官问我“假如有三个询价接口,需要获取到最低报价?”吐露个心声,我毕业至今工作中没什么机会...

叫我宫城大人
2017/11/16
0
0
java多线程系列:通过对战游戏学习CyclicBarrier

CyclicBarrier是java.util.concurrent包下面的一个工具类,字面意思是可循环使用(Cyclic)的屏障(Barrier),通过它可以实现让一组线程到达一个屏障(也可以叫同步点)时被阻塞,直到最后一...

huangzd
2018/01/06
0
0

没有更多内容

加载失败,请刷新页面

加载更多

如何在Linux中复制文档

在办公室里复印文档过去需要专门的员工与机器。如今,复制是电脑用户无需多加思考的任务。在电脑里复制数据是如此微不足道的事,以致于你还没有意识到复制就发生了,例如当拖动文档到外部硬盘...

老孟的Linux私房菜
27分钟前
2
0
SpringBoot 集成MongoDB

一、MongoDB 简介 MongoDB 如今是最流行的 NoSQL 数据库,被广泛应用于各行各业中,很多创业公司数据库选型就直接使用了 MongoDB,但对于大部分公司,使用 MongoDB 的场景是做大规模数据查询...

zw965
36分钟前
14
0
使用 Envoy 和 AdGuard Home 阻挡烦人的广告

> 原文链接:使用 Envoy 和 AdGuard Home 阻挡烦人的广告 通常我们使用网络时,宽带运营商会为我们分配一个 DNS 服务器。这个 DNS 通常是最快的,距离最近的服务器,但会有很多问题,比如: ...

米开朗基杨
今天
22
0
springboot之全局处理异常封装

springboot之全局处理异常封装 简介 在项目中经常出现系统异常的情况,比如NullPointerException等等。如果默认未处理的情况下,springboot会响应默认的错误提示,这样对用户体验不是友好,系...

Purgeyao
今天
27
0
cookie

cookie: n. 饼干;小甜点 为什么会引入Cookie(在客户端保持http状态) 因为http协议是一种无状态协议,web服务器本身不能识别出哪些请求是同一个服务器发送的,浏览器的每一次请求都是独立...

五公里
今天
27
0

没有更多内容

加载失败,请刷新页面

加载更多

返回顶部
顶部