Java线程中的join方法总结

直通车:

JOIN

假如有这么一个需求:我们需要解析Excel里多个Sheet的数据,此时可以考虑多个线程,每个线程解析一个Sheet中的数据,等所有的数据解析完成后,提示完成。那么这个需求中需要主线程等待所有的解析Sheet的子线程结束之后才能提示。那么此时就可以使用join()方法。

public static void main(String[] args) throws InterruptedException {
        Thread thread1 = new Thread(new Runnable() {
            @Override
            public void run() {
                try {
                    Thread.sleep(5000);
                } catch (InterruptedException e) {
                    e.printStackTrace();
                }
                System.out.println("thread1 finished.");
            }
        });

        Thread thread2 = new Thread(new Runnable() {
            @Override
            public void run() {
                try {
                    Thread.sleep(4000);
                } catch (InterruptedException e) {
                    e.printStackTrace();
                }
                System.out.println("thread2 finished.");
            }
        });

        thread1.start();
        thread2.start();
        /**
         * Thread类中的join方法的主要作用就是同步,它可以使得线程之间的并行执行变为串行执行。
         * join的意思是使得放弃当前线程的执行,并返回对应的线程,例如下面代码的意思就是:
         * 程序在main线程中调用thread1线程的join方法,则main线程放弃cpu控制权,并返回thread1线程继续执行直到线程thread1执行完毕
         * 所以结果是thread1线程执行完后,才到主线程执行,相当于在main线程中同步thread1线程,thread1执行完了,main线程才有执行的机会
         */
        thread1.join();
        System.out.println("-------");
        thread2.join();

        System.out.println("main thread finished.");
    }

--- 输出如下:  

thread2 finished.
thread1 finished.
-------
main thread finished.

Process finished with exit code 0
join用于让当前执行线程等待join线程执行结束。其实现原理是不停的检查join线程是否存活,如果join线程存活则让当前线程永远等待,通过调用线程的wait方法来达到同步的目的。其中,wait(0)表示永远等待下去。源码如下:join方法实现原理
/**
 * Waits at most {@code millis} milliseconds for this thread to
 * die. A timeout of {@code 0} means to wait forever.
 *
 * <p> This implementation uses a loop of {@code this.wait} calls
 * conditioned on {@code this.isAlive}. As a thread terminates the
 * {@code this.notifyAll} method is invoked. It is recommended that
 * applications not use {@code wait}, {@code notify}, or
 * {@code notifyAll} on {@code Thread} instances.
 */
public final synchronized void join(long millis)
    throws InterruptedException {
        long base = System.currentTimeMillis();
        long now = 0;

        if (millis < 0) {
            throw new IllegalArgumentException("timeout value is negative");
        }

        if (millis == 0) {
            while (isAlive()) {
                wait(0);
            }
        } else {
            while (isAlive()) {
                long delay = millis - now;
                if (delay <= 0) {
                    break;
                }
                wait(delay);
                now = System.currentTimeMillis() - base;
            }
        }
    }
直到join线程终止后,线程的this.notifyAll()方法会被调用(调用notifyAll()方法是在JVM里实现的)。

API java.lang.Thread {@join}:

/**
 * Waits at most {@code millis} milliseconds for this thread to die. A timeout of {@code 0} means to wait forever.
 */
void join(long millis) throws InterruptedException 

/**
 * Waits at most {@code millis} milliseconds plus {@code nanos} nanoseconds for this thread to die.
 */
void join(long millis, int nanos) throws InterruptedException

/**
 * Waits for this thread to die.same as join(0)
 */    
public final void join() throws InterruptedException {
    join(0);
}

API java.lang.Object {@wait}:

/**
 * The specified amount of real time has elapsed, more or less.  If 
 * {@code timeout} is zero, however, then real time is not taken into 
 * consideration and the thread simply waits until notified.
 */
public final native void wait(long timeout) throws InterruptedException

/**
 * This method is similar to the {@code wait} method of one
 * argument, but it allows finer control over the amount of time to
 * wait for a notification before giving up. The amount of real time,
 * measured in nanoseconds, is given by:
 */
void wait(long timeout, int nanos) throws InterruptedException

/**
 * In other words, this method behaves exactly as if it simply
 * performs the call {@code wait(0)}.
 */ 
public final void wait() throws InterruptedException {
    wait(0);
}

API java.lang.Object {@notify} {@notifyAll}:

/**
 * Wakes up a single thread that is waiting on this object's
 * monitor. If any threads are waiting on this object, one of them
 * is chosen to be awakened. The choice is arbitrary and occurs at
 * the discretion of the implementation. A thread waits on an object's
 * monitor by calling one of the {@code wait} methods.
 * <p>
 * The awakened thread will not be able to proceed until the current
 * thread relinquishes the lock on this object. The awakened thread will
 * compete in the usual manner with any other threads that might be
 * actively competing to synchronize on this object; for example, the
 * awakened thread enjoys no reliable privilege or disadvantage in being
 * the next thread to lock this object.
 * <p>
 * This method should only be called by a thread that is the owner
 * of this object's monitor. A thread becomes the owner of the
 * object's monitor in one of three ways:
 * <ul>
 * <li>By executing a synchronized instance method of that object.
 * <li>By executing the body of a {@code synchronized} statement
 *     that synchronizes on the object.
 * <li>For objects of type {@code Class,} by executing a
 *     synchronized static method of that class.
 * </ul>
 * <p>
 * Only one thread at a time can own an object's monitor.
 *
 * @throws  IllegalMonitorStateException  if the current thread is not
 *               the owner of this object's monitor.
 * @see        java.lang.Object#notifyAll()
 * @see        java.lang.Object#wait()
 */
public final native void notify();

/**
 * Wakes up all threads that are waiting on this object's monitor. A
 * thread waits on an object's monitor by calling one of the
 * {@code wait} methods.
 * <p>
 * The awakened threads will not be able to proceed until the current
 * thread relinquishes the lock on this object. The awakened threads
 * will compete in the usual manner with any other threads that might
 * be actively competing to synchronize on this object; for example,
 * the awakened threads enjoy no reliable privilege or disadvantage in
 * being the next thread to lock this object.
 * <p>
 * This method should only be called by a thread that is the owner
 * of this object's monitor. See the {@code notify} method for a
 * description of the ways in which a thread can become the owner of
 * a monitor.
 *
 * @throws  IllegalMonitorStateException  if the current thread is not
 *               the owner of this object's monitor.
 * @see        java.lang.Object#notify()
 * @see        java.lang.Object#wait()
 */
public final native void notifyAll();

—歪一下楼—

join与start调用顺序问题

刚刚看到join在start之后调用的作用了,假如 join在start前调用,会出现什么后果呢?


--- 将上面代码  join在start 顺序进行调换成下面的样子

    thread1.join();
    System.out.println("-------");
    thread2.join();

    thread1.start();
    thread2.start();
    

--- 输出如下:

-------
main thread finished.
thread2 finished.
thread1 finished.

Process finished with exit code 0

所以得出以下结论,join在start之后调用才有意义。这个很容易理解,Thread类中的join方法的主要作用就是同步,一个线程都没有start,那也就无法同步的。


—我想了解更多的分界线:Java并发编程辅助工具类—

  • CountDownLatch
  • CyclicBarrier
  • Semaphore
  • ConcurrentHashMap
  • BlockingQueue
一、CountDownLatch

在JDK 1.5之后是并发包中(java.util.concurrent)提供的CountDownLatch也可以实现join的功能,且比join功能更多。
举个栗子:
假设上面的对每个Sheet操作分为两个阶段,第一个阶段是解析,第二个阶段是统计。假设main线程在所有线程解析完成之后就可以继续执行,而不必等到所有线程统计结束之后执行。此时join是实现不了的(It is recommended that applications not use {@code wait}, {@code notify}, or {@code notifyAll} on {@code Thread} instances.),那么可以使用CountDownLatch来处理上述需求了。

private static final CountDownLatch latch = new CountDownLatch(2);
public static void main(String[] args) throws InterruptedException {

    Thread thread1 = new Thread(new Runnable() {
        @Override
        public void run() {
            try {
                Thread.sleep(5000);
            } catch (InterruptedException e) {
                e.printStackTrace();
            }
            System.out.println("thread1 finished Analysis.");

            latch.countDown();

            try {
                Thread.sleep(5000);
            } catch (InterruptedException e) {
                e.printStackTrace();
            }
            System.out.println("thread1 finished Statistics.");
        }
    });

    Thread thread2 = new Thread(new Runnable() {
        @Override
        public void run() {
            try {
                Thread.sleep(4000);
            } catch (InterruptedException e) {
                e.printStackTrace();
            }
            System.out.println("thread2 finished Analysis.");

            latch.countDown();

            try {
                Thread.sleep(4000);
            } catch (InterruptedException e) {
                e.printStackTrace();
            }
            System.out.println("thread2 finished Statistics.");
        }
    });

    thread1.start();
    thread2.start();

    latch.await();

    System.out.println("main thread finished.");
}


--- 输出如下:
thread2 finished Analysis.
thread1 finished Analysis.
main thread finished.
thread2 finished Statistics.
thread1 finished Statistics.

Process finished with exit code 0

CountDownLatch的构造函数接收一个int类型的参数作为计数器,如果需要设置N个线程执行结束,这里就设置为N。
当我们调用CountDownLatch的countDown方法时,N就会减1,CountDownLatch的await方法会阻塞当前线程,直到N减到0。由于countDown可以用在任何地方,所以这里说的N个点可以是指N个线程,也可以指一个线程的N个步骤。当用在多线程的时候,只需要把这个CountDownLatch的引用传递到线程里即可。

API java.util.concurrent {#CountDownLatch}

/**
 * Causes the current thread to wait until the latch has counted down to
 * zero, unless the thread is {@linkplain Thread#interrupt interrupted}.
 *
 * <p>If the current count is zero then this method returns immediately.
 *
 * <p>If the current count is greater than zero then the current
 * thread becomes disabled for thread scheduling purposes and lies
 * dormant until one of two things happen:
 * <ul>
 * <li>The count reaches zero due to invocations of the
 * {@link #countDown} method; or
 * <li>Some other thread {@linkplain Thread#interrupt interrupts}
 * the current thread.
 * </ul>
 *
 * <p>If the current thread:
 * <ul>
 * <li>has its interrupted status set on entry to this method; or
 * <li>is {@linkplain Thread#interrupt interrupted} while waiting,
 * </ul>
 * then {@link InterruptedException} is thrown and the current thread's
 * interrupted status is cleared.
 *
 * @throws InterruptedException if the current thread is interrupted
 *         while waiting
 */
void await() throws InterruptedException

/**
 * Causes the current thread to wait until the latch has counted down to
 * zero, unless the thread is {@linkplain Thread#interrupt interrupted},
 * or the specified waiting time elapses.
 *
 * <p>If the current count is zero then this method returns immediately
 * with the value {@code true}.
 *
 * <p>If the current count is greater than zero then the current
 * thread becomes disabled for thread scheduling purposes and lies
 * dormant until one of three things happen:
 * <ul>
 * <li>The count reaches zero due to invocations of the
 * {@link #countDown} method; or
 * <li>Some other thread {@linkplain Thread#interrupt interrupts}
 * the current thread; or
 * <li>The specified waiting time elapses.
 * </ul>
 *
 * <p>If the count reaches zero then the method returns with the
 * value {@code true}.
 *
 * <p>If the current thread:
 * <ul>
 * <li>has its interrupted status set on entry to this method; or
 * <li>is {@linkplain Thread#interrupt interrupted} while waiting,
 * </ul>
 * then {@link InterruptedException} is thrown and the current thread's
 * interrupted status is cleared.
 *
 * <p>If the specified waiting time elapses then the value {@code false}
 * is returned.  If the time is less than or equal to zero, the method
 * will not wait at all.
 *
 * @param timeout the maximum time to wait
 * @param unit the time unit of the {@code timeout} argument
 * @return {@code true} if the count reached zero and {@code false}
 *         if the waiting time elapsed before the count reached zero
 * @throws InterruptedException if the current thread is interrupted
 *         while waiting
 */
boolean await(long timeout, TimeUnit unit) throws InterruptedException

/**
 * Decrements the count of the latch, releasing all waiting threads if
 * the count reaches zero.
 *
 * <p>If the current count is greater than zero then it is decremented.
 * If the new count is zero then all waiting threads are re-enabled for
 * thread scheduling purposes.
 *
 * <p>If the current count equals zero then nothing happens.
 */
void countDown()

NOTE:

计数器必须大于0(If the current count is zero then this method returns immediately.)

如果是0立刻返回不会阻塞当前线程。如果小于0,会报IllegalArgumentException异常。另外,CountDownLatch不可以被重新初始化或者修改CountDownLatch对象的内部计数器的,CountDownLatch是一次性的,计数器的值只能在构造方法中初始化一次,之后没有任何机制再次对其设置值,当CountDownLatch使用完毕后,它不能再次被使用。

二、CyclicBarrier

CyclicBarrier字面意思就是可循环(Cyclic)的屏障(Barrier)。它要做的事情就是让一组线程达到一个屏障(或者称为同步点)时被阻塞,直到最后一个线程也达到屏障时,屏障才会开门,所有被屏障拦截的线程才会继续执行。

--- 举个例子:

static int N = 2;
private static CyclicBarrier barrier = new CyclicBarrier(N);

public static void main(String[] args) {

    new Thread(new Runnable() {
        @Override
        public void run() {
            try {
                Thread.sleep(5000);
            } catch (InterruptedException e) {
                e.printStackTrace();
            }
            System.out.println(Thread.currentThread() + " finished Analysis.");

            try {
                barrier.await();
            } catch (InterruptedException | BrokenBarrierException e) {
                e.printStackTrace();
            }

            try {
                Thread.sleep(5000);
            } catch (InterruptedException e) {
                e.printStackTrace();
            }
            System.out.println(Thread.currentThread() + " finished Statistics.");

        }
    }).start();

    try {
        barrier.await();
    } catch (InterruptedException | BrokenBarrierException e) {
        e.printStackTrace();
    }

    System.out.println("main thread finished.");
}

--- 输出如下:
Thread[Thread-0,5,main] finished Analysis.
main thread finished.
Thread[Thread-0,5,main] finished Statistics.

Process finished with exit code 0

因为设置的CyclicBarrier参数parties值为2,从输出可以看到主线程并没有继续执行,而是到了一个屏障等待。直到子线程中也到达屏障之后,此时已经达到设置的总屏障值,屏障解除,主线程和子线程均继续执行。
如果把上述的new CyclicBarrier(2) 修改为 new CyclicBarrier(3),那么子线程和主线程就会永远等待,因为没有第三个线程执行await方法,即没有第三个线程到达屏障,所以之前到达屏障的两个线程将会被一致阻塞。
CyclicBarrier还提供了更高级的构造函数,CyclicBarrier(int parties, Runnable barrierAction),用于达到屏障时优先执行barrierAction,以便处理更复杂的场景。

--- 举个例子:
static int N = 2;
private static CyclicBarrier barrier = new CyclicBarrier(N, new BarrierActionThread());

public static void main(String[] args) {

    new Thread(new Runnable() {
        @Override
        public void run() {
            try {
                Thread.sleep(5000);
            } catch (InterruptedException e) {
                e.printStackTrace();
            }
            System.out.println(Thread.currentThread() + " finished Analysis.");

            try {
                barrier.await();
            } catch (InterruptedException | BrokenBarrierException e) {
                e.printStackTrace();
            }

            try {
                Thread.sleep(5000);
            } catch (InterruptedException e) {
                e.printStackTrace();
            }
            System.out.println(Thread.currentThread() + " finished Statistics.");

        }
    }).start();

    try {
        barrier.await();
    } catch (InterruptedException | BrokenBarrierException e) {
        e.printStackTrace();
    }

    System.out.println("main thread finished.");
}

static class BarrierActionThread implements Runnable {

    @Override
    public void run() {
        try {
            Thread.sleep(5000);
        } catch (InterruptedException e) {
            e.printStackTrace();
        }
        System.out.println(Thread.currentThread() + " aggregation finished.");
    }
}
--- 输出如下:
Thread[Thread-0,5,main] finished Analysis.
Thread[Thread-0,5,main] aggregation finished.
main thread finished.
Thread[Thread-0,5,main] finished Statistics.

Process finished with exit code 0

从输出可以看出,上述示例在所有线程到达屏障之后主线程和子线程均在BarrierActionThread线程执行结束之后才开始恢复执行。

API java.util.concurrent {#CyclicBarrier}

/**
 * Creates a new {@code CyclicBarrier} 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
 */
CyclicBarrier(int parties)


/**
 * Creates a new {@code CyclicBarrier} 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
 */
CyclicBarrier(int parties, Runnable barrierAction)

如果看源码,可以看到CyclicBarrier(int parties)实现为调用this(parties, null),即构造一个barrierAction为null的CyclicBarrier,且从CyclicBarrier(int parties, Runnable barrierAction)实现中可以看到参数parties不可小于等于0,否则会报IllegalArgumentException异常。

CyclicBarrier应用场景

举个栗子:
假设上面的对每个Sheet操作分为两个阶段,第一个阶段是解析、统计,第二阶段是将各个统计进行聚合。那么可以使用CyclicBarrier来处理上述需求了。

static int N = 4;
private static CyclicBarrier barrier = new CyclicBarrier(N, new BarrierActionThread());

public static void main(String[] args) {

    for (int i = 0; i < N; i++) {
        new Thread(new Runnable() {
            @Override
            public void run() {
                try {
                    Thread.sleep(5000);
                } catch (InterruptedException e) {
                    e.printStackTrace();
                }
                System.out.println(Thread.currentThread() + " finished Analysis、Statistics.");

                try {
                    barrier.await();
                } catch (InterruptedException | BrokenBarrierException e) {
                    e.printStackTrace();
                }
                System.out.println(Thread.currentThread() + " finished.");
            }
        }).start();
    }
}

static class BarrierActionThread implements Runnable {

    @Override
    public void run() {
        try {
            Thread.sleep(5000);
        } catch (InterruptedException e) {
            e.printStackTrace();
        }
        System.out.println(Thread.currentThread() + " aggregation finished.");
    }
}

---输出如下:
Thread[Thread-2,5,main] finished Analysis、Statistics.
Thread[Thread-0,5,main] finished Analysis、Statistics.
Thread[Thread-1,5,main] finished Analysis、Statistics.
Thread[Thread-3,5,main] finished Analysis、Statistics.
Thread[Thread-3,5,main] aggregation finished.
Thread[Thread-3,5,main] finished.
Thread[Thread-1,5,main] finished.
Thread[Thread-0,5,main] finished.
Thread[Thread-2,5,main] finished.

Process finished with exit code 0

从结果可以看出,当所有线程都到达barrier状态后,会从存活的线程中选择一个线程去执行Runnable。
另外:CyclicBarrier是可以被重用的,CyclicBarrier的计数器是可以通过reset()方法重置。reset之后CyclicBarrier将被重置到初始状态,如有有其他的线程等待,那么将会报BrokenBarrierException结束等待。重置之后是可以根据需求重新执行一次。

API java.util.concurrent {#CyclicBarrier @reset}

/**
 * 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 <em>after</em>
 * 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.
 */
void reset()

关于reset使用方式,举个例子:

static int N = 4;
private static CyclicBarrier barrier = new CyclicBarrier(N, new BarrierActionThread());

public static void main(String[] args) throws InterruptedException {

    cyclicBarrierTest();
    try {
        barrier.await();
    } catch (InterruptedException | BrokenBarrierException e) {
        e.printStackTrace();
    }

    //休眠一下,重置计数器的值
    Thread.sleep(10 * 1000);
    barrier.reset();
    System.out.println("--------------------reset-------------------");

    cyclicBarrierTest();
    try {
        barrier.await();
    } catch (InterruptedException | BrokenBarrierException e) {
        e.printStackTrace();
    }
    System.out.println(Thread.currentThread() + " finished.");
}

private static void cyclicBarrierTest() {
    for (int i = 0; i < N - 1; i++) {
        new Thread(new Runnable() {
            @Override
            public void run() {
                try {
                    Thread.sleep(5000);
                } catch (InterruptedException e) {
                    e.printStackTrace();
                }
                System.out.println(Thread.currentThread() + " finished Analysis、Statistics.");

                try {
                    barrier.await();
                } catch (InterruptedException | BrokenBarrierException e) {
                    e.printStackTrace();
                }
                System.out.println(Thread.currentThread() + " finished.");
            }
        }).start();
    }
}

static class BarrierActionThread implements Runnable {

    @Override
    public void run() {
        try {
            Thread.sleep(5000);
        } catch (InterruptedException e) {
            e.printStackTrace();
        }
        System.out.println(Thread.currentThread() + " aggregation finished.");
    }
}

---输出如下:
Thread[Thread-0,5,main] finished Analysis、Statistics.
Thread[Thread-1,5,main] finished Analysis、Statistics.
Thread[Thread-2,5,main] finished Analysis、Statistics.
Thread[Thread-2,5,main] aggregation finished.
Thread[Thread-2,5,main] finished.
Thread[Thread-1,5,main] finished.
Thread[Thread-0,5,main] finished.
--------------------reset-------------------
Thread[Thread-4,5,main] finished Analysis、Statistics.
Thread[Thread-3,5,main] finished Analysis、Statistics.
Thread[Thread-5,5,main] finished Analysis、Statistics.
Thread[Thread-5,5,main] aggregation finished.
Thread[Thread-5,5,main] finished.
Thread[Thread-4,5,main] finished.
Thread[Thread-3,5,main] finished.
Thread[main,5,main] finished.

Process finished with exit code 0

从输出中是可以看出reset之前开启三个线程,等所有线程达到等待之后,对其进行reset重置计数器, reset之后同样的开启三个线程,等新线程都到达等待之后主线程结束。
如果按照上述逻辑将CyclicBarrier改造为CountDownLatch会发现第二次await将不会生效,此时可以理解为CountDownLatch的count为0。

CountDownLatch和CyclicBarrier的区别
  1. CountDownLatch的计数器只能使用一次,而CyclicBarrier的计数器可以通过reset进行重置,CyclicBarrier更为灵活、可以用于比较复杂的业务场景,比如异常之后重置计数器,让线程重新再执行一次。
  2. CyclicBarrier还提供了更多有用的方法。如:getNumberWaiting可以获得当前阻塞的线程数量; isBroken可以了解注册的线程是否中断。
三、Semaphore

Semaphore(信号量)用来控制同时访问特定资源的线程数量,通过协调各个线程,以保证合理的使用公共资源。
Semaphore可以用于流量控制,特别是公共资源有限的场景,比如,数据库连接。
假如,有一个需求,启动30个线程解析Excel,解析之后直接保存到数据库,但是数据库的连接只有10个,那么我们就要控制同时只有10个线程保存数据。此场景就可以用Semaphore实现。

private static int MAX_THREAD_NUM = 30;
private static int MAX_DB_CONNECT_NUM = 10;

private static Semaphore semaphore = new Semaphore(MAX_DB_CONNECT_NUM);
private static ExecutorService threadPool = Executors.newFixedThreadPool(MAX_THREAD_NUM);

public static void main(String[] args) {

    for (int i = 0; i < MAX_THREAD_NUM; i++) {
        threadPool.execute(new Runnable() {
            @Override
            public void run() {
                try {
                    semaphore.acquire();
                    Thread.sleep(5000);
                    System.out.println(Thread.currentThread() + " Analysis Sheet、Save Data");
                } catch (InterruptedException e) {
                    e.printStackTrace();
                }  finally {
                    semaphore.release();
                }
            }
        });
    }
    threadPool.shutdown();
}

执行上面代码,能看到输出为10个线程为一组进行输出。在代码中虽然有30个线程并发执行,但是信号量只有10个资源,最多只运行10个线程并发执行。
Semaphore(int permits)接受一个整形的数字,表示可用的许可证数量。上述的Semaphore(10)表示运行10个线程获取许可证,也就是最大并发数为10。Semaphore的用法很简单,首先使用Semaphore的acquire()获取许可证,使用完后,再使用release()归还许可证。另外还支持tryAcquire尝试获取许可证。

另外一种应用场景为:在多线程处理的时候我们为了限制某特定资源不被耗尽(比如内存),我们可以使用Semaphore加以约束。示例:

private static int MAX_THREAD_NUM = 3;
private static int MAX_PERMIT_NUM = 13;
private static Semaphore semaphore = new Semaphore(MAX_PERMIT_NUM);
private static ExecutorService threadPool = Executors.newFixedThreadPool(MAX_THREAD_NUM);

public static void main(String[] args) throws InterruptedException {
    for (int i = 0; i < 15000000; i++) {
        semaphore.acquire();
        threadPool.execute(new Runnable() {
            @Override
            public void run() {
                try {
                    Thread.sleep(5000);
                    System.out.println(Thread.currentThread() + " Analysis Sheet、Save Data");
                } catch (InterruptedException e) {
                    e.printStackTrace();
                } finally {
                    semaphore.release();
                }
            }
        });
    }
    threadPool.shutdown();
}

从上述示例代码可以看到在线程池拥有一个无界(或者说有界最大是Integer.MAX)的队列时,为了使抑制线程对内存的无限使用,防止过度膨胀,我们可以使用Semaphore配合线程池保证我们的运行的数据加阻塞队列的数据达到设定的最大值后维持恒定不再增长,控制内存不被耗尽,且又能受益于使用多线程带来的效率提升。

API java.util.concurrent {#Semaphore}

/**
 * Acquires a permit from this semaphore, blocking until one is
 * available, or the thread is {@linkplain Thread#interrupt interrupted}.
 */
void acquire() throws InterruptedException
/**
 * Acquires the given number of permits from this semaphore,
 * blocking until all are available,
 * or the thread is {@linkplain Thread#interrupt interrupted}.
 */
void acquire(int permits)

/**
 * Acquires a permit from this semaphore, only if one is available at the
 * time of invocation.
 */
boolean tryAcquire()

/**
 * Acquires the given number of permits from this semaphore, only
 * if all are available at the time of invocation.
 */
boolean tryAcquire(int permits)

/**
 * Acquires a permit from this semaphore, if one becomes available
 * within the given waiting time and the current thread has not
 * been {@linkplain Thread#interrupt interrupted}.
 */
boolean tryAcquire(long timeout, TimeUnit unit) throws InterruptedException

/**
 * Acquires the given number of permits from this semaphore, if all
 * become available within the given waiting time and the current
 * thread has not been {@linkplain Thread#interrupt interrupted}.
 */
boolean tryAcquire(int permits, long timeout, TimeUnit unit) throws InterruptedException

/**
 * Releases a permit, returning it to the semaphore.
 */
void release()

/**
 * Releases the given number of permits, returning them to the semaphore.
 */
void release(int permits)

/**
 * Returns the current number of permits available in this semaphore.
 *
 * <p>This method is typically used for debugging and testing purposes.
 *
 * @return the number of permits available in this semaphore
 */
int availablePermits()

/**
 * Returns an estimate of the number of threads waiting to acquire.
 * The value is only an estimate because the number of threads may
 * change dynamically while this method traverses internal data
 * structures.  This method is designed for use in monitoring of the
 * system state, not for synchronization control.
 *
 * @return the estimated number of threads waiting for this lock
 */
int getQueueLength()

/**
 * Queries whether any threads are waiting to acquire. Note that
 * because cancellations may occur at any time, a {@code true}
 * return does not guarantee that any other thread will ever
 * acquire.  This method is designed primarily for use in
 * monitoring of the system state.
 *
 * @return {@code true} if there may be other threads waiting to
 *         acquire the lock
 */
boolean hasQueuedThreads()

参考:

  1. 《Java并发编程的艺术》
  2. http://www.importnew.com/21889.html
  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值