Java 并发工具类使用

java.util.concurrent 包从 JDK1.5 开始引入,目的是解决并发编程的线程安全问题,提供非常有用的并发工具类,包括 CountDownLatch、CyclicBarrier 与 Semaphore 等。

在 concurrent 包下还有两个子包,一个是 atomic,包含一些原子类,可以解决原子性问题;另一个包是 locks,提供了并发包里线程安全的最为基础的工具——显示锁(ReentrantLock、ReadWriteLock)。

CountDownLatch使用
CountDownLatch 允许一个或多个线程等待其他线程完成操作,利用它可以实现类似计数器的功能。比如现在有一个线程,需要等待其它线程执行完之后才能执行,此时就可以使用 CountDownLatch 实现这种需求。

当然,也可以通过 join() 方法来实现,不过 CountDownLatch 实现的更为灵活、更为方便。

CountDownLatch 的主要方法如下:

public CountDownLatch(int count):构造器,指定 count 值
public void await():调用 await() 方法的线程会被挂起,直到 count 值为0继续执行
public boolean await(long timeout, TimeUnit unit):调用 await() 方法的线程会被挂起,直到 count 值为0或指定时间后继续执行
public void countDown():将计数值 count 减1
public long getCount():返回 count 值

public class CountDownLatchDemo {
    private static CountDownLatch latch = new CountDownLatch(2);

    public static void main(String[] args) throws InterruptedException {
        new Thread(() -> {
            try {
                System.out.println(Thread.currentThread().getName() + "正在执行");
                Thread.sleep(1000);
                System.out.println(Thread.currentThread().getName() + "执行完毕");
                latch.countDown();
            } catch (InterruptedException e) {
                e.printStackTrace();
            }
        }).start();

        new Thread(() -> {
            try {
                System.out.println(Thread.currentThread().getName() + "正在执行");
                Thread.sleep(3000);
                System.out.println(Thread.currentThread().getName() + "执行完毕");
                latch.countDown();
            } catch (InterruptedException e) {
                e.printStackTrace();
            }
        }).start();

        System.out.println("等待2个子线程执行完毕...");
        latch.await();
        System.out.println("2个子线程执行完毕");
        System.out.println("count值:" + latch.getCount());
        System.out.println("继续执行主线程");
    }
}

输出结果:

等待2个子线程执行完毕...
Thread-0正在执行
Thread-1正在执行
Thread-0执行完毕
Thread-1执行完毕
2个子线程执行完毕
count值:0
继续执行主线程

CountDownLatch 并没有提供设置 count 值的方法,只能在初始化时设置。当 count 值变为0时,如果还有第二次等待,还需要创建一个新的 CountDownLatch。

CyclicBarrier用法
CyclicBarrier 的字面意思是可循环使用(Cyclic)的屏障(Barrier),或者说回环栅栏。通过它可以实现让一组线程到达一个屏障(同步点)时被阻塞,直到最后一个线程到达屏障时,所有被屏障拦截的线程才会继续执行。每个线程调用 await() 方法告诉 CyclicBarrier 我已经到达了屏障,然后当前线程被阻塞。

CyclicBarrier 主要方法如下:

public CyclicBarrier(int parties):构造器,参数为屏障拦截的线程数量
public CyclicBarrier(int parties, Runnable barrierAction):构造器,参数为屏障拦截的线程数量、这些线程都到达同步点时会执行的内容
public int await():调用 await() 方法的线程会被挂起,直至所有线程都到达同步点
public int await(long timeout, TimeUnit unit):调用 await() 方法的线程会被挂起,直至所有线程都到达同步点或到达指定的时间
public int getParties():返回设置的 parties 值
public void reset():重置为初始状态

注意:

如果有线程已经处于等待状态,调用 reset() 方法会导致已经在等待的线程抛出异常,并且将会导致其它线程始终处于阻塞状态。
如果在等待的过程中,线程被中断(Interrupt),会抛出异常,并且会影响到其他所有的线程。
如果超出指定的等待时间,当前线程会抛出 TimeoutException 异常,其他线程会抛出 BrokenBarrierException 异常。
如果在执行屏障操作过程(barrierAction)中发生异常,会影响到其他所有的线程,抛出 BrokenBarrierException 异常,屏障被损坏。

示例如下:

public class CyclicBarrierDemo {
    public static void main(String[] args) {
        CyclicBarrier cyclicBarrier = new CyclicBarrier(3, () ->
                System.out.println(Thread.currentThread().getName() + ":所有线程写入完毕"));

        for (int i = 0; i < cyclicBarrier.getParties(); i++)
            new Thread(() -> {
                System.out.println(Thread.currentThread().getName() + "正在写入数据...");
                try {
                    Thread.sleep(3000);
                    System.out.println(Thread.currentThread().getName() + "写入数据完毕");
                    cyclicBarrier.await();
                } catch (InterruptedException | BrokenBarrierException e) {
                    e.printStackTrace();
                }
            }).start();
    }
}

输出结果:

Thread-0正在写入数据...
Thread-1正在写入数据...
Thread-2正在写入数据...
Thread-0写入数据完毕
Thread-1写入数据完毕
Thread-2写入数据完毕
Thread-2:所有线程写入完毕

CountDownLatch 与 CyclicBarrier 的含义稍微有一点区别。

对于前者,重点是一个或多个线程等待,而其它的线程在完成“某件事情”之后,可以继续执行;对于后者,重点是在任意一个线程没有完成“某件事情”之前,所有的线程都必须等待。

除此之外,相比 CountDownLatch,CyclicBarrier 是可以重用的。

Semaphore用法
Semaphore,即计数信号量,通常用来限制可以同时访问某些资源的线程数量。

举一个例子来说,假如银行有4个服务窗口,现在有7个人同时想要办理业务,怎么办呢?只能是先进去4个人,另外3个人在大厅等候。只有前面的一个人办完业务,服务窗口有空闲,另外一个人才能进去办业务。这里服务窗口就相当于资源,4个人相当于被限制的线程数量。

Semaphore 的主要方法如下:

public Semaphore(int permits):构造器,指定允许同时访问的许可数量,不遵循FIFO原则
public Semaphore(int permits, boolean fair):构造器,指定允许同时访问的线程数量;如果fair为true,表示遵循FIFO原则
public void acquire() throws InterruptedException:获得访问资源的权利(许可)
public void release():释放访问资源的权利(许可)
public int availablePermits():返回剩余可获得的许可数量

示例如下:
 

public class SemaphoreDemo {

    private static Semaphore semaphore = new Semaphore(4);

    public static void main(String[] args) throws InterruptedException {
        for (int i = 0; i < 7; i++) {
            int order = i;
            new Thread(() -> {
                try {
                    semaphore.acquire();
                    System.out.println(order + "号线程接入,目前剩余" + semaphore.availablePermits());
                    TimeUnit.SECONDS.sleep((long) (Math.random() * 10) + 1);
                } catch (InterruptedException e) {
                    e.printStackTrace();
                }
                semaphore.release();
                System.out.println(order + "号线程释放");
            }).start();
        }
    }
}

输出结果:

0号线程接入,目前剩余3
2号线程接入,目前剩余2
1号线程接入,目前剩余1
3号线程接入,目前剩余0
2号线程释放
4号线程接入,目前剩余0
1号线程释放
5号线程接入,目前剩余0
3号线程释放
6号线程接入,目前剩余0
0号线程释放
4号线程释放
6号线程释放
5号线程释放

注:因为多线程执行顺序不确定,因此多次执行的结果可能并不一致。

  • 7
    点赞
  • 17
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值