菜鸟学Java—并发—Semaphore、CountDownLatch、CyclicBarrier的使用

文章目录

  • Semaphore
    • Semaphore使用,模拟公交车
  • CountDownLatch
    • CountDownLatch 的三种典型用法
      • CountDownLatch 应用 ,喝到茶要干什么
      • CountDownLatch 应用2,发令枪
  • CyclicBarrier
    • CyclicBarrier 应用,游戏中多个玩家同时进入游戏
    • CyclicBarrier 应用,模拟秒杀

Semaphore

和 synchronized、ReentrantLock 不同的是,Semaphore 允许多个线程访问同一个资源,常常用作限制对一个资源的访问线程数。

Semaphore使用,模拟公交车

import java.util.concurrent.Semaphore;
/**
 * 模拟公交车(火车)
 * 信号量: 信号量维护了一些许可证,每一个 acquire() 方法在获取到许可证之前,如果当前没有可用的许可  证,就会被阻塞,然后才能拿到许可证。 每个release() 方法添加一个许可证,潜在得释放一个被阻塞的获取者。但是,并没有实际的许可证,Semaphore 只是维护了可用许可证的数量。
 * 信号量常常用来限制访问某些资源(物理的或逻辑的)的线程数量
 * @author ljx
 * @Date Jan 16, 2019 10:34:51 AM
 */
public class Bus {

	private final static int SEAT_MAX = 5;

	private Semaphore tickets = new Semaphore(SEAT_MAX);
	private boolean[] used = new boolean[SEAT_MAX];

    // 上车买票,然后得到一个座位
	public int getSeat() throws InterruptedException {
		tickets.acquire();
		return getAviliableSeat();
	}
	// 下车
	public void debus(int i) {
		markAsUnused(i);
		tickets.release();
	}

	private synchronized int getAviliableSeat() {
		for (int i = 0; i < SEAT_MAX; i++) {
			if (!this.used[i]) {
				used[i] = true;
				return i + 1;
			}
		}
		return -1;
	}

	private synchronized void markAsUnused(int i) {
		this.used[i - 1] = false;
	}
}

执行:

import java.util.concurrent.Executor;
import java.util.concurrent.Executors;

/**
 * 信号量: 信号量维护了一些许可证,每一个 acquire() 方法在获取到许可证之前,如果当前没有可用的许可证,就会被阻塞,然后才能拿到许可证。 每个
 * release() 方法添加一个许可证,潜在得释放一个被阻塞的获取者。但是,并没有实际的许可证,Semaphore 只是维护了可用许可证的数量。
 * 
 * 信号量常常用来限制访问某些资源(物理的或逻辑的)的线程数量
 * 
 * @author ljx
 * @Date Jan 16, 2019 9:58:47 AM
 *
 */
public class SemaphoreTest {

	static class Passenger implements Runnable {

		private Bus bus;
		private String name;

		public Passenger(Bus bus, String name) {
			this.bus = bus;
			this.name = name;
		}

		@Override
		public void run() {
			try {
				int seat = bus.getSeat();
				System.out.println("Passenger " + name + " get a seat: " + seat);
				Thread.sleep((int) (Math.random() * 5000));
				bus.debus(seat);
				System.out.println("           Passenger " + name + " debus,seat: " + seat + " is useable");
			} catch (InterruptedException e) {
				e.printStackTrace();
			}
		}

	}

	public static void main(String[] args) {
		Executor exe = Executors.newCachedThreadPool();
		Bus bus = new Bus();
		for (int i = 0; i < 8; i++) {
			exe.execute(new Passenger(bus,"P"+(i+1)));
		}
	}
}

执行结果:

Passenger P1 get a seat: 1
Passenger P2 get a seat: 2
Passenger P3 get a seat: 3
Passenger P4 get a seat: 4
Passenger P5 get a seat: 5
           Passenger P1 debus,seat: 1 is useable
Passenger P6 get a seat: 1
           Passenger P3 debus,seat: 3 is useable
Passenger P7 get a seat: 3
           Passenger P6 debus,seat: 1 is useable
Passenger P8 get a seat: 1
           Passenger P4 debus,seat: 4 is useable
           Passenger P8 debus,seat: 1 is useable
           Passenger P5 debus,seat: 5 is useable
           Passenger P7 debus,seat: 3 is useable
           Passenger P2 debus,seat: 2 is useable

CountDownLatch

CountDownLatch是一个同步工具类,它允许一个或多个线程一直等待,直到其他线程的操作执行完后再执行。

CountDownLatch 的三种典型用法

① 某一线程在开始运行前等待n个线程执行完毕。将 CountDownLatch 的计数器初始化为n :new
CountDownLatch(n) ,每当一个任务线程执行完毕,就将计数器减1
countdownlatch.countDown(),当计数器的值变为0时,在CountDownLatch上 await()
的线程就会被唤醒。一个典型应用场景就是启动一个服务时,主线程需要等待多个组件加载完毕,之后再继续执行。


实现多个线程开始执行任务的最大并行性。注意是并行性,不是并发,强调的是多个线程在某一时刻同时开始执行。类似于赛跑,将多个线程放到起点,等待发令枪响,然后同时开跑。做法是初始化一个共享的
CountDownLatch 对象,将其计数器初始化为 1 :new CountDownLatch(1) ,多个线程在开始执行任务前首先
coundownlatch.await(),当主线程调用 countDown() 时,计数器变为0,多个线程同时被唤醒。

③ 死锁检测:一个非常方便的使用场景是,你可以使用n个线程访问共享资源,在每次测试阶段的线程数目是不同的,并尝试产生死锁。

参考:JavaGuide/Java相关/Multithread/AQS.md#41-countdownlatch-的三种典型用法

CountDownLatch 应用 ,喝到茶要干什么

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

/**
 * CountDownLatch 应用 1:喝茶线程需要等到卖茶叶和烧开水线程完成之后才能执行
 * @author ljx
 * @Date Jan 16, 2019 11:31:13 AM
 */
public class CountDownLatchTest {

	static class DrinkTea implements Runnable{		
		private CountDownLatch cdl;
		public DrinkTea(CountDownLatch cdl) {
			this.cdl = cdl;
		}
		@Override
		public void run() {
			try {
				cdl.await();
			} catch (InterruptedException e) {
				e.printStackTrace();
			}
			System.out.println("drink tea");
		}
		
	}
	
	static class BuyTea implements Runnable{
		private CountDownLatch cdl;
		public BuyTea(CountDownLatch cdl) {
			this.cdl = cdl;
		}
		@Override
		public void run() {
			try {
				Thread.sleep(2000);
			} catch (InterruptedException e) {
				e.printStackTrace();
			}
			cdl.countDown();
			System.out.println("go outside buy tea");
		}
	}
	
	static class BoilWater implements Runnable{
		private CountDownLatch cdl;
		public BoilWater(CountDownLatch cdl) {
			this.cdl = cdl;
		}
		@Override
		public void run() {
			try {
				Thread.sleep(3000);
			} catch (InterruptedException e) {
				e.printStackTrace();
			}
			cdl.countDown();
			System.out.println("gu lu gu lu...");
		}
	}
	
	public static void main(String[] args) {
		ExecutorService exe = Executors.newFixedThreadPool(3);
		CountDownLatch cdl = new CountDownLatch(2);
		
		exe.execute(new DrinkTea(cdl));
		exe.execute(new BuyTea(cdl));
		exe.execute(new BoilWater(cdl));
		
		exe.shutdown();
	}
}

执行结果如下:

go outside buy tea
gu lu gu lu...
drink tea

CountDownLatch 应用2,发令枪

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

/**
 * CountDownLatch 应用 2 :多任务并行,多个线程在同一时刻执行 类似赛跑,发令枪响,所有运动员开始跑
 * 
 * @author ljx
 * @Date Jan 16, 2019 2:07:45 PM
 *
 */
public class CountDownLatchTest2 {

	static class Runner implements Runnable {
		private CountDownLatch cdl;
		private String name;// 跑者姓名
		private int during;// 跑完所花的时间

		public Runner(CountDownLatch cdl, String name, int during) {
			this.cdl = cdl;
			this.name = name;
			this.during = during;
		}

		@Override
		public void run() {
			try {
				// I am Ready !!!
				cdl.await();
				System.out.println(name + " run...");
				Thread.sleep(during); // running hard
				System.out.println(name + " run end....");
			} catch (InterruptedException e) {
				e.printStackTrace();
			}
		}
	}

	public static void main(String[] args) {
		ExecutorService exe = Executors.newFixedThreadPool(5);
		CountDownLatch cdl = new CountDownLatch(1);
		// Ready !!!
		for (int i = 0; i < 5; i++) {
			exe.execute(new Runner(cdl, "r" + (i + 1), (i + 1) * 1000));
		}
		System.out.println("...... Pang .....");
		// Go !!!
		cdl.countDown();
		exe.shutdown();
	}
}

执行结果:

......Pang .....
r1 run...
r2 run...
r3 run...
r4 run...
r5 run...
r1 run end....
r2 run end....
r3 run end....
r4 run end....
r5 run end....

CyclicBarrier

之所以叫循环,是因为 CyclicBarrier 可以通过 reset 方法进行重用。

CyclicBarrier 应用,游戏中多个玩家同时进入游戏

import java.util.concurrent.BrokenBarrierException;
import java.util.concurrent.CyclicBarrier;
import java.util.concurrent.ExecutorService;
import java.util.concurrent.Executors;

/**
 * 循环栅栏(屏障)。和 CountDownLatch 非常类似,可以实现线程间的等待,但功能更复杂和强大
 * 作用是,让一组线程达到同一个屏障(同步点)时被阻塞,直到最后一个线程到达同步点,才会打开屏障。
 * 
 * 实现游戏中十个玩家的加载,必须每个玩家都加载完成才会开始游戏。
 * @author ljx
 * @Date Jan 16, 2019 2:41:45 PM
 */
public class CyclicBarrierTest {

	static class Player implements Runnable {

		private CyclicBarrier cb;
		private String name;
		private int loadLatey;

		public Player(CyclicBarrier cb, String name, int ll) {
			this.cb = cb;
			this.name = name;
			this.loadLatey = ll;
		}

		@Override
		public void run() {
			try {
				Thread.sleep(loadLatey);
				System.out.println(name + " Ready....");
				cb.await();
			} catch (InterruptedException e) {
				e.printStackTrace();
			} catch (BrokenBarrierException e) {
				e.printStackTrace();
			}
		}

		public static void main(String[] args) {
			final int TEN = 10;
			ExecutorService exe = Executors.newFixedThreadPool(TEN);
			// CyclicBarrier(int parties, Runnable barrierAction),指定线程到达屏障时的操作,优先执行barrierAction(parties 个线程)
			CyclicBarrier cb = new CyclicBarrier(TEN,() -> {
				System.out.println("欢迎来到 王者荣耀");
			});
			for (int i = 1; i <= TEN; i++) {
				exe.execute(new Player(cb, "P" + i, i * 1000));
			}			
			exe.shutdown();
		}
	}
}

执行结果:

P1 Ready....
P2 Ready....
P3 Ready....
P4 Ready....
P5 Ready....
P6 Ready....
P7 Ready....
P8 Ready....
P9 Ready....
P10 Ready....
欢迎来到 王者荣耀

CyclicBarrier 应用,模拟秒杀

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

/**
 * 秒杀应用模拟,使用 Semaphore 和 CyclicBarrier(CountDownLatch 也可以完成)
 * @author ljx
 * @Date Jan 16, 2019 2:33:42 PM
 */
public class Seckill {

	/**
	 * 商品数量有限
	 * @author ljx
	 * @Date Jan 16, 2019 4:27:19 PM
	 *
	 */
	static class Goods{
		private final int MAX = 10;
		private int count = MAX;
		private Semaphore max = new Semaphore(MAX,false);
		
		public int take() throws InterruptedException {
			return max.tryAcquire() ? getNext() : -1;
		}
		
		private synchronized int getNext() {
			count--;
			return 1;
		}
	}
	
	static class Consumer implements Runnable{

		private Goods goods;
		private String name;
		private CyclicBarrier cb;
		public Consumer(Goods goods,String name,CyclicBarrier cb) {
			this.goods = goods;
			this.name = name;
			this.cb = cb;
		}
		@Override
		public void run() {
			try {
				// 假设没有延迟
				cb.await();
			} catch (InterruptedException | BrokenBarrierException e) {
				e.printStackTrace();
			}
			try {
				int yeah = goods.take();
				if(yeah != -1) {
					System.out.println(name + " got "+ yeah);
				}else {
					System.out.println(name + " bad... "+ yeah);
				}
			} catch (InterruptedException e) {
				e.printStackTrace();
			}
		}
	}
	
	public static void main(String[] args) {
		int parties = 15;
		// 参加的人为 parties 个时,开始秒杀
		CyclicBarrier cb = new CyclicBarrier(parties);
		ExecutorService pool = Executors.newCachedThreadPool();
		Goods g = new Goods();
		for(int i = 0;i<parties;i++) {
			pool.execute(new Consumer(g,"C"+(i+1),cb));
		}
		pool.shutdown();
	}
}

执行结果:

C1 got 1
C13 bad... -1
C12 bad... -1
C11 bad... -1
C10 got 1
C9 got 1
C8 got 1
C7 got 1
C6 got 1
C5 got 1
C4 got 1
C3 got 1
C2 got 1
C14 bad... -1
C15 bad... -1

你可能感兴趣的:(java,并发,菜鸟学Java)