多线程篇(工具类(CountDownLatch & CyclicBarrier & Semaphore & Exchanger & Phaser等))(持续更新迭代)

目录

前言

一、等待多线程完成的CountDownLatch

1. 简介

2. 注意

二、同步屏障CyclicBarrier

1. 简介

2. 与countdownlatch的区别

3. 核心参数

4. 构造方法

5. 核心方法

5.1. await方法

6. 应用

7. 推荐文章

三、控制并发线程数的Semaphore

1. 简介

2. 主要方法

3. 示例

四、线程间交换数据的Exchanger

1. 简介

2. 示例

3. 源码分析

(1)字段

(2)内部类

(3)构造方法

(4)方法exchange

(5)基本算法

(6)方法slotExchange

(7)方法arenaExchange

五、阶段器 Phaser

1. 什么是Phaser?

2. Phaser的常用方法

register

bulkRegister

getRegisteredParties

arriveAndAwaitAdvance

arriveAndDeregister

arrive

awaitAdvance

awaitAdvanceInterruptibly

getArrivedParties

getUnarrivedParties

getPhase

isTerminated

forceTermination

3. 应用案例

3.1. 使用Phaser动态注册parties

3.2. 使用Phaser设置多个阶段

3.3. 常用方法演示

3.4. 利用arrive只监听线程完成第一部分任务

3.5. awaitAdvance演示

五、本章总结


前言

在JDK的并发包里提供了几个非常有用的并发工具类。

CountDownLatch、CyclicBarrier 和 Semaphore工具类提供了一种并发流程控制的手段,

Exchanger工具类则提供了在线程间交换数 据的一种手段。

本章会配合一些应用场景来介绍如何使用这些工具类。

一、等待多线程完成的CountDownLatch

1. 简介

CountDownLatch允许一个或多个线程等待其他线程完成操作

假如有这样一个需求:我们需要解析一个Excel里多个sheet数据,此时可以考虑使用多线程,每个线程解析一

个sheet里的数据,等到所有的sheet都解析完之后,程序需要提示解析完成。在这个需求中,要实现主线程等

待所有线程完成sheet的解析操作,最简单的做法时使用join()方法,代码如下所示:

public class JoinCountDownLatchTest {
    public static void main(String[] args) throws InterruptedException {
        Thread parser1 = new Thread(new Runnable() {
            @Override
            public void run() {
            }
        });
        Thread parser2 = new Thread(new Runnable() {
            @Override
            public void run() {
                System.out.println("parser2 finish");
            }
        });
        parser1.start();
        parser2.start();
        parser1.join();// join用于让当前执行线程等待join线程执行结束。其实现原理是不停检查join线程是否存活,如果join线程存活则让当前线程永远等待。
        parser2.join();
        System.out.println("all parser finish");
    }
}

join用于让当前执行线程等待join线程执行结束。

其实现原理是不停检查join线程是否存活,如果join线程存活则让当前线程永远等待。

其中,wait(0)表示永远等待下去,代码片段如下。

        while (isAlive()) {
            wait(0); // 永远等待下去
        }

直到join线程中止后,线程的this.notifyAll()方法会被调用,调用notifyAll()方法是在JVM里实现的,所

以在JDK里看不到,大家可以查看JVM源码。 在JDK 1.5之后的并发包中提供的CountDownLatch也可以

实现join的功能,并且比join的功能更多,代码如下:

public class CountDownLatchTest {
	// CountDownLatch的构造函数接收一个int类型的参数作为计数器,如果你想等待N个点完成,这里就传n
    static CountDownLatch c = new CountDownLatch(2);
    public static void main(String[] args) throws InterruptedException {
        new Thread(new Runnable() {
            @Override
            public void run() {
                System.out.println(1);
                c.countDown(); // 调用此方法n就会减1  
                System.out.println(2);
                c.countDown();// 由于countDown()可以用到任何地方,所以这里说N个点,可以是n个线程,也可以是1个线程里的n个步骤。用在多个线程时,只需要把这个CountDownLatch的引用传递到线程里即可
            }
        }).start();
        c.await(); // 会阻塞当前线程,直到n变成0.
        System.out.println("3");
    }
}

CountDownLatch的构造函数接收一个int类型的参数作为计数器,如果你想等待N个点完 成,这里

就传入N。

当我们调用CountDownLatch的countDown方法时,N就会减1,CountDownLatch的await方法

会阻塞当前线程,直到N变成零。由于countDown方法可以用在任何地方,所以这里说的N个 点,可以

是N个线程,也可以是1个线程里的N个执行步骤。用在多个线程时,只需要把这个CountDownLatch的

引用传递到线程里即可。

如果有某个解析sheet的线程处理得比较慢,我们不可能让主线程一直等待,所以可以使 用另外一个带指

定时间的await方法——await(long time,TimeUnit unit),这个方法等待特定时 间后,就会不再阻塞当前

线程。join也有类似的方法。

2. 注意

计数器必须大于等于0,只是等于0时候,计数器就是零,调用await方法时不会阻塞当前线程。

CountDownLatch不可能重新初始化或者修改CountDownLatch对象的内部计数 器的值。

一个线程调用countDown方法happen-before,另外一个线程调用await方法。

二、同步屏障CyclicBarrier

1. 简介

CyclicBarrier的字面意思是可循环使用(Cyclic)的屏障(Barrier)。它要做的事情是,让一组线程到

达一个屏障(也可以叫同步点)时被阻塞,直到最后一个线程到达屏障时,屏障才会开门,所有被屏障

拦截的线程才会继续运行。下图演示了这一过程。

2. 与countdownlatch的区别

它的主要作用其实和CountDownLanch差不多,都是让一组线程到达一个屏障时被阻塞,直到最后一个

线程到达屏障时,屏障会被打开,所有被屏障阻塞的线程才会继续执行,不过它是可以循环执行的,这

是它与CountDownLanch最大的不同。

3. 核心参数

 /** The lock for guarding barrier entry */
    // 可重入锁
    private final ReentrantLock lock = new ReentrantLock();
    /** Condition to wait on until tripped */
    // 条件队列,具体看AQS
    private final Condition trip = lock.newCondition();
    /** The number of parties */
    // 参与的线程数量
    private final int parties;
    /* The command to run when tripped */
    // 由最后一个进入 barrier 的线程执行的操作
    private final Runnable barrierCommand;
    /** The current generation */
    // 当前代
    private Generation generation = new Generation();
    // 正在等待进入屏障的线程数量
    private int count;

4. 构造方法

public CyclicBarrier(int parties, Runnable barrierAction) {
        // 参与的线程数量小于等于0,抛出异常
        if (parties <= 0) throw new IllegalArgumentException();
        // 设置parties
        this.parties = parties;
        // 设置count
        this.count = parties;
        // 设置barrierCommand
        this.barrierCommand = barrierAction;
    }

该构造函数可以指定关联该CyclicBarrier的线程数量,并且可以指定在所有线程都进入屏障后的执行动

作,该执行动作由最后一个进行屏障的线程执行

5. 核心方法

5.1. await方法

(1)dowait方法

await中调用dowait

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) {  // 正在等待进入屏障的线程数量为0,所有线程都已经进入
                // 运行的动作标识
                boolean ranAction = false;
                try {
                    // 保存运行动作
                    final Runnable command = barrierCommand;
                    if (command != null) // 动作不为空
                        // 运行
                        command.run();
                    // 设置ranAction状态
                    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) // 设置了等待时间,并且等待时间大于0
                        // 等待指定时长
                        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) { // 设置了等待时间,并且等待时间小于0
                    // 损坏屏障
                    breakBarrier();
                    // 抛出异常
                    throw new TimeoutException();
                }
            }
        } finally {
            // 释放锁
            lock.unlock();
        }
    }

(2)核心流程

(3)nextGeneration方法

该方法会唤醒所有线程并且重置次数,这也是为什么CyclicBarrier可以循环调用的原因

private void nextGeneration() {
        // signal completion of last generation
        // 唤醒所有线程
        trip.signalAll();
        // set up next generation
        // 恢复正在等待进入屏障的线程数量
        count = parties;
        // 新生一代
        generation = new Generation();
    }

6. 应用

 
    static class MyThread extends Thread {
        private CyclicBarrier cb;
        public MyThread(String name, CyclicBarrier cb) {
            super(name);
            this.cb = cb;
        }
        public void run() {
            System.out.println(Thread.currentThread().getName() + " going to await");
            try {
                cb.await();
                System.out.println(Thread.currentThread().getName() + " continue");
            } catch (Exception e) {
                e.printStackTrace();
            }
        }
    }
    public static void main(String[] args) throws InterruptedException, BrokenBarrierException {
        CyclicBarrier cb = new CyclicBarrier(3, new Thread("barrierAction") {
            public void run() {
                System.out.println(Thread.currentThread().getName() + " barrier action");
            }
        });
        MyThread t1 = new MyThread("t1", cb);
        MyThread t2 = new MyThread("t2", cb);
        t1.start();
        t2.start();
        System.out.println(Thread.currentThread().getName() + " going to await");
        cb.await();
        System.out.println(Thread.currentThread().getName() + " continue");
    }

运行结果:

所以调用时序为:

到此这篇关于Java并发编程中的CyclicBarrier线程屏障详解的文章就介绍到这了,更多相关CyclicBarrier

线程屏障内容请搜索脚本之家以前的文章或继续浏览下面的相关文章希望大家以后多多支持脚本之家!

7. 推荐文章

您可能感兴趣的文章:

三、控制并发线程数的Semaphore

Semaphore是Java并发库中的一个类,用于控制对共享资源的并发访问。它通过计数器管理许可,允许多个线

程同时访问。当许可用完时,其他线程将被阻塞,直到有线程释放许可。Semaphore可以实现类似锁的功能,

也可用于创建有限大小的数据结构,例如有限长度的链表。示例代码展示了如何在多线程环境中使用

Semaphore限制并发线程数量。

1. 简介

Semaphore(信号量)是用来控制同时访问特定资源的线程数量,

通过协调各个线程以保证合理地使用公共资源。

Semaphore通过使用计数器来控制对共享资源的访问。 如果计数器大于0,则允许访问。 如果为0,则拒绝访

问。 计数器所计数的是允许访问共享资源的许可。 因此,要访问资源,必须从信号量中授予线程许可。

Semaphore 当前在多线程环境下被扩放使用,操作系统的信号量是个很重要的概念,在进程控制方面都有应

用。Java 并发库 的Semaphore 可以很轻松完成信号量控制,Semaphore可以控制某个资源可被同时访问的

个数,通过 acquire() 获取一个许可,如果没有就等待,而 release() 释放一个许可。比如在Windows下可以

设置共享文件的最大客户端访问个数。

Semaphore 实现的功能就类似厕所有5个坑,假如有10个人要上厕所,那么同时只能有多少个人去上厕所呢?

同时只能有5个人能够占用,当5个人中 的任何一个人让开后,其中等待的另外5个人中又有一个人可以占用

了。另外等待的5个人中可以是随机获得优先机会,也可以是按照先来后到的顺序获得机会,这取决于构造

Semaphore对象时传入的参数选项。单个信号量的Semaphore对象可以实现互斥锁的功能,并且可以是由一

个线程获得了“锁”,再由另一个线程释放“锁”,这可应用于死锁恢复的一些场合。

Semaphore 维护了当前访问的个数,提供同步机制,控制同时访问的个数。在数据结构中链表可以保存“无

限”的节点,用Semaphore可以实现有限大小的链表。另外重入锁 ReentrantLock 也可以实现该功能,但实

现上要复杂些。

2. 主要方法

void acquire() :从信号量获取一个许可,如果无可用许可前将一直阻塞等待,

void acquire(int permits) :获取指定数目的许可,如果无可用许可前也将会一直阻塞等待

boolean tryAcquire():从信号量尝试获取一个许可,如果无可用许可,直接返回false,不会阻塞

boolean tryAcquire(int permits): 尝试获取指定数目的许可,如果无可用许可直接返回false

boolean tryAcquire(int permits, long timeout, TimeUnit unit):

在指定的时间内尝试从信号量中获取许可,如果在指定的时间内获取成功,返回true,否则返回false

void release():释放一个许可,别忘了在finally中使用,注意:多次调用该方法,会使信号量的许可数增加,

达到动态扩展的效果,如:初始permits为1,调用了两次release,最大许可会改变为2

int availablePermits(): 获取当前信号量可用的许可

Semaphore构造函数

public Semaphore(int permits) {
sync = new NonfairSync(permits);
}

public Semaphore(int permits, boolean fair) {
    sync = fair ? new FairSync(permits) : new NonfairSync(permits);
}

permits: 初始许可数,也就是最大访问线程数

fair: 当设置为false时,创建的信号量为非公平锁;当设置为true时,信号量是公平锁

3. 示例

import java.util.concurrent.ExecutorService;
import java.util.concurrent.Executors;
import java.util.concurrent.Semaphore;

public class SemaphoreTest1 {
    private static final int SEM_MAX = 10;
    public static void main(String[] args) {
        Semaphore sem = new Semaphore(SEM_MAX);
        //创建线程池
        ExecutorService threadPool = Executors.newFixedThreadPool(3);
        //在线程池中执行任务
        threadPool.execute(new MyThread(sem, 5));
        threadPool.execute(new MyThread(sem, 4));
        threadPool.execute(new MyThread(sem, 7));
        //关闭池
        threadPool.shutdown();
    }
}

class MyThread extends Thread {
    private volatile Semaphore sem;    // 信号量
    private int count;        // 申请信号量的大小

    MyThread(Semaphore sem, int count) {
        this.sem = sem;
        this.count = count;
    }

    public void run() {
        try {
            // 从信号量中获取count个许可
            sem.acquire(count);

            Thread.sleep(2000);
            System.out.println(Thread.currentThread().getName() + " acquire count="+count);
        } catch (InterruptedException e) {
            e.printStackTrace();
        } finally {
            // 释放给定数目的许可,将其返回到信号量。
            sem.release(count);
            System.out.println(Thread.currentThread().getName() + " release " + count + "");
        }
    }
}

运行结果:

pool-1-thread-1 acquire count=5
pool-1-thread-2 acquire count=4
pool-1-thread-1 release 5
pool-1-thread-2 release 4
pool-1-thread-3 acquire count=7
pool-1-thread-3 release 7

结果说明:信号量sem的许可总数是10个;共3个线程,分别需要获取的信号量许可数是5,4,7。

前面两个线程获取到信号量的许可后,sem中剩余的可用的许可数是1;因此,最后一个线程必须等前两

个线程释放了它们所持有的信号量许可之后,才能获取到7个信号量许可。

四、线程间交换数据的Exchanger

1. 简介

Exchanger(交换者)是一个用于线程间协作的工具类, 它可以在配对线程中配对并交换数据。每个线程都可以

在exchange入口方法上携带数据,然后与相应的线程进行匹配,并在返回时接收配对线程的数据。它提供一

个同步点,在这个同步点,两个线程可以交换彼此的数据。这两个线程通过exchange方法交换数据,如果第

一个线程先执行exchange()方法,它会一直等待第二个线程也执行exchange方法,当两个线程都到达同步点

时,这两个线程就可以交换数据,将本线程生产出来的数据传递给对方。交换器可能在遗传算法和管道设计等

应用场景中有很大用处。 (基于JDK1.8)

2. 示例

下面是一个交换缓冲区的示例,FillingLoop向一个缓冲区中填充数据,若缓冲区已满就将当前的缓冲

区交换出去获取一个新的未满的缓冲区,EmptyingLoop向一个缓冲区中移除数据,若缓冲区已经空了,

就将此缓冲区交换出去获取一个新未空缓冲区。


class FillAndEmpty {
    Exchanger<DataBuffer> exchanger = new Exchanger<DataBuffer>();
    DataBuffer initialEmptyBuffer = ... a made-up type
    DataBuffer initialFullBuffer = ...

    class FillingLoop implements Runnable {
        public void run() {
            DataBuffer currentBuffer = initialEmptyBuffer;
            try {
                while (currentBuffer != null) {
                    addToBuffer(currentBuffer);
                    if (currentBuffer.isFull())
                        currentBuffer = exchanger.exchange(currentBuffer);
                }
            } catch (InterruptedException ex) { ... handle ... }
        }
    }

    class EmptyingLoop implements Runnable {
        public void run() {
            DataBuffer currentBuffer = initialFullBuffer;
            try {
                while (currentBuffer != null) {
                    takeFromBuffer(currentBuffer);
                    if (currentBuffer.isEmpty())
                        currentBuffer = exchanger.exchange(currentBuffer);
                }
            } catch (InterruptedException ex) { ... handle ...}
        }
    }

    void start() {
        new Thread(new FillingLoop()).start();
        new Thread(new EmptyingLoop()).start();
    }
}

Exchanger也可以用于校对工作 ,比如我们需要将纸制银行流水通过人工的方式录入成电子银行流水,

为了避免错误,采用AB岗两人进行录入,录入到Excel之后,系统需要加载这两个Excel,并对两个Excel

数据进行校对,看看是否录入一致.

class ExchangerTest {
    private static final Exchanger<String> exgr = new Exchanger<String>();
    private static ExecutorService threadPool = Executors.newFixedThreadPool(2);

    public static void main(String[] args) {
        threadPool.execute(() -> {
            try {
                String A = "银行流水A";// A录入银行流水数据
                exgr.exchange(A);
            } catch (InterruptedException e) {
            }
        });
        threadPool.execute(() -> {
            try {
                String B = "银行流水B";// B录入银行流水数据
                String A = exgr.exchange(B);
                System.out.println("A和B数据是否一致:" + A.equals(B) + ",A录入的是:"
                        + A + ",B录入是:" + B);
            } catch (InterruptedException e) {
            }
        });
        threadPool.shutdown();
    }
}

3. 源码分析

(1)字段

	//node数组,存储每个线程 待交换数据 的相关信息
    private volatile Node[] arena;
	// 封装两个线程时的数据交换信息
    private volatile Node slot;
	//高24位是版本号,低8位是arena数组的最大有效索引(元素为null不计数为有效索引)
    private volatile int bound;	

	//arena每个元素之间的间隔位移255   (1<<7)   
	private static final int ASHIFT = 7;
    //数组arena的最大容量
    private static final int MMASK = 0xff;
		//bound的版本号 ,bound每次修改一次,其二进制的倒数第9位加1  
    private static final int SEQ = MMASK + 1;//二进制 0b1 0000 0000

		//cpu个数,用来确定自旋次数
    private static final int NCPU = Runtime.getRuntime().availableProcessors();

	//arena的最大下标,表示所有线程无竞争或arena的最大可用的下标
    static final int FULL = (NCPU >= (MMASK << 1)) ? MMASK : NCPU >>> 1;

	//自旋次数
    private static final int SPINS = 1 << 10;

	//交换null数据时返回的对象。
    private static final Object NULL_ITEM = new Object();
		//交换数据超时返回的对象。
    private static final Object TIMED_OUT = new Object();
	//线程本地变量,继承于ThreadLocal
    private final Participant participant;

bound:bound的高24位相当是版本号,每添加或删除一个node元素,bound的高24位都将加1;而bound

的低8位表示arena数组的最大序数索引(p.index的最大值),添加一个Node元素,低8位加1,删除一个Node

元素,低8位减1.

为了防止伪共享,arena中的元素不是连接分布的,每个元素间隔(1<<ASHIFT)个下标长度,假设一个

node元素的有效索引为i,那么(i<<ASHIFT)才是实际的数组下标。

(2)内部类

@sun.misc.Contended static final class Node {
    //在arena数组中的下标
    int index;              // Arena index
    //上次记录的bound
    int bound;              // Last recorded value of Exchanger.bound
    //CAS失败的次数
    int collides;           // Number of CAS failures at current bound
    //计数自旋次数会用到的随机码
    int hash;               // Pseudo-random for spins
    //当前线程需要交换的item.
    Object item;            // This thread's current item
    //匹配线程传过来的item。
    volatile Object match;  // Item provided by releasing thread
    //当前休眠的线程,在被唤醒后设为null
    volatile Thread parked; // Set to this thread when parked, else null
}
//线程本地变量,记录每个线程对应的node
/** The corresponding thread local class */
static final class Participant extends ThreadLocal<Node> {
    public Node initialValue() { return new Node(); }
}

(3)构造方法

构造方法只是简单地将participant实例化了

public Exchanger() {
    participant = new Participant();
}

(4)方法exchange

Exchanger只有两个重载的公共方法exchange(V )exchange(V , long, TimeUnit ),前者不限定阻塞

时长,而后者要限定阻塞时长(超时版本).

public V exchange(V x) throws InterruptedException {
    Object v;
    Object item = (x == null) ? NULL_ITEM : x; // translate null args
    if ((arena != null ||
         (v = slotExchange(item, false, 0L)) == null) &&
        ((Thread.interrupted() || // disambiguates null return
          (v = arenaExchange(item, false, 0L)) == null)))
        throw new InterruptedException();
    return (v == NULL_ITEM) ? null : (V)v;
}

这两个重载的方法逻辑基本一致,内部核心实现都是调用slotExchangearenaExchange

这里以exchange(V , long, TimeUnit )为例作简单说明。

public V exchange(V x, long timeout, TimeUnit unit)
    throws InterruptedException, TimeoutException {
    Object v;
    Object item = (x == null) ? NULL_ITEM : x;//x为空,将其转为NULL_ITEM。
    long ns = unit.toNanos(timeout);
    if ((arena != null ||
         //arena为空,未被初始化,执行slotExchange
         (v = slotExchange(item, true, ns)) == null) &&
        ((Thread.interrupted() || //若线程中断就会抛出InterruptedException异常
          (v = arenaExchange(item, true, ns)) == null))) //slotExchange返回null或arean不为null,执行arenaExchange。
        throw new InterruptedException();
    if (v == TIMED_OUT)
        throw new TimeoutException();
    return (v == NULL_ITEM) ? null : (V)v;
}

exchange的主要逻辑是:若arena为空,就执行slotExchange,

若arena不为空,就执行slotExchange。若slotExchange返回空,还将执行slotExchange。

(5)基本算法

在分析slotExchangearenaExchange方法之前,我们要先了解exchange的算法原理,正好Exchanger

内部对其算法有些注释说明,下面是它的基本算法:

     for (;;) {
         if (slot is empty) {          // offer 第一个线程执行exchange,添加slot  阻塞等待
             //slot是空的,就将构造一个Node节点,(将待交换值item包装成一个Node)
           place item in a Node;
            
           if (can CAS slot from empty to node) { //使用CAS成功将slot设为刚构造的node结点
               //当前线程休眠等待,直到被唤醒
             wait for release;
               //被唤醒后返回node中匹配的数据slot.match
             return matching item in node;
           }
         }
         //slot不为空,   CAS成功将slot中设为null
         else if (can CAS slot from node to empty) { // release 第二个线程执行exchange,获取slot中的值。 唤醒等待线程
              //获取slot中的item,(这个item是第二个线程要返回的值)
           get the item in node;
             //将待交换的item设置到slot.match中
           set matching item in node;
             //唤醒等待的线程
           release waiting thread;
             
         }
         // else retry on CAS failure  其他条件需要CAS自旋重试
       }

其大致流程是:第一个线程执行首次exchange方法时,检测到slot为空,它就用自己的item构造一个Node节

点,将这个node节点设为slot,然后阻塞等待直到第二线程将它的item传过来。第二个线程执行exchange方

法,检测到slot不为空,它就获取到了slot中的item,这个item就是第一个线程传来的,现在第二个线程将自己

的item设到slot.match中,然后唤醒第一个线程。

第一个线程被唤醒后返回第二个线程中设置的slot.match, 第二个线程返回第一个线程中设置的slot.item.

这种数据交换在两个线程中实现起来比较简单,但如果有3个以上的线程需要数据交换就比较麻烦了,这里就

需要引入arena数组来保存多个数据项了。

(6)方法slotExchange

slotExchange方法的主要逻辑:

for循环自旋: slot不为空,设置slot的匹配数据,唤醒等待的线程,返回slot.item;slot为空,就将线程局部

变量p通过CAS初始化赋值给slot,退出for循环;若arena不为空,就返回null。

while循环:先自旋SPINS次,然后休眠当前线程,直到被配对线程唤醒。

    private final Object slotExchange(Object item, boolean timed, long ns) {
        Node p = participant.get();
        Thread t = Thread.currentThread();
        //外部方法exchange的if语句中的Thread.interrupted()能重置中断,将其设为非中断的 ,外部的exchange方法将抛出中断异常
        if (t.isInterrupted()) // preserve interrupt status so caller can recheck 
            return null;

        //第一次自旋,初始化slot和arena
        for (Node q;;) {
            //q代表当前的slot
            if ((q = slot) != null) { //slot不为空,
                //CAS将slot设空,
                //获取并返回对方线程传入的交换数据q.item
                //将当前线程待交换数据item设到匹配项q.match,
                //唤醒对方线程(对方线程在被唤醒后将返回q.match)
                if (U.compareAndSwapObject(this, SLOT, q, null)) {
                    Object v = q.item;
                    q.match = item;
                    Thread w = q.parked;
                    if (w != null)
                        U.unpark(w);
                    return v;
                }
                //CAS失败,有线程竞争,初始化arena
                // create arena on contention, but continue until slot null
                if (NCPU > 1 && bound == 0 &&
                        U.compareAndSwapInt(this, BOUND, 0, SEQ))//将bound的倒数第9设为1,bound低8位全是零
                    arena = new Node[(FULL + 2) << ASHIFT];
            }
            else if (arena != null)
                //slot为空和arena不为空,返回空,然后将执行arenaExchange方法
                return null; // caller must reroute to arenaExchange
            else { //slot和arean均为空,第一个线程首次执行exchange方法,就是这种情况
                //将待交换的数据放在当前线程变量p中,
                p.item = item;
                //CAS设值
                if (U.compareAndSwapObject(this, SLOT, null, p))
                    break;//CAS成功了,进入下面的自旋等待
                //CAS失败,可能其他线程先于当前线程CAS设值成功,可以获取另外其他线程传的item.
                //重新将p.item设为null,为下次自旋准备
                p.item = null;
            }
        }

        // await release
        int h = p.hash;
        long end = timed ? System.nanoTime() + ns : 0L; //超时的截止时间
        int spins = (NCPU > 1) ? SPINS : 1;//CPU数目只有一个时,不进行自旋,防止浪费有限的CPU资源
        Object v;
        while ((v = p.match) == null) {//反复获取p.match,v不为空才返回
            if (spins > 0) {
                h ^= h << 1; h ^= h >>> 3; h ^= h << 10;//异或操作将,h更随机
                if (h == 0)
                    //p.hash作为成员变量,初始值为0,将其初始化为非零的值
                    h = SPINS | (int)t.getId();
                else if (h < 0 && (--spins & ((SPINS >>> 1) - 1)) == 0) //将自旋次数减1
                    //礼让其他线程
                    Thread.yield();
            }
            else if (slot != p)//slot与p不等,表明slot被其他给修改了,需要重新自旋,重新最新的值
                spins = SPINS;
           //自旋次数为0,不再自旋,准备休眠等待
            else if (!t.isInterrupted() && arena == null &&  //未中断,arena不为空
                    (!timed || (ns = end - System.nanoTime()) > 0L)) {//未超时
                U.putObject(t, BLOCKER, this); //记录当前线程阻塞的对象this
                p.parked = t;//阻塞的线程是当前线程t
                if (slot == p)
                    U.park(false, ns);//当前线程休眠
                //被唤醒后,将阻塞相关的属性重新设为null
                p.parked = null;
                U.putObject(t, BLOCKER, null);
            }
            //arena为null 或超时了或中断了(非正常状态),尝试将slot设为null(方法准备返回了)
            else if (U.compareAndSwapObject(this, SLOT, p, null)) {
                // CAS成功,退出自旋,。
                v = timed && ns <= 0L && !t.isInterrupted() ? TIMED_OUT : null;
                break;
            }
        }
        //p.match被取出了,将其设为null
        U.putOrderedObject(p, MATCH, null);
        //item也设为null
        p.item = null;
        p.hash = h;//记录算出的hash
        return v;
    }

(7)方法arenaExchange

arenaExchange与slotExchange方法很相似,只不过arenaExchange针对数组的node元素进行相应的处理而

已。arenaExchange先要确定元素的下标位置,p.index是表示p在arena中的序数i(从0开始计数,表示第i个元

素),arean中的元素不是连接分布的,每个元素间隔1<<ASHIFT个下标,所以i<<ASHIFT才是第i个元素的下

标。

    private final Object arenaExchange(Object item, boolean timed, long ns) {
        Node[] a = arena;
        Node p = participant.get();
        for (int i = p.index;;) {                      // access slot at i
            int b, m, c; long j;                       // j is raw array offset
            //取出对应的node节点,
            // (1 << ASHIFT) 是每个node的索引间隔(防止伪共享)
            Node q = (Node)U.getObjectVolatile(a, j = (i << ASHIFT) + ABASE);
            //因为要即将取走这个node中的item,所以CAS将arena中第i个有效元素设为null
            if (q != null && U.compareAndSwapObject(a, j, q, null)) {
                //CAS成功
                //获取并返回对方线程传入的交换数据q.item
                //将当前线程待交换数据item设到匹配项q.match,
                //唤醒对方线程(对方线程在被唤醒后将返回q.match)
                Object v = q.item;                     // release
                q.match = item;//设置匹配item
                Thread w = q.parked;
                if (w != null)
                    U.unpark(w);//唤醒对方线程
                return v;
            }
            // (bound) & MMAS取bound的低8位,所以m最大索引
            //q为null且索引位i小于等于最大索引
            else if (i <= (m = (b = bound) & MMASK) && q == null) {
                p.item = item;                         // offer
                //将CAS设置arena数组中对应位置的node
                if (U.compareAndSwapObject(a, j, null, p)) {
                    //超时截止时间
                    long end = (timed && m == 0) ? System.nanoTime() + ns : 0L;
                    Thread t = Thread.currentThread(); // wait
                    //自旋等待
                    for (int h = p.hash, spins = SPINS;;) {
                        //返回对方传入匹配数据p.match,并将相应的数据清空
                        Object v = p.match;
                        if (v != null) { //有匹配的数据
                            U.putOrderedObject(p, MATCH, null);//取出match后,将之清空
                            //将p.item清空,为下次做准备
                            p.item = null;             // clear for next use
                            p.hash = h;//记录hash
                            return v;
                        }
                        else if (spins > 0) { //v为空,且还有剩余自旋次数
                            //异或操作将,h更随机
                            h ^= h << 1; h ^= h >>> 3; h ^= h << 10; // xorshift
                            if (h == 0)                // initialize hash
                                //p.hash作为成员变量,初始值为0,将其初始化为非零的值
                                h = SPINS | (int)t.getId();
                            else if (h < 0 &&          // approx 50% true
                                    (--spins & ((SPINS >>> 1) - 1)) == 0)
                                Thread.yield();        // two yields per wait
                        }
                        else if (U.getObjectVolatile(a, j) != p)
                            //表示被其他线程修改了,自旋重新获取最新的值
                            spins = SPINS;       // releaser hasn't set match yet
                        //自旋次数为0,准备休眠等待
                        else if (!t.isInterrupted() && m == 0 &&
                                (!timed ||
                                        (ns = end - System.nanoTime()) > 0L)) {
                            //休眠等待,并设置相关的记录字段,唤醒后清空相应的字段
                            U.putObject(t, BLOCKER, this); // emulate LockSupport
                            p.parked = t;              // minimize window
                            if (U.getObjectVolatile(a, j) == p)
                                U.park(false, ns);
                            p.parked = null;
                            U.putObject(t, BLOCKER, null);
                        }
                        //其他情况,如中断了,m不为0,或超时了 (非正常状态)
                        else if (U.getObjectVolatile(a, j) == p &&
                                U.compareAndSwapObject(a, j, p, null)) {
                            if (m != 0)                // try to shrink
                                //bound加一个SEQ单位,”-1“表示最大有效索引减1,arena中少了一个元素
                                U.compareAndSwapInt(this, BOUND, b, b + SEQ - 1);
                            p.item = null;
                            p.hash = h;
                            //i减半
                            i = p.index >>>= 1;        // descend
                            if (Thread.interrupted())//中断
                                return null;
                            if (timed && m == 0 && ns <= 0L)//超时
                                return TIMED_OUT;
                            break;                     // expired; restart
                        }
                    }
                }
                else//CAS设置arena数组中对应位置的node 失败
                //清空p.item,将自旋重试
                    p.item = null;                     // clear offer
            }
            else {  //q不为空,i大于最大的有效索引
                if (p.bound != b) { // stale; reset 值b过期了,重新获取
                    p.bound = b;
                    p.collides = 0;//cas失败次数为0
                    i = (i != m || m == 0) ? m : m - 1;//i取最大可用的索引(准备从尾向前遍历)
                }
                else if ((c = p.collides) < m || m == FULL ||
                        //”+1“表示最大有效索引加1,arena中多了一个元素
                        !U.compareAndSwapInt(this, BOUND, b, b + SEQ + 1)) {
                    //失败次数加1
                    p.collides = c + 1;
                    //循环遍历,(反向遍历到第一个元素之后,又跳到数组的最后一个元素)
                    i = (i == 0) ? m : i - 1;          // cyclically traverse
                }
                else
                    //有效索引加1,
                    i = m + 1;                         // grow
                p.index = i;//更新index
            }
        }
    }
}

参考: 《 Java并发编程的艺术》

五、阶段器 Phaser

1. 什么是Phaser?

Phaser又称“阶段器”,用来解决控制多个线程分阶段共同完成任务的情景问题。

它与CountDownLatch和CyclicBarrier类似,都是等待一

组线程完成工作后再执行下一步,协调线程的工作。但

在CountDownLatch和CyclicBarrier中我们都不可以动态的配置parties,而Phaser可以动态注册需要协调的线

程,相比CountDownLatch和CyclicBarrier就会变得更加灵活。

2. Phaser的常用方法

register

动态添加一个parties

int register()

bulkRegister

动态添加多个parties

  • parties:需要添加的个数
int bulkRegister(int parties)

getRegisteredParties

获取当前的parties数

int getRegisteredParties()

arriveAndAwaitAdvance

到达并等待其他线程到达

int arriveAndAwaitAdvance()

arriveAndDeregister

到达并注销该parties,这个方法不会使线程阻塞

int arriveAndDeregister()

arrive

到达,但不会使线程阻塞

int arrive()

awaitAdvance

等待前行,可阻塞也可不阻塞,判断条件为传入的phase是否为当前phaser的phase。如果相等则阻塞,反之不进行阻塞

  • phase:阶段数值
int awaitAdvance(int phase)

awaitAdvanceInterruptibly

该方法与awaitAdvance类似,唯一不一样的就是它可以进行打断。

  • phase:阶段数值
  • timeout:超时时间
  • unit:时间单位
int awaitAdvanceInterruptibly(int phase)
int awaitAdvanceInterruptibly(int phase, long timeout, TimeUnit unit)

getArrivedParties

获取当前到达的parties数

int getArrivedParties()

getUnarrivedParties

获取当前未到达的parties数

int getUnarrivedParties()

getPhase

获取当前属于第几阶段,默认从0开始,最大为integer的最大值

int getPhase()

isTerminated

判断当前phaser是否关闭

boolean isTerminated()

forceTermination

强制关闭当前phaser

void forceTermination()

3. 应用案例

3.1. 使用Phaser动态注册parties

package com.test.part3.lock;

import java.util.Random;
import java.util.concurrent.Phaser;
import java.util.concurrent.TimeUnit;

public class PhaserExample {
    private static Random random = new Random(System.currentTimeMillis());
    public static void main(String[] args) {
        Phaser phaser = new Phaser();
        //创建5个任务
        for (int i=0;i<5;i++){
            new Task(phaser).start();
        }
        //动态注册
        phaser.register();
        //等待其他线程完成工作
        phaser.arriveAndAwaitAdvance();
        System.out.println("All of worker finished the task");
    }

    private static class Task extends Thread{
        private Phaser phaser;

        public Task(Phaser phaser) {
            this.phaser = phaser;
            //动态注册任务
            this.phaser.register();
        }

        @Override
        public void run() {
            try {
                System.out.println("The thread ["+getName()+"] is working");
                TimeUnit.SECONDS.sleep(random.nextInt(5));
            } catch (InterruptedException e) {
                e.printStackTrace();
            }
            System.out.println("The thread ["+getName()+"] work finished");
            //等待其他线程完成工作
            phaser.arriveAndAwaitAdvance();
        }
    }
}

运行结果:

The thread [Thread-0] is working
The thread [Thread-1] is working
The thread [Thread-2] is working
The thread [Thread-3] is working
The thread [Thread-4] is working
The thread [Thread-2] work finished
The thread [Thread-4] work finished
The thread [Thread-0] work finished
The thread [Thread-1] work finished
The thread [Thread-3] work finished
All of worker finished the task

Process finished with exit code 0

3.2. 使用Phaser设置多个阶段

这边使用的案例是运动员,模拟多个运动员参加多个项目。

package com.test.part3.lock;

import java.util.Random;
import java.util.concurrent.Phaser;
import java.util.concurrent.TimeUnit;

public class PhaserExample2 {
    private static Random random = new Random(System.currentTimeMillis());
    public static void main(String[] args) {
        //初始化5个parties
        Phaser phaser = new Phaser(5);
        for (int i=1;i<6;i++){
            new Athlete(phaser,i).start();
        }
    }
    //创建运动员类
    private static class Athlete extends Thread{
        private Phaser phaser;
        private int no;//运动员编号

        public Athlete(Phaser phaser,int no) {
            this.phaser = phaser;
            this.no = no;
        }

        @Override
        public void run() {
            try {
                System.out.println(no+": 当前处于第:"+phaser.getPhase()+"阶段");
                System.out.println(no+": start running");
                TimeUnit.SECONDS.sleep(random.nextInt(5));
                System.out.println(no+": end running");
                //等待其他运动员完成跑步
                phaser.arriveAndAwaitAdvance();

                System.out.println(no+": 当前处于第:"+phaser.getPhase()+"阶段");
                System.out.println(no+": start bicycle");
                TimeUnit.SECONDS.sleep(random.nextInt(5));
                System.out.println(no+": end bicycle");
                //等待其他运动员完成骑行
                phaser.arriveAndAwaitAdvance();

                System.out.println(no+": 当前处于第:"+phaser.getPhase()+"阶段");
                System.out.println(no+": start long jump");
                TimeUnit.SECONDS.sleep(random.nextInt(5));
                System.out.println(no+": end long jump");
                //等待其他运动员完成跳远
                phaser.arriveAndAwaitAdvance();
            } catch (InterruptedException e) {
                e.printStackTrace();
            }
        }
    }
}

运行结果:

1: 当前处于第:0阶段
3: 当前处于第:0阶段
3: start running
2: 当前处于第:0阶段
2: start running
1: start running
4: 当前处于第:0阶段
4: start running
5: 当前处于第:0阶段
5: start running
5: end running
2: end running
1: end running
4: end running
3: end running
3: 当前处于第:1阶段
5: 当前处于第:1阶段
5: start bicycle
1: 当前处于第:1阶段
1: start bicycle
2: 当前处于第:1阶段
2: start bicycle
3: start bicycle
4: 当前处于第:1阶段
4: start bicycle
1: end bicycle
3: end bicycle
4: end bicycle
5: end bicycle
2: end bicycle
3: 当前处于第:2阶段
1: 当前处于第:2阶段
1: start long jump
4: 当前处于第:2阶段
4: start long jump
5: 当前处于第:2阶段
5: start long jump
2: 当前处于第:2阶段
2: start long jump
3: start long jump
2: end long jump
4: end long jump
1: end long jump
5: end long jump
3: end long jump

Process finished with exit code 0

3.3. 常用方法演示

package com.brycen.part3.lock;

import java.util.Random;
import java.util.concurrent.Phaser;
import java.util.concurrent.TimeUnit;

public class PhaserExample3 {
    private static Random random = new Random(System.currentTimeMillis());
    public static void main(String[] args) throws InterruptedException {
        //初始化5个parties
        Phaser phaser = new Phaser(5);

        //只有当全部线程通过时才会进入下一阶段,从0开始
        System.out.println("当前阶段数:"+phaser.getPhase());

        //添加一个parties
        phaser.register();
        System.out.println("当前Parties数:"+phaser.getRegisteredParties());
        //添加多个parties
        phaser.bulkRegister(4);
        System.out.println("当前Parties数:"+phaser.getRegisteredParties());

        new Thread(new Runnable() {
            @Override
            public void run() {
                //到达并等待其他线程到达
                phaser.arriveAndAwaitAdvance();
            }
        }).start();
        new Thread(new Runnable() {
            @Override
            public void run() {
                //到达后注销该parties,不等待其他线程
                phaser.arriveAndDeregister();
                System.out.println("go on");
            }
        }).start();
        TimeUnit.MILLISECONDS.sleep(100);
        System.out.println("当前Parties数:"+phaser.getRegisteredParties());
        System.out.println("当前到达数:"+phaser.getArrivedParties());
        System.out.println("当前未达数:"+phaser.getUnarrivedParties());

        //何时会停止,只有当parties中的数量为0时或者调用forceTermination方法就会停止了,我们也可以重写phaser中的onAdvance,给他返回true就会使这个phaser停止了
        System.out.println("phaser是否结束:"+phaser.isTerminated());
        phaser.forceTermination();
        System.out.println("phaser是否结束:"+phaser.isTerminated());
    }

}

运行结果:

当前阶段数:0
当前Parties数:6
当前Parties数:10
go on
当前Parties数:9
当前到达数:1
当前未达数:8
phaser是否结束:false
phaser是否结束:true

3.4. 利用arrive只监听线程完成第一部分任务

package com.brycen.part3.lock;

import java.util.Random;
import java.util.concurrent.Phaser;
import java.util.concurrent.TimeUnit;
import java.util.stream.IntStream;

public class PhaserExample4 {
    private static Random random = new Random(System.currentTimeMillis());
    public static void main(String[] args) throws InterruptedException {
        //初始化6个parties
        Phaser phaser = new Phaser(6);
        //创建5个任务
        IntStream.rangeClosed(1,5).forEach(i->new ArrayTask(i,phaser).start());
        //等待5个任务的第一部分完成
        phaser.arriveAndAwaitAdvance();
        System.out.println("all work finished");
    }

    private static class ArrayTask extends Thread{
        private Phaser phaser;

        public ArrayTask(int name,Phaser phaser) {
            super(String.valueOf(name));
            this.phaser = phaser;
        }

        @Override
        public void run() {
            try {
                //模拟第一部分工作
                System.out.println(getName()+" start working");
                TimeUnit.SECONDS.sleep(random.nextInt(3));
                System.out.println(getName()+" end working");
                //该方法表示到达但不会使线程阻塞
                phaser.arrive();
                //模拟第二部分工作
                TimeUnit.SECONDS.sleep(random.nextInt(3));
                System.out.println(getName()+" do other thing");
            } catch (InterruptedException e) {
                e.printStackTrace();
            }
        }
    }
}

运行结果:

1 start working
2 start working
3 start working
3 end working
4 start working
5 start working
1 end working
4 end working
2 end working
2 do other thing
5 end working
3 do other thing
all work finished
5 do other thing
1 do other thing
4 do other thing

3.5. awaitAdvance演示

package com.test.part3.lock;

import java.util.Random;
import java.util.concurrent.Phaser;
import java.util.concurrent.TimeUnit;
import java.util.stream.IntStream;

public class PhaserExample5 {
    private static Random random = new Random(System.currentTimeMillis());
    public static void main(String[] args) {
        //初始化6个parties
        Phaser phaser = new Phaser(5);
        //创建5个任务
        IntStream.rangeClosed(1,5).forEach(i->new ArrayTask(i,phaser).start());
        //当phaser中的当前阶段等于传入的阶段则该方法会阻塞,反之不会
        phaser.awaitAdvance(phaser.getPhase());
        System.out.println("all work finished");
    }

    private static class ArrayTask extends Thread{
        private Phaser phaser;

        public ArrayTask(int name,Phaser phaser) {
            super(String.valueOf(name));
            this.phaser = phaser;
        }

        @Override
        public void run() {
            try {
                System.out.println(getName()+" start working");
                TimeUnit.SECONDS.sleep(random.nextInt(3));
                System.out.println(getName()+" end working");
                phaser.arriveAndAwaitAdvance();
            } catch (InterruptedException e) {
                e.printStackTrace();
            }
        }
    }
}

运行结果:

1 start working
2 start working
3 start working
4 start working
5 start working
5 end working
1 end working
2 end working
3 end working
4 end working
all work finished

五、本章总结

本章配合一些应用场景介绍JDK中提供的几个并发工具类,大家记住这个工具类的用途, 一旦有对应的业务场

景,不妨试试这些工具类。

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包

打赏作者

wclass-zhengge

你的鼓励是我创作最大的动力

¥1 ¥2 ¥4 ¥6 ¥10 ¥20
扫码支付:¥1
获取中
扫码支付

您的余额不足,请更换扫码支付或充值

打赏作者

实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

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

余额充值