Java多线程——高级同步机制

本文深入探讨了Java中用于同步的高级工具,包括信号量、倒数闸门、循环栅栏、阶段并发任务管理和线程间数据交换的实现与应用。

摘要生成于 C知道 ,由 DeepSeek-R1 满血版支持, 前往体验 >

内置锁和显式锁是两种比较基本的同步方式,除此之外,Java还提供了一些高级的同步工具:

1.  Semaphores

2. CountDownLatch

3. CyclicBarrier

4. Phaser

5. Exchanger


一、控制资源访问:Semaphores (信号量).

当一个线程试图访问共享的资源时,它首先要获取许可。如果内部的信号量计数器是大于0的话,该线程即可获得许可,然后信号量的计数器-1。但是如果获取信号量时内部计数器是0的话,该线程就会阻塞(或者返回false)。有共享的资源被释放时,许可就会被释放,计数器+1。这是由java.util.concurrent.Semaphore类实现的

例子:

class Pool {
  private static final int MAX_AVAILABLE = 100;
  private final Semaphore available = new Semaphore(MAX_AVAILABLE, true);

  public Object getItem() throws InterruptedException {
    available.acquire();
    return getNextAvailableItem();
  }

  public void putItem(Object x) {
    if (markAsUnused(x))
      available.release();
  }

  // Not a particularly efficient data structure; just for demo

  protected Object[] items = ... whatever kinds of items being managed
  protected boolean[] used = new boolean[MAX_AVAILABLE];

  protected synchronized Object getNextAvailableItem() {
    for (int i = 0; i < MAX_AVAILABLE; ++i) {
      if (!used[i]) {
         used[i] = true;
         return items[i];
      }
    }
    return null; // not reached
  }

  protected synchronized boolean markAsUnused(Object item) {
    for (int i = 0; i < MAX_AVAILABLE; ++i) {
      if (item == items[i]) {
         if (used[i]) {
           used[i] = false;
           return true;
         } else
           return false;
      }
    }
    return false;
  }

}


通过acquire (或者tryAcquire,非阻塞版本,如果获取许可失败返回false) 来获取许可,如果获取成功计数器-1,否则阻塞,方法响应中断(通过acquireUninterruptibly获取许可将不响应中断)。

通过release可以释放许可。

需要注意的是

1. acquire(and tryAcquire)和release可以一次请求/释放多个许可。当请求数量大于available的许可时会阻塞(tryAcquire返回false)。

2. Semaphore可以为公平的或非公平的,从并发性的角度考虑,默认是非公平的。


二、等待多个并发事件:CountDownLatch (倒数闸门)

CountDownLatch允许一个或者多个线程等待其他一些线程中的操作的完成。CountDownLatch维护一个计数器,并为其设定初始值,即要等待的任务。每当一个被等待的任务完成,就会使计数器-1,当计数器为0时,等待的线程将会被唤醒。

例子:

class Driver { // ...
  public static void main() throws InterruptedException {
    CountDownLatch doneSignal = new CountDownLatch(N);
    Executor e = ...

    for (int i = 0; i < N; ++i) // create and start threads
      e.execute(new WorkerRunnable(doneSignal, i));

    doneSignal.await();           // wait for all to finish
  }
}

class WorkerRunnable implements Runnable {
  private final CountDownLatch doneSignal;
  private final int i;
  WorkerRunnable(CountDownLatch doneSignal, int i) {
     this.doneSignal = doneSignal;
     this.i = i;
  }
  public void run() {
     try {
       doWork(i);
       doneSignal.countDown();
     } catch (InterruptedException ex) {} // return;
  }

  void doWork() { ... }
}
完成任务后,通过CountDown使计数器-1;通过await使主线程等待所有任务完成。


三、在某一点同步多个任务:CyclicBarrier (循环栅栏)

在某一点同步多个任务,这其实和CountDownLatch十分相似,但CyclicBarrier是可重用的。

例子:

class Solver {
  final int N;
  final float[][] data;
  final CyclicBarrier barrier;

  class Worker implements Runnable {
    int myRow;
    Worker(int row) { myRow = row; }
    public void run() {
      while (!done()) {
        processRow(myRow);

        try {
          barrier.await();
        } catch (InterruptedException ex) {
          return;
        } catch (BrokenBarrierException ex) {
          return;
        }
      }
    }
  }

  public Solver(float[][] matrix) {
    data = matrix;
    N = matrix.length;
    barrier = new CyclicBarrier(N,
                                new Runnable() {
                                  public void run() {
                                    mergeRows(...);
                                  }
                                });
    for (int i = 0; i < N; ++i)
      new Thread(new Worker(i)).start();

    waitUntilDone();
  }
}

线程中的await方法会一直阻塞,除非所有参与的线程都运行到了await方法。await响应中断,也可以抛出BrokenBarrierException。


四、运行阶段性并发任务:Phaser

也是一种可重用的栅栏,与CyclicBarrier以及CountDownLatch相似,但是更加灵活和强大。

Phaser把任务分成几个阶段,当某个阶段所有的参与者全部到达协议位置时,这个阶段就会结束,新的阶段就会开启。下面是更加具体的介绍:

1. 注册

与一般的栅栏不同的是,Phaser中注册的参与者的数量随着时间的推移是变化的。通过构造函数或者register(或者bulkRegister)方法,任务可以随时注册进来。而通过arriveAndDeregister方法又可以在任务到达时把任务从Phaser中解除。

2. 同步

a. 到达:当任务完成时调用arrive或arriveAndDeregister可以获得阶段号,并不阻塞。onArrive方法可以override,以用来定制化arrive后的操作

b. 等待:调用awaitAdvance(int phase)和awaitAdvanceInterruptibly(int phase),就可以等待目标阶段的其他参与线程,后者响应阻塞。而arriveAndAwaitAdvance等价于awaitAdvance(arrive())。如果想要删除到达的任务就可以调用awaitAdvance(arriveAndDeregister())

3. 终结

阶段或被终结,如果被终结的话,所有该阶段的参与者都不会再等待。相关方法:isTerminated,forceTermination

4. 分层

可以注册树状结构的阶段,即存在child和parent。当child的注册任务不为0时,child就会注册到parent上,否则就会被Deregister掉

5. 监控

通过getter可以获取到Phaser的信息。包括getRegisteredParties(), getArrivedParties(),getPhase(),getUnarrivedParties(),toString()


五、并发的线程间交换数据:Exchanger (交换器)

通过Exchanger可以实现线程间对象的相互传递。两个线程共享一个同步的点:Exchanger。和SynchronizedQueue左右相似,不同的是Exchanger是双向的,而SynchronizedQueue是从“生产者”到“消费者”的,而且SynchronizedQueue是没有内部空间的,仅仅相当于一个管道。

例子:

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();
  }
}


Reference:

Java Documents Provided by Oracle

《Java 7 Concurrency Cookbook》

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值