Java并发协作控制之相关API

线程协作

  • Thread/Executor/Fork-Join
    – 线程启动,运行,结束
    – 线程之前缺少协作
  • synchronized 同步
    – 限定只能一个线程进入关键区
    – 简单粗暴,性能损失大

Lock

  • Lock实现同步的效果
    – 实现更复杂的临界区结构
    – tryLock方法可以预判锁是否空闲
    – 允许分离读写操作,多个读,一个写
    – 性能更好
  • ReentrantLock 类,可重入的互斥锁
  • ReentrantReadWriteLock 类,可重入的读写锁
  • lock和unlock函数

奶茶店的例子

public class LockExample {

	private static final ReentrantLock queueLock = new ReentrantLock(); //可重入锁
	private static final ReentrantReadWriteLock orderLock = new ReentrantReadWriteLock(); //可重入读写锁
	
	/**
	 * 有家奶茶店,点单有时需要排队 
	 * 假设想买奶茶的人如果看到需要排队,就决定不买
	 * 又假设奶茶店有老板和多名员工,记单方式比较原始,只有一个订单本
	 * 老板负责写新订单,员工不断地查看订单本得到信息来制作奶茶,在老板写新订单时员工不能看订单本 ,读写分离
	 * 多个员工可同时看订单本,在员工看时老板不能写新订单
	 * @param args
	 * @throws InterruptedException 
	 */
	public static void main(String[] args) throws InterruptedException {
		buyMilkTea(); //可重入锁,锁需要手动关闭
		handleOrder(); //锁需手动关闭
	}
	
	public void tryToBuyMilkTea() throws InterruptedException {
		boolean flag = true;
		while(flag)
		{
			if (queueLock.tryLock()) { //重入锁,如果有人排队,就返回false
				//queueLock.lock();,tryLock()中,判断是否lock,还包含了lock的操作,
				long thinkingTime = (long) (Math.random() * 500);
				Thread.sleep(thinkingTime);
				System.out.println(Thread.currentThread().getName() + ": 来一杯珍珠奶茶,不要珍珠");
				flag = false;
				queueLock.unlock();
			} else {
				//System.out.println(Thread.currentThread().getName() + ":" + queueLock.getQueueLength() + "人在排队");
				System.out.println(Thread.currentThread().getName() + ": 再等等");
			}
			if(flag)
			{
				Thread.sleep(1000);
			}
		}
		
	}
	
	public void addOrder() throws InterruptedException {
		orderLock.writeLock().lock();
		long writingTime = (long) (Math.random() * 1000);
		Thread.sleep(writingTime);
		System.out.println("老板新加一笔订单");
		orderLock.writeLock().unlock();
	}
	
	public void viewOrder() throws InterruptedException {
		orderLock.readLock().lock();  //readLock,读的锁,可以多个线程共享,比如这里就可以三个员工同时查看,writeLock,写的锁,只能一个线程拥有
			
		long readingTime = (long) (Math.random() * 500);
		Thread.sleep(readingTime);
		System.out.println(Thread.currentThread().getName() + ": 查看订单本");
		orderLock.readLock().unlock();			

	}
	
	public static void buyMilkTea() throws InterruptedException {
		LockExample lockExample = new LockExample();
		int STUDENTS_CNT = 10;  
		
		Thread[] students = new Thread[STUDENTS_CNT];  //线程数组
		for (int i = 0; i < STUDENTS_CNT; i++) {
			students[i] = new Thread(new Runnable() {  //数组里每一个元素都是一个匿名线程类

				@Override
				public void run() {
					try {
						long walkingTime = (long) (Math.random() * 1000); //产生随机数
						Thread.sleep(walkingTime);
						lockExample.tryToBuyMilkTea();
					} catch(InterruptedException e) {
						System.out.println(e.getMessage());
					}
				}
				
			}
			);
			
			students[i].start();
		}
		
		for (int i = 0; i < STUDENTS_CNT; i++)
			students[i].join();

	}
	
	
	public static void handleOrder() throws InterruptedException {
		LockExample lockExample = new LockExample();
		
		
		Thread boss = new Thread(new Runnable() {

			@Override
			public void run() {
				while (true) {
					try {
						lockExample.addOrder(); //添加订单
						long waitingTime = (long) (Math.random() * 1000);
						Thread.sleep(waitingTime);
					} catch (InterruptedException e) {
						System.out.println(e.getMessage());
					}
				}
			}
		});
		boss.start();

		int workerCnt = 3;  //员工
		Thread[] workers = new Thread[workerCnt];
		for (int i = 0; i < workerCnt; i++)
		{
			workers[i] = new Thread(new Runnable() {

				@Override
				public void run() {
					while (true) {
						try {
								lockExample.viewOrder();
								long workingTime = (long) (Math.random() * 5000);
								Thread.sleep(workingTime);
							} catch (InterruptedException e) {
								System.out.println(e.getMessage());
							}
						}
				}
				
			});
			
			workers[i].start();
		}
		
	}
}
//输出
Thread-1: 再等等
Thread-5: 再等等
Thread-9: 再等等
Thread-8: 来一杯珍珠奶茶,不要珍珠
Thread-6: 再等等
Thread-2: 来一杯珍珠奶茶,不要珍珠
Thread-3: 再等等
Thread-4: 再等等
Thread-7: 再等等
Thread-1: 再等等
Thread-5: 再等等
Thread-0: 来一杯珍珠奶茶,不要珍珠
Thread-9: 来一杯珍珠奶茶,不要珍珠
Thread-3: 再等等
Thread-4: 再等等
Thread-7: 再等等
Thread-6: 来一杯珍珠奶茶,不要珍珠
Thread-5: 再等等
Thread-1: 来一杯珍珠奶茶,不要珍珠
Thread-4: 再等等
Thread-7: 再等等
Thread-5: 再等等
Thread-3: 来一杯珍珠奶茶,不要珍珠
Thread-7: 再等等
Thread-5: 再等等
Thread-4: 来一杯珍珠奶茶,不要珍珠
Thread-7: 来一杯珍珠奶茶,不要珍珠
Thread-5: 来一杯珍珠奶茶,不要珍珠
老板新加一笔订单
Thread-12: 查看订单本
Thread-11: 查看订单本
Thread-13: 查看订单本
老板新加一笔订单
老板新加一笔订单
Thread-11: 查看订单本
老板新加一笔订单
Thread-11: 查看订单本
Thread-12: 查看订单本
老板新加一笔订单
Thread-12: 查看订单本
读写分离了 

Semaphore

  • 信号量:本质是一个计数器
  • 计数器大于0,可以使用,等于0不能使用
  • 可以设置多个并发量,例如现在十个访问(需要计数器满足要求,才能启动线程)
  • Semaphore
    – acquire 获取,信号量减一
    – release 释放,信号量加一
  • 比Lock更进一步,可以控制多个同时访问关键区
  • 限制一个关键区,多少个线程同时访问

停车的例子,五个车位,十个车的线程类,run方法就是判断是否有空车位,然后是否停进去。一个信号量来判断车位是否为满。协作多个线程

public class SemaphoreExample {

	private final Semaphore placeSemaphore = new Semaphore(5);
	
	public boolean parking() throws InterruptedException {
		if (placeSemaphore.tryAcquire()) { //这里 判断是否有空车位,如果有,车停进去,会把信号量减一
			System.out.println(Thread.currentThread().getName() + ": 停车成功");
			return true;
		} else {
			System.out.println(Thread.currentThread().getName() + ": 没有空位");
			return false;
		}

	}
	
	public void leaving() throws InterruptedException {
		placeSemaphore.release();  //信号量加一
		System.out.println(Thread.currentThread().getName() + ": 开走");
	}
	
	/**
	 * 现有一地下车库,共有车位5个,由10辆车需要停放,每次停放时,去申请信号量
	 * @param args
	 * @throws InterruptedException 
	 */
	public static void main(String[] args) throws InterruptedException {
		int tryToParkCnt = 10;  //定义十辆车,十个车的线程类
		
		SemaphoreExample semaphoreExample = new SemaphoreExample();
		
		Thread[] parkers = new Thread[tryToParkCnt];
		
		for (int i = 0; i < tryToParkCnt; i++) {
			parkers[i] = new Thread(new Runnable() {

				@Override
				public void run() {
					try {
						long randomTime = (long) (Math.random() * 1000);
						Thread.sleep(randomTime);
						if (semaphoreExample.parking()) {  
							long parkingTime = (long) (Math.random() * 1200);
							Thread.sleep(parkingTime);
							semaphoreExample.leaving(); // 离开车位
						}
					} catch (InterruptedException e) {
						e.printStackTrace();
					}
				}
			});
			
			parkers[i].start();
		}

		for (int i = 0; i < tryToParkCnt; i++) {
			parkers[i].join();  //这里就是让所有车线程执行完,才结束main线程
		}	
	}
}

Thread.join()方法,在A线程中调用了B线程的join()方法时,表示只有当B线程执行完毕时,A线程才能继续执行。

Latch

  • 等待锁,是一个同步辅助类
  • 用来同步执行任务的一个或者多个线程
  • 不是用来保护临界区或者共享资源
  • 用来协调各个线程,同步 一个或者多个线程在一个地方等待,等大家都到了,再往下走(采用计数器,设置计数器的数量,等到计数器到达0,才开始启动线程里工作的代码)
  • CountDownLatch
    – countDown() 计数器减一
    – await() 等待latch变成0,变成0,await解锁。
public class CountDownLatchExample {

	/**
	 * 设想百米赛跑比赛 发令枪发出信号后选手开始跑,全部选手跑到终点后比赛结束
	 * 
	 * @param args
	 * @throws InterruptedException
	 */
	public static void main(String[] args) throws InterruptedException {
		int runnerCnt = 10;
		CountDownLatch startSignal = new CountDownLatch(1);  //启动信号
		CountDownLatch doneSignal = new CountDownLatch(runnerCnt);  //到终点的选手,要满十个

		for (int i = 0; i < runnerCnt; ++i) // create and start threads
			new Thread(new Worker(startSignal, doneSignal)).start();

		System.out.println("准备工作...");
		System.out.println("准备工作就绪");
		startSignal.countDown(); // 将Latch减一,变成0,将会唤醒Latch上的await线程,解锁他们的await等待
		System.out.println("比赛开始");
		doneSignal.await(); // 等待所以选手到达,解锁这个await
		System.out.println("比赛结束");
	}

	static class Worker implements Runnable {
		private final CountDownLatch startSignal;
		private final CountDownLatch doneSignal;

		Worker(CountDownLatch startSignal, CountDownLatch doneSignal) {
			this.startSignal = startSignal;
			this.doneSignal = doneSignal;
		}

		public void run() {
			try {
				startSignal.await(); //等startSignal为0
				doWork();
				doneSignal.countDown();
			} catch (InterruptedException ex) {
			} // return;
		}

		void doWork() {
			System.out.println(Thread.currentThread().getName() + ": 跑完全程");
		}
	}
}
//输出
准备工作...
准备工作就绪
比赛开始
Thread-5: 跑完全程
Thread-6: 跑完全程
Thread-4: 跑完全程
Thread-2: 跑完全程
Thread-3: 跑完全程
Thread-0: 跑完全程
Thread-8: 跑完全程
Thread-7: 跑完全程
Thread-1: 跑完全程
Thread-9: 跑完全程
比赛结束

Barrier

  • 集合点,也是一个同步辅助类
  • 允许多个线程在某一点上进行同步(等待所有子线程做好任务,然后await,然后await的数量到达要求,解锁await,同时可以启动一个总线程)
  • CyclicBarrier 类
    – 构造函数是需要同步的线程数量
    – await等待其他线程,到达数后, 就放行
public class CyclicBarrierExample {
	
	/**
	 * 假定有三行数,用三个线程分别计算每一行的和,最终计算总和
	 * @param args
	 */
	public static void main(String[] args) {
		final int[][] numbers = new int[3][5];
		final int[] results = new int[3];
		int[] row1 = new int[]{1, 2, 3, 4, 5};
		int[] row2 = new int[]{6, 7, 8, 9, 10};
		int[] row3 = new int[]{11, 12, 13, 14, 15};
		numbers[0] = row1;
		numbers[1] = row2;
		numbers[2] = row3;
		
		CalculateFinalResult finalResultCalculator = new CalculateFinalResult(results);
		CyclicBarrier barrier = new CyclicBarrier(3, finalResultCalculator);
		//当有3个线程在barrier上await,就执行finalResultCalculator
		
		for(int i = 0; i < 3; i++) {
			CalculateEachRow rowCalculator = new CalculateEachRow(barrier, numbers, i, results);
			new Thread(rowCalculator).start();
		}		
	}
}

class CalculateEachRow implements Runnable {

	final int[][] numbers;
	final int rowNumber;
	final int[] res;
	final CyclicBarrier barrier;
	
	CalculateEachRow(CyclicBarrier barrier, int[][] numbers, int rowNumber, int[] res) {
		this.barrier = barrier;
		this.numbers = numbers;
		this.rowNumber = rowNumber;
		this.res = res;
	}
	
	@Override
	public void run() {
		int[] row = numbers[rowNumber];
		int sum = 0;
		for (int data : row) {
			sum += data;
			res[rowNumber] = sum;
		}
		try {
			System.out.println(Thread.currentThread().getName() + ": 计算第" + (rowNumber + 1) + "行结束,结果为: " + sum);
			barrier.await(); //等待!只要超过3个(Barrier的构造参数),就放行。
		} catch (InterruptedException | BrokenBarrierException e) {
			e.printStackTrace();
		}
	}
	
}


class CalculateFinalResult implements Runnable {
	final int[] eachRowRes;
	int finalRes;
	public int getFinalResult() {
		return finalRes;
	}
	
	CalculateFinalResult(int[] eachRowRes) {
		this.eachRowRes = eachRowRes;
	}
	
	@Override
	public void run() {
		int sum = 0;
		for(int data : eachRowRes) {
			sum += data;
		}
		finalRes = sum;
		System.out.println("最终结果为: " + finalRes);
	}
	
}
//输出
Thread-0: 计算第1行结束,结果为: 15
Thread-2: 计算第3行结束,结果为: 65
Thread-1: 计算第2行结束,结果为: 40
最终结果为: 120

当Barrier上的await的线程数量到达预定的要求后,所有的await的线程不再等待,全部解锁。同时Barrier会执行前面参数上的回调动作。
Barrier非常适合做这种归并程序,每个子线程做自己的任务,等到所有子线程完成后,在启动最终合并的那个线程。

Phaser

  • 允许执行并发多阶段任务,同步辅助类
  • 在每一个阶段结束的位置对线程进行同步,当所有的线程都达到这步,再进行下一步
  • 与 Barrier不同的是,可以多次运用
  • Phaser
    – arrive(),不做等待
    –arriveAndAwaitAdvance(),等待
public class PhaserExample {

	/**
	 * 假设举行考试,总共三道大题,每次下发一道题目,等所有学生完成后再进行下一道
	 * 
	 * @param args
	 */
	public static void main(String[] args) {

		int studentsCnt = 5;
		Phaser phaser = new Phaser(studentsCnt);

		for (int i = 0; i < studentsCnt; i++) {
			new Thread(new Student(phaser)).start();
		}
	}
}

class Student implements Runnable {

	private final Phaser phaser;

	public Student(Phaser phaser) {
		this.phaser = phaser;
	}

	@Override
	public void run() {
		try {
			doTesting(1);
			phaser.arriveAndAwaitAdvance(); //等到5个线程都到了,才放行
			doTesting(2);
			phaser.arriveAndAwaitAdvance();
			doTesting(3);
			phaser.arriveAndAwaitAdvance();
		} catch (InterruptedException e) {
			e.printStackTrace();
		}
	}

	private void doTesting(int i) throws InterruptedException {
		String name = Thread.currentThread().getName();
		System.out.println(name + "开始答第" + i + "题");
		long thinkingTime = (long) (Math.random() * 1000);
		Thread.sleep(thinkingTime);
		System.out.println(name + "第" + i + "道题答题结束");
	}
}

Exchanger

  • 允许在并发线程中互相互换消息

  • 允许在俩个线程中定义同步的点,当俩个线程都达到同步点,然后开始交换数据

  • Exchanger
    – exchange(),线程双方互相交互数据
    – 交换数据是双向的

public class ExchangerExample {
	
	/**
	 * 本例通过Exchanger实现学生成绩查询,简单线程间数据的交换
	 * @param args
	 * @throws InterruptedException
	 */
	public static void main(String[] args) throws InterruptedException {
		Exchanger<String> exchanger = new Exchanger<String>();
		BackgroundWorker worker = new BackgroundWorker(exchanger);
		new Thread(worker).start();
		
		Scanner scanner = new Scanner(System.in);
		while(true) {
			System.out.println("输入要查询的属性学生姓名:");
			String input = scanner.nextLine().trim();
			exchanger.exchange(input); //把用户输入传递给线程
			String value = exchanger.exchange(null); //拿到线程反馈结果
			if ("exit".equals(value)) {
				break;
			}
			System.out.println("查询结果:" + value);
		}
		scanner.close();
	} 
}

class BackgroundWorker implements Runnable {

	final Exchanger<String> exchanger;
	BackgroundWorker(Exchanger<String> exchanger) {
		this.exchanger = exchanger;
	}
	@Override
	public void run() {
		while (true) {
			try {
				String item = exchanger.exchange(null);
				switch (item) {
				case "zhangsan": 
					exchanger.exchange("90");
					break;
				case "lisi":
					exchanger.exchange("80");
					break;
				case "wangwu":
					exchanger.exchange("70");
					break;
				case "exit":
					exchanger.exchange("exit");
					return;
				default:
					exchanger.exchange("查无此人");
				}					
			} catch (InterruptedException e) {
				e.printStackTrace();
			}				
		}
	}		
}

注意:

  • 只有当俩个线程都在进入 exchange ()方法并给出对象时,才能接受其他线程返回时给出的对象。
  • 每次只能两个线程交换数据,如果有多个线程,也只有两个能交换彼此的数据。其他线程在各自交换,这样怎么保证是需要正确的线程交换数据?
  • 当线程A达到change()时,会等待线程B达到change()吗?然后各自交换数据,中间有一个缓冲区。交换数据的时候线程A,线程B在干嘛呢?
  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值