• 【面试专栏】Java常用并发工具类


    1. 简介

      Java中常见的四种并发工具类:

    • CountDownLatch
    • CyclicBarrier
    • Semaphore
    • Exchanger

    2. 主线程等待子线程之CountDownLatch

    • 原理
        CountDownLatch允许一个或多个线程等待其他一组线程完成操作,再继续执行。
        CountDownLatch的构造函数传入一个int类型的参数N作为计数器,这个参数N作为需要等待的线程的数量。当调用CountDownLatch的countDown方法时N减1,CountDownLatch的await方法会阻塞当前线程(即主线程在闭锁上等待),直到N减为零,此时闭锁的主线程继续执行任务。
    • 示例图
    • 使用场景
        超市的某一种商品,当促销时往往会有多人从货架上取出商品,只到该商品销售完后,促销结束。
    import java.util.concurrent.CountDownLatch;
    
    public class CountDownLatchDemo {
    
    	public static void main(String[] args) throws InterruptedException {
    		CountDownLatch countDownLatch = new CountDownLatch(5);
    		System.out.println("商品货源充足,促销开始!");
    		for (int i = 1; i <= 5; i++) {
    			new Thread(() -> {
    				System.out.println("商品编号[" + Thread.currentThread().getName() + "]已售出");
    				countDownLatch.countDown();
    			}, String.valueOf(i)).start();
    		}
    
    		countDownLatch.await();
    		System.out.println("商品已售罄,促销结束!");
    	}
    
    }
    
    商品货源充足,促销开始!
    商品编号[2]已售出
    商品编号[3]已售出
    商品编号[1]已售出
    商品编号[4]已售出
    商品编号[5]已售出
    商品已售罄,促销结束!
    

    3. 同步到达屏障之CyclicBarrier

    • 原理
        让一组线程在到达一处屏障(即同步点)时被阻塞,只到最后一个线程达到屏障点,所有被阻塞的线程被释放继续执行。
        CyclicBarrier的构造函数传入一个int类型的参数N作为计数器,这个参数N作为需要等待的线程的数量。每个线程调用CyclicBarrier的await方法使自己被阻塞,当N个线程调用了await方法后,所有线程停止等待,继续执行。
    • 与CountDownLatch对比
        相同点:都需要等待线程然后才能继续执行。
        不相同点:可以循环使用。假如,将CyclicBarrier的计数器设置为10,当等待到10个线程后,计数器归0,然后可以继续等待10个线程。
    • 示例图
    • 使用场景
        班主任收集学生信息,当全班同学的信息收集完成后,才可以交给教务处。
    import java.util.concurrent.BrokenBarrierException;
    import java.util.concurrent.CyclicBarrier;
    
    public class CyclicBarrierDemo {
    
    	public static void main(String[] args) {
    		CyclicBarrier cyclicBarrier = new CyclicBarrier(5, () -> {
    			System.out.println("收集信息完成,转交教务处!");
    		});
    
    		for (int i = 1; i <= 5; i++) {
    			new Thread(() -> {
    				System.out.println("收集到学生[" + Thread.currentThread().getName() + "]的信息");
    				try {
    					cyclicBarrier.await();
    				} catch (InterruptedException | BrokenBarrierException e) {
    					e.printStackTrace();
    				}
    			}, String.valueOf(i)).start();
    		}
    
    	}
    
    }
    
    收集到学生[1]的信息
    收集到学生[5]的信息
    收集到学生[3]的信息
    收集到学生[2]的信息
    收集到学生[4]的信息
    收集信息完成,转交教务处!
    

    4. 控制并发线程数之Semaphore

    • 原理
        JDK1.5就提供了Semaphore来统计一定数量的线程获得许可而对公共资源的访问。
        Semaphore的构造函数入一个int类型的参数N作为可颁发的许可数量,线程通过调用acquire方法获取许可,只有获得许可的线程才可以执行任务,执行完成后调用release方法归还许可。当线程获取许可失败时,表示已经到达了最大的并发线程数,即许可已经颁发完毕,需要等待有线程归还后再次尝试获取。
    • 示例图
    • 使用场景
        餐厅只有两个位置可以就餐,每个人需要就餐时,需要先看位置是否为空。为空时可直接就餐,不为空时则等待。
    import java.util.concurrent.Semaphore;
    import java.util.concurrent.TimeUnit;
    
    public class SemaphoreDemo {
    
    	public static void main(String[] args) {
    		Semaphore semaphore = new Semaphore(2);
    
    		for (int i = 1; i <= 5; i++) {
    			new Thread(() -> {
    				try {
    					semaphore.acquire();
    					TimeUnit.SECONDS.sleep(3);
    
    					System.out.println("顾客[" + Thread.currentThread().getName() + "]就餐,时间为3秒钟");
    				} catch (InterruptedException e) {
    					e.printStackTrace();
    				} finally {
    					semaphore.release();
    				}
    			}, String.valueOf(i)).start();
    		}
    	}
    
    }
    
    顾客[1]就餐,时间为3秒钟
    顾客[2]就餐,时间为3秒钟
    顾客[4]就餐,时间为3秒钟
    顾客[3]就餐,时间为3秒钟
    顾客[5]就餐,时间为3秒钟
    

    5. 交换数据之Exchanger

    • 原理
        Exchanger是一个线程间协作的工具类,用于线程间的数据交换。线程A调用exchange方法到达同步点,等待数据交换,线程B也调用exchange方法到达同步点,此时线程A和线程B进行数据交换。
    • 示例图
    • 使用场景
        幼儿园进行玩具分享会,当两个小朋友都原因交换玩具时,进行交换玩具。
    import java.util.concurrent.Exchanger;
    
    public class ExchangerDemo {
    
    	public static void main(String[] args) {
    		Exchanger<String> exchanger = new Exchanger<>();
    
    		for (int i = 1; i <= 6; i++) {
    			new Thread(() -> {
    				final String treadName = Thread.currentThread().getName();
    				try {
    					String data = "[物体" + treadName + "]";
    					System.out.println("小朋友[" + treadName + "]交换前拥有" + data);
    					data = exchanger.exchange(data);
    
    					System.err.println("小朋友[" + treadName + "]交换后拥有" + data);
    				} catch (InterruptedException e) {
    					e.printStackTrace();
    				}
    			}, String.valueOf(i)).start();
    		}
    	}
    
    }
    
    小朋友[1]交换前拥有[物体1]
    小朋友[3]交换前拥有[物体3]
    小朋友[6]交换前拥有[物体6]
    小朋友[4]交换前拥有[物体4]
    小朋友[2]交换前拥有[物体2]
    小朋友[5]交换前拥有[物体5]
    小朋友[3]交换后拥有[物体1]
    小朋友[1]交换后拥有[物体3]
    小朋友[4]交换后拥有[物体6]
    小朋友[6]交换后拥有[物体4]
    小朋友[5]交换后拥有[物体2]
    小朋友[2]交换后拥有[物体5]
    
  • 相关阅读:
    C# winform 使用FastReport.Net自动打印一维码条码和二维码的解决方法
    C# winform 使用rdlc打印小票其中包含动态显示多条形码的解决方法
    我学习的LIS系统业务
    C# DataTable DataSet DataRow 转实体类集合,实体类和实体类集合转成DataTable 扩展方法分享
    我的自动化设备上位机软件开发设计(一)
    打开操作系统数据执行保护,关闭操作系统数据执行保护
    visualstudio2019 的报表技术rdlc在windows10上出现乱码的问题解决方法
    我带旅游ERP管理系统开发的经历
    C# web程序,winform程序,控制台程序配置log4net,使用log4net
    freemodbus modbus TCP 学习笔记
  • 原文地址:https://www.cnblogs.com/cao-lei/p/14277053.html
Copyright © 2020-2023  润新知