Java多线程技术之六(JUC之线程控制工具)

一、信号量 Semaphore

信号量用于控制并发访问某个共享资源的线程数量,常用于限流。

使用时将信号量初始化为最大许可数量,信号量通过该值控制线程并发访问的数量。线程访问共享资源前先调用acquire()申请访问许可,如果申请成功,则该线程可以访问共享资源。如果申请失败,则该线程暂时等待。线程释放共享资源时需要调用release()返还访问许可。

常用方法

// 构造函数,permits表示最大许可数量
public Semaphore(int permits)

// 构造函数,fair表示是否公平,即等待时间越久的线程优先获取许可
public Semaphore(int permits, boolean fair)

// 申请一个许可,如果没有许可,线程将等待
public void acquire()

// 申请permits个许可,如果没有许可,线程将等待
public void acquire(int permits)

// 返还一个许可
public void release()

// 返还permits个许可
public void release(int permits)

// 尝试申请一个许可,如果没有许可则返回false
public boolean tryAcquire()  

使用示例

public class SemaphoreDemo {
    // 允许最高5个线程并发
    private static final int MAX_AVAILABLE = 5;  
    private static Semaphore semaphore = new Semaphore(MAX_AVAILABLE);  

    public static void main(String[] args) {
        ExecutorService pool = Executors.newFixedThreadPool(5*MAX_AVAILABLE);
        for (int i = 0; i < 10*MAX_AVAILABLE; i++) {
            pool.execute(new Runnable() {
                public void run() {
                    try {
                        // 申请一个许可
                        semaphore.acquire();
                        System.out.println("第" + i + "个线程申请到共享资源");
                        Thread.sleep(3000);
                        System.out.println("第" + i + "个线程返还了共享资源");
                        // 返还一个许可
                        semaphore.release();   
                    } catch (InterruptedException e) {
                        e.printStackTrace();
                    }
                }
            });
        }
        pool.shutdown();
    }

}

二、倒计数器 CountDownLatch

俗称闭锁,它允许一个或多个线程一直等待,直到其他线程都执行完后再执行。

它是通过一个计数器来实现的,计数器的初始值是线程的数量。每当一个线程执行完毕后,计数器的值就减1,当计数器的值为0时,表示所有线程都执行完毕,然后等待在倒计数器上的线程就可以恢复执行了。

常用方法

// 实例化一个倒计数器,count指定计数个数
public CountDownLatch(int count)

//调用await()方法的线程会被挂起,它会等待直到count值为0才继续执行
public void await()

//将count值减1
public void countDown()

使用示例

public class CountDownLatchDemo {

    public static void main(String[] args) {
        System.out.println("主线程开始执行");
        final CountDownLatch latch = new CountDownLatch(50);
        ExecutorService pool = Executors.newCachedThreadPool();
        for (int i = 0; i < 50; i++) {
            pool.execute(new Runnable() {
                @Override
                public void run() {
                    try {
                        System.out.println("子线程:"+Thread.currentThread().getName()+"执行");
                        Thread.sleep(3000);
                    } catch (InterruptedException e) {
                        e.printStackTrace();
                    }
                    // 子线程执行完毕,计数器减一
                    latch.countDown();
                }
            });
        }        
        pool.shutdown();
        System.out.println("主线程等待50个子线程执行完毕");
        try {
            // 主线程等待在latch上
            latch.await();
            System.out.println("50个子线程都执行完毕,主线程恢复执行");
        } catch (InterruptedException e) {
            e.printStackTrace();
        }        
    }
}

三、循环栅栏 CyclicBarrier

循环栅栏可以使一组线程到达一个同步点处等待,直到所有线程都到达同步点后,栅栏打开,所有线程恢复执行,同时栅栏将被重置。

常用方法

// 构造函数,parties表示让多少个线程等待在同步点。
public CyclicBarrier(int parties)

// 构造函数,barrierAction表示所有线程到达同步点后先执行barrierAction,再打开栅栏。
public CyclicBarrier(int parties, Runnable barrierAction)

// 线程调用await()表示自己已经到达同步点。
public int await()

// 带超时时间的await
public int await(long timeout, TimeUnit unit)

使用示例

class Writer extends Thread {
    private CyclicBarrier cyclicBarrier;

    public Writer(CyclicBarrier cyclicBarrier) {
        this.cyclicBarrier = cyclicBarrier;
    }

    @Override
    public void run() {
        try {
            System.out.println(Thread.currentThread().getName()+"正在写入数据...");
            Thread.sleep(3000);    
            System.out.println(Thread.currentThread().getName()+"写入数据完毕,等待其他线程");
            // 同步点,等待其他线程到达
            cyclicBarrier.await();
        } catch (InterruptedException e) {
            e.printStackTrace();
        }catch(BrokenBarrierException e){
            e.printStackTrace();
        }
    }
}

public class CyclicBarrierDemo {

    public static void main(String[] args) {
        CyclicBarrier cyclicBarrier = new CyclicBarrier(5, new Runnable() {
            @Override
            public void run() {
                System.out.println("所有写操作执行完毕,下面继续执行读操作...");
                System.out.println(Thread.currentThread().getName()+"正在读数据...");
            }
        });
        System.out.println("所有线程开始执行写操作...");
        for(int i = 0;i < 5; i++) new Writer(cyclicBarrier ).start();

        try {
            Thread.sleep(5000);
        } catch (InterruptedException e) {
            e.printStackTrace();
        }

        System.out.println("cyclicBarrier重用...");
        System.out.println("所有线程开始执行写操作...");
        for(int i = 0;i < 5; i++) new Writer(cyclicBarrier ).start();
    }
}

四、阶段协同器 Phaser

阶段协同器用于控制多个线程分阶段协作完成任务。

Phaser把任务划分为多个阶段,每个阶段可以有任意个线程参与,可以在运行时随时注册或注销线程到某个阶段。Phaser支持层次化结构,子Phaser可以作为一个参与者加入到父Phaser中,当子Phaser中没有线程参与时,将自动从父Phaser中注销。Phaser内部维护了一个计数器,用来控制阶段需要等待的线程数量。调用register()方法或arriveAndDeregister()方法可以动态的增减计数器的值。线程完成阶段任务后需要调用arrive系列方法告知Phaser自己已到达(阶段终点)。当一个阶段中所有注册的线程都到达后,Phaser的OnAdvance()方法将被回调。然后Phaser释放等待线程进入下个阶段。如此循环,直到结束。Phaser内部维护了一个阶段号,初始值为0。每当所有注册的线程都到达一个阶段时,阶段号加1。

常用方法

// 构造函数,初始化阶段号为0
public Phaser()

// 构造函数,初始化阶段号为0,parties指定计数器的值
public Phaser(int parties)

// 构造函数,效果同Phaser(parent, 0)
public Phaser(Phaser parent)

// 构造函数,parent指定父Phaser,parties指定计数器的值
public Phaser(Phaser parent, int parties)

// 注册一个线程到本阶段及后面的阶段(计数器加一),返回当前阶段号
public int register()

// 注册parties个线程到本阶段及后面的阶段(计数器加parties),返回当前阶段号
public int bulkRegister(int parties)

// 告知到达,然后不等待当前阶段下其他线程,直接向下一个阶段前进。返回当前阶段号,如果Phaser已经终止,则返回负数
public int arrive()

// 告知到达,然后等待当前阶段下其他线程到达。返回到达阶段号,如果Phaser已经终止,则返回负数
public int arriveAndAwaitAdvance()

// 告知到达,然后从本阶段起注销一个线程(计数器减一),然后不等待当前阶段下其他线程,直接向下一个阶段前进。返回当前阶段号。
public int arriveAndDeregister()

// 在指定阶段等待
public int awaitAdvance(int phase)
                                    
// 阶段执行完成后回调此方法,可以覆盖此方法来定义阶段到达动作,phase表示阶段号,此方法返回true将终结Phaser对象
protected boolean onAdvance(int phase, int registeredParties)
    
// 获取当前阶段号
public final int getPhase()

// 强制终止,此后Phaser对象将不可用,register等将不再有效
public void forceTermination()

// 判断是否终止
public boolean isTerminated()

使用示例

class MyPhaser extends Phaser {
 
    @Override
    // 每个阶段完成后回调此方法
    protected boolean onAdvance(int phase, int registeredParties) {         
        switch (phase) {
        case 0:
            System.out.println("阶段0结束了");
            return false;
        case 1:
            System.out.println("阶段1结束了");
            return false;
        case 2:
            System.out.println("阶段2结束了");
            return false;
        case 3:
            System.out.println("阶段3结束了");
            return false;
        default:
            System.out.println("整体结束了");
            return true;
        }       
    }   
}

class Task extends Thread {
    private Phaser phaser;

    Task(Phaser phaser) {
        this.phaser = phaser;
    }

    @Override
    public void run() {
        try {
            System.out.println(Thread.currentThread().getName() + "阶段0的任务开始了");
            Thread.sleep(3000);
            System.out.println(Thread.currentThread().getName() + "阶段0的任务完成了");
            // 到达并在此等待其他线程到达
            phaser.arriveAndAwaitAdvance();

            System.out.println(Thread.currentThread().getName() + "阶段1的任务开始了");
            Thread.sleep(3000);
            System.out.println(Thread.currentThread().getName() + "阶段1的任务完成了");
            // 到达并在此等待其他线程到达
            phaser.arriveAndAwaitAdvance();

            System.out.println(Thread.currentThread().getName() + "阶段2的任务开始了");
            Thread.sleep(3000);
            System.out.println(Thread.currentThread().getName() + "阶段2的任务完成了");
            // 到达并在此等待其他线程到达
            phaser.arriveAndAwaitAdvance();

            System.out.println(Thread.currentThread().getName() + "阶段3的任务开始了");
            Thread.sleep(3000);
            System.out.println(Thread.currentThread().getName() + "阶段3的任务完成了");
            // 到达并在此等待其他线程到达
            phaser.arriveAndAwaitAdvance();
        } catch (InterruptedException e) {
            e.printStackTrace();
        }
    }
}

public class PhaserDemo {

    public static void main(String[] args) {
        Phaser phaser = new MyPhaser(3);
        for (int i = 1; i <= 3; i++) {
            new Task(phaser).start();
        }
    }

}

五、交换器 Exchanger

交换器用于两个线程之间交换信息。当在一个线程中调用Exchanger的exchange方法后,会等待对方线程调用Exchanger的exchange方法,对方线程调用exchange方法后,双方线程交换参数,双方线程继续执行。

常用方法

// 构造方法
public Exchanger()

// 调用此方法后,当前线程会等待对方线程调用exchange方法,对方线程调用exchange方法后,双方线程交换参数,双方线程继续执行。
public V exchange(V x)

// exchange(V x)的超时重载,如果在timeout时间内对方线程没有调用exchange方法,则会抛出TimeoutException。
public V exchange(V x, long timeout, TimeUnit unit)

使用示例

class ExchangerThread extends Thread {
    private Exchanger exchanger;
    private String name;
 
    public ExchangerThread(Exchanger exchanger, String name) {
        this.exchanger = exchanger;
        this.name = name;
        setName(name);
    }
 
    public void run() {
        try {
            System.out.println(Thread.currentThread().getName() + ": " + exchanger.exchange(name));
        } catch (InterruptedException e) {
            e.printStackTrace();
        }
    }
}
 
public class ExchangerDemo {

    public static void main(String[] args) {
        Exchanger exchanger = new Exchanger();
        Thread xxx = new ExchangerThread(exchanger, "xxx");
        Thread yyy = new ExchangerThread(exchanger, "yyy");
        xxx.start();
        yyy.start();
    }
}

你可能感兴趣的:(Java多线程技术之六(JUC之线程控制工具))