Java学习笔记-并发编程-1.4 J.U.C工具类

1.4 J.U.C工具类

CountDownLatch

CountDownLatch:用于监听某些初始化操作,并且线程进行阻塞,等初始化执行完毕后,通知主线程继续工作执行。

public class UseCountDownLatch {

	
	public static void main(String[] args) {
		
        // 构造参数表示唤醒线程需要通知的次数
		CountDownLatch countDownLatch = new CountDownLatch(2);
		
		 Thread t1 = new Thread(new Runnable() {
			
			@Override
			public void run() {
				System.err.println("进入t1线程..");
				try {
					Thread.sleep(3000);	//做了一些初始化的准备..
				} catch (InterruptedException e) {
					e.printStackTrace();
				}
				System.err.println("t1线程初始化完毕, 通知t3 线程继续操作");
                // 通知唤醒
				countDownLatch.countDown();
			}
		}, "t1");
		 
		 Thread t2 = new Thread(new Runnable() {
				
			@Override
			public void run() {
				System.err.println("进入t2线程..");
				try {
					Thread.sleep(4000);	//做了一些初始化的准备..
				} catch (InterruptedException e) {
					e.printStackTrace();
				}
				System.err.println("t2线程初始化完毕, 通知t3 线程继续操作");
                // 通知唤醒
				countDownLatch.countDown();
				
			}
		}, "t2");
		 
		 Thread t3 = new Thread(new Runnable() {
				
			@Override
			public void run() {
				System.err.println("进入t3线程.., 并且进入等待...");
				try {
                    // 休眠,等待唤醒
					countDownLatch.await();
				} catch (InterruptedException e) {
					e.printStackTrace();
				}
				System.err.println("t3线程 进行后续的操作!");
				
			}
		}, "t3");
		 
		 t1.start();
		 t2.start();
		 t3.start();
		
	}
}

//运行结果
进入t1线程..
进入t2线程..
进入t3线程.., 并且进入等待...
t1线程初始化完毕, 通知t3 线程继续操作
t2线程初始化完毕, 通知t3 线程继续操作
t3线程 进行后续的操作!
    
t3线程进入之后,调用countDownLatch.await();方法来等待其他线程完成初始化之后再开始操作。

CyclicBarrier

CyclicBarrier:栅栏的概念,多线程的进行阻塞,等待某一个临界值条件满足后,同时执行

假设一个场景:每个线程代表一个跑步运动员,当运动员都准备好后,才一起出发,只要有一个人没有准备好,大家都等待!

public class UseCyclicBarrier {

	
	static class Runner implements Runnable {
		
		private String name;
		
		private CyclicBarrier cyclicBarrier;
		
		public Runner(String name, CyclicBarrier cyclicBarrier){
			this.name = name;
			this.cyclicBarrier = cyclicBarrier;
		}
		
		
		@Override
		public void run() {
			try {
				System.err.println("运动员:" + this.name + ", 进行准备工作!");
                // 随机休眠
				Thread.sleep(1000 * (new Random()).nextInt(10));
				System.err.println("运动员:" + this.name + ", 准备OK!");
                // 阻塞等待
				this.cyclicBarrier.await();
			} catch (InterruptedException e) {
				e.printStackTrace();
			} catch (BrokenBarrierException e) {
				e.printStackTrace();
			}
			System.err.println("运动员: " + this.name + ", GO! GO! GO!");
		}
	}
	
	
	public static void main(String[] args) {
		
        // 栅栏阻塞三个线程
		CyclicBarrier cyclicBarrier = new CyclicBarrier(3);
		
        // 使用线程池
		ExecutorService executorService = Executors.newFixedThreadPool(3);
		
		executorService.submit(new Thread(new Runner("张三", cyclicBarrier)));
		executorService.submit(new Thread(new Runner("李四", cyclicBarrier)));
		executorService.submit(new Thread(new Runner("王五", cyclicBarrier)));
		
		executorService.shutdown();
		
	}
}

// 运行结果
运动员:李四, 进行准备工作!
运动员:张三, 进行准备工作!
运动员:王五, 进行准备工作!
运动员:张三, 准备OK!
运动员:王五, 准备OK!
运动员:李四, 准备OK!
运动员: 李四, GO! GO! GO!
运动员: 张三, GO! GO! GO!
运动员: 王五, GO! GO! GO!
    
每个人都要等待其他人准备好之后再一起起跑

实际场景使用

在这里插入图片描述

图中,方法4需要等待方法1,2,3运行的结果作为参数来运行,因此方法4需要等待其余方法执行完成才能运行。与使用监听器监听其他方法的执行结果相比,使用CyclicBarrier来进行阻塞更为直观方便。

Future与Caller回调

  • Future模式,也是非常经典的设计模式,这种模式主要就利用空间换时间的概念,也就是说异步执行(需要开启一个新的线程)。
  • 互联网高并发的应用服务中,我们随处可见这种理念和代码,主要就是使用了这种模式!
  • Future模式非常适合在处理很耗时很长的业务逻辑时进行使用,可以有效的减小系统的响应时间提高系统的吞吐量
public class UseFuture implements Callable<String> {

	private String param;
	
	public UseFuture(String param){
		this.param = param;
	}
	
	@Override
	public String call() throws Exception {
		//模拟执行业务逻辑的耗时
		Thread.sleep(3000);
		String result = this.param + ", 处理完成!";
		return result;
	}

	
	public static void main(String[] args) throws InterruptedException, ExecutionException {
		
		String queryStr1 = "query1";
		String queryStr2 = "query2";
		FutureTask<String> future1 = new FutureTask<String>(new UseFuture(queryStr1));
		
		FutureTask<String> future2 = new FutureTask<String>(new UseFuture(queryStr2));
		
		
		ExecutorService executorService = Executors.newFixedThreadPool(2);
		
		executorService.submit(future1);	//异步的操作
		executorService.submit(future2);	//异步的操作
		
		System.err.println("处理其他相关的任务...");
		Thread.sleep(2000);
		
		String ret1 = future1.get();
		String ret2 = future2.get();
		
		System.err.println("数据处理完成: " + ret1);
		System.err.println("数据处理完成: " + ret2);
	}
}

// 运行结果
处理其他相关的任务...
数据处理完成: query1, 处理完成!
数据处理完成: query2, 处理完成!
    
future1 和 future2与主线程为异步操作

利用设计模式模拟Future

Future模式有点类似于商品订单。
比如在网购时,当看重某一件商品事,就可以提交订单,当订单处理完成后,在家里等待商品送货上门即可。

或者说更形象的我们发送Ajax请求的时候,页面是异步的进行后台处理,用户无须一直等待请求的结果,可以继续浏览或操作其他内容。

在这里插入图片描述

此处将模拟Future模式

先定义接口Data,然后分别创建FutureData包装类和RealData真实数据类,两个类均实现Data接口

然后创建FutureClient客户端,最后创建Main来进行测试

/**
 *	Data 接口
 */
public interface Data {

	String getRequest();
	
}
/**
 * Data 包装类
 */
public class FutureData implements Data {

	// 真实数据对象的引用
	private RealData realData;
	
	// 实际处理的状态
	private boolean isReady = false;
	
	public synchronized void setRealData(RealData realData) {
		
		if(isReady) {
			return;
		}
		//如果真实对象赋值成功, 那么就认为数据已经准备好了
		this.realData = realData;
		
		isReady = true;
		//真实数据已经准备好了的时候 我们进行唤醒操作
		notify();
		
	}
	
	@Override
	public synchronized String getRequest() {
		
		while(!isReady) {
			try {
				// 等待线程准备真实数据
				wait();
			} catch (InterruptedException e) {
				e.printStackTrace();
			}
		}
		// 返回真实数据
		return this.realData.getRequest();
	}

}

/**
 * Data 真实数据类
 */
public class  RealData implements Data {

	private String result;

	/**
	 * 创建
	 * @param queryStr
	 */
	public RealData(String queryStr) {
		System.err.println("根据查询参数:" + queryStr + "进行查询数据库操作, 这可能需要5秒左右...");
		try {
			//查询耗时
			Thread.sleep(5000);
		} catch (InterruptedException e) {
			e.printStackTrace();
		}
		result = "100条数据";
	}

	@Override
	public String getRequest() {
		return result;
	}

}
/**
 * Future 客户端
 */
public class FutureClient {

	public Data request(final String queryStr) {
		
		FutureData futureData = new FutureData();
		
		//异步的起一个线程去进行相应的处理
		new Thread(new Runnable() {
			@Override
			public void run() {
				//需要把请求参数 设置到真实的数据处理对象中去
				RealData realData = new RealData(queryStr);
				//真实请求处理完成以后, 我们要进行设置结果给包装类对象
				futureData.setRealData(realData);
			}
		}).start();

		return futureData;
	}

}
public class Main {

	
	public static void main(String[] args) {
		
		FutureClient fc = new FutureClient();
		Data data = fc.request("请求参数..");		//异步执行的
		System.err.println("做其他的相关业务操作!");
		String ret = data.getRequest();		//才是真正的获取实际数据的方法
		System.err.println("--------: " + ret);
	}
	
}

// 运行结果
做其他的相关业务操作!
根据查询参数:请求参数..进行查询数据库操作, 这可能需要5秒左右...
--------100条数据 //5秒后才输出该信息

在这里插入图片描述

Exchanger多线程间数据交换

Exchanger用于进行线程间的数据交换,它提供一个同步点,在这个同步点,两个线程可以交换彼此的数据;
两个线程通过exchange方法交换数据,如果一个线程先执行exchange方法,它会一直等待第二个线程也执行exchange方法
当两个线程都达到同步点时,这两个线程就可以交换数据,将本线程生产出来的数据传递给对方

使用场景:
遗传算法:遗传算法里需要选出两个人作为交换对象,这时会交换两人的数据,并使用交叉规则得出两个人交换结果。
用于校对工作:A、B同时录入数据,然后对A、B进行比较,看是否录入一致,保证数据正确性。
比如我们需要将纸制银流通过人工的方式录入成电子银行流水,为了避免错误,采用AB岗两人进行录入,录入到Excel之后,系统需要加载这两个Excel,并对这两个Excel数据进行校对,看看是否录入的一致。


public class UseExchanger {
	
	private static final Exchanger<String> exchanger = new Exchanger<>();

	private static ExecutorService executorService = Executors.newFixedThreadPool(2);
	
	public static void main(String[] args) {
		
		executorService.execute(new Runnable() {
			@Override
			public void run() {
				String A = "银行流水A";
				try {
					String B = exchanger.exchange(A);	//交换我自己的数据 并且获取别人的数据
				
					System.err.println("线程A: " + B);
				} catch (InterruptedException e) {
					e.printStackTrace();
				}	
			}
		});
		
		executorService.execute(new Runnable() {
			@Override
			public void run() {
				String B = "银行流水B";
				try {
					String A = exchanger.exchange(B);	//交换我自己的数据 并且获取别人的数据
					
					System.err.println("线程B: " + A);
				} catch (InterruptedException e) {
					e.printStackTrace();
				}		
			}
		});
		executorService.shutdown();
	}
	
}

ForkJoin并行

Fork/Join框架是JAVA7提供的一个用于并行执行任务的框架,是一个把大任务分割成若干个小任务,最终汇总每个小任务结果后得到大任务结果的框架

Fork/Join中两个重要的类:

  • ForkJoinTask:使用该框架,需要创建一个ForkJoin任务,它提供在任务中执行forkjoin操作的机制。一般情况下,我们并不需要直接继承ForkJoinTask类,只需要继承它的子类,它的子类有两个:
    • RecursiveAction:用于没有返回结果的任务。
    • RecursiveTask:用于有返回结果的任务。
  • ForkJoinPool:任务ForkJoinTask需要通过ForkJoinPool来执行。
public class UseForkJoin extends RecursiveTask<Integer> {
	
	private static final int THRESHOLD = 2;		//阈值
	
	private int start;
	
	private int end;
	
	public UseForkJoin(int start, int end) {
		this.start = start;
		this.end = end;
	}
	
	@Override
	protected Integer compute() {
		
		int sum = 0;	// 1 + 2 .... + 100
		
		boolean canCompute = (end - start) <= THRESHOLD;
		
		if(canCompute) {
			for(int i = start; i <=end; i++ ){
				sum += i;
			}
		} else {
			//如果说 任务数大于阈值的话, 就进行拆分 fork操作 然后去join 
			// 1 + 100 /2  = 50
			int middle = (start + end)/2;
			UseForkJoin leftTask = new UseForkJoin(start, middle);
			UseForkJoin rightTask = new UseForkJoin(middle+1, end);
			
			//执行左右两边的任务
			leftTask.fork();		
			rightTask.fork();	
			//等待任务执行完成后 进行获取结果
			int leftResult = leftTask.join();
			int rightResult = rightTask.join();
			sum = leftResult + rightResult;
		}
		
		return sum;
	}

	public static void main(String[] args) throws InterruptedException, ExecutionException {
		ForkJoinPool pool = new ForkJoinPool();
		
		UseForkJoin ufj = new UseForkJoin(1, 100);
		
		Future<Integer> result = pool.submit(ufj);
		
		System.err.println("最终执行结果为:" + result.get());
		
	}
	
}

// 运行结果:
最终执行结果为:5050

Master-Worker并发组件模拟-生产消费者模式

Master-Worker模式是常用的并行计算模式

它的核心思想是系统由两类进程协作工作:Master进程和Worker进程

Master负责接收和分配任务,Worker负责处理子任务。

当各个Worker子进程处理完成后,会将结果返回给Master,由Master做归纳和总结

其好处是能将一个大任务分解成若干个小任务,并行执行,从而提高系统的吞吐量

设置一个Master类和一个Worker类,然后定义一个Task任务类,最后编写测试Main

public class Master {

	//1 承装任务的一个容器:
	private ConcurrentLinkedQueue<Task> taskQueue = new ConcurrentLinkedQueue<>();
	
	//2 承装worker执行器
	private HashMap<String, Thread> workers = new HashMap<>();
	
	//3 接受worker处理成功的结果集合
	private ConcurrentHashMap<String, Object> resultMap = new ConcurrentHashMap<>();
	
	//4 构造方法里面,要对worker进行一个初始化操作
	public Master(Worker worker, int workCount) {
		//4.1 每一个worker 应该有master里 任务队列容器的引用
		worker.setTaskQueue(this.taskQueue);
		//4.2 每一个worker 应该有master里 结果集容器的引用
		worker.setResultMap(this.resultMap);
		//4.3 我们把所有的worker进行初始化 放入 workers容器中
		for(int i = 0; i < workCount; i ++){
			this.workers.put(Integer.toString(i), new Thread(worker));
		}
	}
	
	//5 需要一个提交任务的方法
	public void submit(Task task) {
		this.taskQueue.add(task);
	}
	
	//6 需要有一个真正让我们Master里的所有Worker进行工作的方法
	public void execute() {
		for(Map.Entry<String, Thread> me : this.workers.entrySet()) {
			me.getValue().start();
		}
	}
	
	//7 需要有一个统计的方法,用于合并结果集
	public int getResult() {
		int sum = 0;
		for(Map.Entry<String, Object> me : resultMap.entrySet()) {
			sum += (Integer)me.getValue();
		}
		return sum;
	}

	//8 判断是否所有的worker都完成工作了 如果完成返回true
	public boolean isComplete() {
		for(Map.Entry<String, Thread> me : this.workers.entrySet()) {
			if(me.getValue().getState() != Thread.State.TERMINATED){
				return false;
			}
		}
		return true;
	}
	
}
public class Worker implements Runnable {

	private ConcurrentLinkedQueue<Task> taskQueue;
	
	private ConcurrentHashMap<String, Object> resultMap;
	
	public void setTaskQueue(ConcurrentLinkedQueue<Task> taskQueue) {
		this.taskQueue = taskQueue;
	}

	public void setResultMap(ConcurrentHashMap<String, Object> resultMap) {
		this.resultMap = resultMap;
	}

	@Override
	public void run() {
		while(true) {
			Task task = this.taskQueue.poll();
			if(task == null) break;
			try {
				Object result = handle(task);
				this.resultMap.put(Integer.toString(task.getId()), result);
			} catch (Exception e) {
				e.printStackTrace();
			}
		}
	}
 
	private  Random r = new Random();
	
	//实际做每一个工作!
	private Object handle(Task task) throws Exception {
		//每一个任务处理的时间是:
		//Thread.sleep(200 * r.nextInt(10));
		Thread.sleep(200);
		int ret = task.getCount();
		return ret;
	}

}

public class Task {

	private int id;
	private int count;
	
	public Task() {
	}

	public Task(int id, int count) {
		this.id = id;
		this.count = count;
	}
	public int getId() {
		return id;
	}
	public void setId(int id) {
		this.id = id;
	}
	public int getCount() {
		return count;
	}
	public void setCount(int count) {
		this.count = count;
	}
	
}
public class Main {

	
	public static void main(String[] args) {
		
		System.err.println("线程数:" + Runtime.getRuntime().availableProcessors());
		Master master = new Master(new Worker(), Runtime.getRuntime().availableProcessors());
		
		Random r = new Random();
		
		for(int i = 0; i < 100; i ++) {
			Task t = new Task(i, r.nextInt(1000));
			master.submit(t);
		}
		
		master.execute();
		
		long start = System.currentTimeMillis();
		
		//CountDownLatch
		
		while(true) {
			if(master.isComplete()) {
				long end = System.currentTimeMillis();
				int result = master.getResult();
				System.err.println("最终执行结果: " + result + ", 总耗时: " + (end - start));
				break;
			}
		}
		
	}
}

// 运行结果:
线程数:8
最终执行结果: 56007, 总耗时: 2613

Semaphore信号量与限流策略

Semaphore信号量非常适合高并发访问限制

相关概念:

  • PV(page view) 网站的总访问量,页面浏览量或点击量,用户每刷新一次就会被记录一次。

  • UV(unique Visitor) 访问网站的一台电脑客户端为一个访客。一般来讲时间上以00:00-24:00之内相同ip的客户端只记录。

  • QPS(query per second) 即每秒查询数,qps很大程度上代表了系统业务上的繁忙程度,每次请求的背后,可能对应着多次磁盘I/O,多次网络请求,多个cpu时间片等。我们通过qps可以非常直观的了解当前系统业务情况,一旦当前qps超过所设定的预警阀值,可以考虑增加机器对集群扩容,以免压力过大导致宕机,可以根据前期的压力测试得到估值,在结合后期综合运维情况,估算出阀值。

  • RT(response time) 请求的响应时间,这个指标非常关键,直接说明前端用户的体验,任何系统设计师都想降低rt时间。当然还涉及cpu、内存、网络、磁盘等情况,更细节的问题很多,如select、update、delete/ps等数据库层面的统计。

  • 容量评估:一般来说通过开发、运维、测试、以及业务等相关人员,综合出系统的一系列阀值,然后我们根据关键阀值如qps、rt等,对系统进行有效的变更。

    一般来讲,我们进行多轮压力测试以后,可以对系统进行峰值评估,采用所谓的80/20原则,即80%的访问请求将在20%的时间内达到。这样我们可以根据系统对应的PV计算出峰值qps。
    峰值qps= (总PV × 80%)/ (60 × 60 × 24 × 20%)
    然后在将总的峰值qps除以单台机器所能承受的最高的qps值,就是所需要机器的数量:

    机器数 = 总的峰值qps / 压测得出的单机极限qps

    public class UseSemaphore {
    	
    	public static void main(String[] args) {
    		
    		ExecutorService executorService = Executors.newFixedThreadPool(20);
    		
            // 最多只能并发执行5个线程
    		Semaphore semaphore = new Semaphore(5);
    		
    		for(int index = 1; index <= 20; index ++){
    			final int token = index;
    			
    			Runnable run = new Runnable() {
    				@Override
    				public void run() {
    					try {
    						semaphore.acquire();
    						//进行相关的业务操作
    						System.err.println("获得许可,执行操作..." + token);
    					    long sleepTime = (long)(Math.random() * 10000);
    						Thread.sleep(sleepTime);
    					} catch (InterruptedException e) {
    						e.printStackTrace();
    					} finally {
    						semaphore.release();
    					}
    				}
    			};
    			
    			executorService.execute(run);
    		}
    		System.err.println("queue length: " + semaphore.getQueueLength());
    		executorService.shutdown();
    
    	}
    }
    
    // 运行结果
    获得许可,执行操作...1
    获得许可,执行操作...5
    获得许可,执行操作...3
    获得许可,执行操作...4
    获得许可,执行操作...2
    queue length: 13	//达到了限制,阻塞了13个线程
    获得许可,执行操作...6
    获得许可,执行操作...7
    获得许可,执行操作...8
    获得许可,执行操作...10
    获得许可,执行操作...11
    获得许可,执行操作...12
    获得许可,执行操作...9
    获得许可,执行操作...13
    获得许可,执行操作...14
    获得许可,执行操作...15
    获得许可,执行操作...16
    获得许可,执行操作...17
    获得许可,执行操作...18
    获得许可,执行操作...19
    获得许可,执行操作...20
    
    
    
  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值