4. 多线程 工具类

343 阅读3分钟

CountDownLatch

概念

  • 等待多线程完成。CountDownLatch允许一个或多个线程等待其他线程完成操作。
  • 在java.util.cucurrent包下,基于AQS实现。
  • 是通过一个计数器来实现的,计数器的初始值是线程的数量。每当一个线程执行完毕后,计数器的值就-1,当计数器的值为0时,表示所有线程都执行完毕,然后在闭锁上等待的线程就可以恢复工作了。

举栗子

  • 要解析一个excel里的多个sheet页,一个sheet页一个线程的处理,要等所有sheet都解析完成后,才能提示解析完成。
CountDownLatch latch = new CountDownLatch(2);
new Thread(() -> {
        System.out.println("t1启动");
        latch.countDown();
}, "t1").start();
new Thread(() -> {
        System.out.println("t2启动");
        latch.countDown();
}, "t2").start();
latch.await();
System.out.println("结束");
// out:t1启动 t2启动 结束

构造方法

//参数count为计数值
public CountDownLatch(int count) {  };  

方法

//调用await()方法的线程会被挂起,它会等待直到count值为0才继续执行
public void await() throws InterruptedException { };   
//和await()类似,只不过等待一定的时间后count值还没变为0的话就会继续执行
public boolean await(long timeout, TimeUnit unit) throws InterruptedException { };  
//将count值减1
public void countDown() { };  

CyclicBarrier 同步屏障

现实生活中我们经常会遇到这样的情景,在进行某个活动前需要等待人全部都齐了才开始。例如吃饭时要等全家人都上座了才动筷子,旅游时要等全部人都到齐了才出发,比赛时要等运动员都上场后才开始。

CyclicBarrier字面意思是“可重复使用的栅栏”,CyclicBarrier 相比 CountDownLatch 来说,要简单很多,其源码没有什么高深的地方,它是 ReentrantLock 和 Condition 的组合使用。

image.png

构造方法

public CyclicBarrier(int parties)
public CyclicBarrier(int parties, Runnable barrierAction)
// parties 是参与线程的个数
// 第二个构造方法有一个 Runnable 参数,这个参数的意思是最后一个到达线程要做的任务

方法

public int await() 
public int await(long timeout, TimeUnit unit) 

栗子

20人满人才发车

CyclicBarrier barrier = new CyclicBarrier(20, () -> System.out.println("满人"));
for(int i=0; i<100; i++) {
    new Thread(()->{
        try {
            barrier.await();
        } catch (InterruptedException e) {
            e.printStackTrace();
        } catch (BrokenBarrierException e) {
            e.printStackTrace();
        }
    }).start();
}

Phaser

phase 是阶段的意思。Phaser是按照不同阶段执行线程的,如果程序中遇到分好几个阶段执行,并且有的阶段需要多个线程共同参与的情况下可能会用到Phase。

自定义一个MarriagePhaser 继承 Phaser 重写Phaser的onAdvance方法定义了4个阶段(进入下一个阶段时该方法被自动调用)。 阶段必须从0开始。onAdvice的两个参数 phase是第几个阶段,registeredParties是目前有几个已注册线程参加。 最后返回值为false表示流程未结束,继续执行下一阶段,返回true表示流程结束。 模拟结婚:

static Random r = new Random();
static MarriagePhaser phaser = new MarriagePhaser();

static void milliSleep(int milli) {
    try {
        TimeUnit.MILLISECONDS.sleep(milli);
    } catch (InterruptedException e) {
        e.printStackTrace();
    }
}

public static void main(String[] args) {
    phaser.bulkRegister(7);
    for(int i=0; i<5; i++) {
        new Thread(new Person("p" + i)).start();
    }
    new Thread(new Person("新郎")).start();
    new Thread(new Person("新娘")).start();
}
// 复写Phaser的onAdvance方法
static class MarriagePhaser extends Phaser {
    @Override
    protected boolean onAdvance(int phase, int registeredParties) {
        switch (phase) {
            case 0:
                System.out.println("所有人到齐了!" + registeredParties);
                System.out.println();
                return false;
            case 1:
                System.out.println("所有人吃完了!" + registeredParties);
                System.out.println();
                return false;
            case 2:
                System.out.println("所有人离开了!" + registeredParties);
                System.out.println();
                return false;
            case 3:
                System.out.println("婚礼结束!新郎新娘抱抱!" + registeredParties);
                return true;
            default:
                return true;
        }
    }
}


static class Person implements Runnable {
    String name;
    public Person(String name) {
        this.name = name;
    }
    public void arrive() {
        milliSleep(r.nextInt(1000));
        System.out.printf("%s 到达现场!\n", name);
        //当前线程已经到达屏障,在此等待,等条件满足后继续向下一个屏障继续执行
        phaser.arriveAndAwaitAdvance();
    }
    public void eat() {
        milliSleep(r.nextInt(1000));
        System.out.printf("%s 吃完!\n", name);
        phaser.arriveAndAwaitAdvance();
    }
    public void leave() {
        milliSleep(r.nextInt(1000));
        System.out.printf("%s 离开!\n", name);
        phaser.arriveAndAwaitAdvance();
    }

    private void hug() {
        if(name.equals("新郎") || name.equals("新娘")) {
            milliSleep(r.nextInt(1000));
            System.out.printf("%s 洞房!\n", name);
            phaser.arriveAndAwaitAdvance();
        } else {
            // 当前线程退出,并且是parties值减1
            phaser.arriveAndDeregister();
        }
    }

    @Override
    public void run() {
        arrive();
        eat();
        leave();
        hug();
    }
}

Semaphore 控制并发线程数

Semaphore也是一个线程同步的辅助类,可以维护当前访问自身的线程个数,并提供了同步机制。使用Semaphore可以控制同时访问资源的线程个数,例如,实现一个文件允许的并发访问数。

主要方法

  • void acquire():从此信号量获取一个许可,在提供一个许可前一直将线程阻塞,否则线程被中断。
  • void release():释放一个许可,将其返回给信号量。
  • int availablePermits():返回此信号量中当前可用的许可数。
  • boolean hasQueuedThreads():查询是否有线程正在等待获取。
Semaphore s = new Semaphore(1, true);

new Thread(()->{
    try {
        s.acquire();
        System.out.println("T1 running...");
        Thread.sleep(200);
        System.out.println("T1 running...");
    } catch (InterruptedException e) {
        e.printStackTrace();
    } finally {
        s.release();
    }
}).start();

new Thread(()->{
    try {
        s.acquire();
        System.out.println("T2 running...");
        Thread.sleep(200);
        System.out.println("T2 running...");
        s.release();
    } catch (InterruptedException e) {
        e.printStackTrace();
    }
}).start();
}

Exchanger

Exchanger 是 JDK 1.5 开始提供的一个用于两个工作线程之间交换数据的封装工具类,简单说就是一个线程在完成一定的事务后想与另一个线程交换数据,则第一个先拿出数据的线程会一直等待第二个线程,直到第二个线程拿着数据到来时才能彼此交换对应数据。

方法

Exchanger 泛型类型,其中 V 表示可交换的数据类型

  • V exchange(V v):等待另一个线程到达此交换点(除非当前线程被中断),然后将给定的对象传送给该线程,并接收该线程的对象。
  • V exchange(V v, long timeout, TimeUnit unit):等待另一个线程到达此交换点(除非当前线程被中断或超出了指定的等待时间),然后将给定的对象传送给该线程,并接收该线程的对象

栗子

static Exchanger<String> exchanger = new Exchanger<>();
new Thread(()->{
    String s = "T1";
    try {
        s = exchanger.exchange(s);
    } catch (InterruptedException e) {
        e.printStackTrace();
    }
    System.out.println(Thread.currentThread().getName() + " " + s);
}, "t1").start();

new Thread(()->{
    String s = "T2";
    try {
        s = exchanger.exchange(s);
    } catch (InterruptedException e) {
        e.printStackTrace();
    }
    System.out.println(Thread.currentThread().getName() + " " + s);
}, "t2").start();