并发编程学习---常用工具类介绍

1. 目录

  • ThreadLocal
  • CountDownLatch
  • CyclicBarrier
  • Semaphore
  • Exchanger
  • AtomicInteger

2. ThreadLocal

2.1 介绍:

即线程变量,是一个以ThreadLocal对象为键、任意对象为值的存储结构。这
个结构被附带在线程上,也就是说一个线程可以根据一个ThreadLocal对象查询到绑定在这个线程上的一个值

2.2 应用场景

同一个线程下共享同一个值,可以获取、修改和删除这个值,是线程安全的操作

2.3 常用方法

//获取ThreadLocal在当前线程中保存的变量
public T get()
//设置ThreadLocal在当前线程中保存的变量
public void set(T value) 
//删除ThreadLocal在当前线程中保存的变量
public void remove()

2.4 实例

在web工程中运用AOP统计controller层的方法调用时间
@Aspect
@Component
public class TimeAspect {
    ThreadLocal<Long> timeThreadLocal = new ThreadLocal<Long>();

    @Pointcut("execution(public * com..*.controller.*Controller.*(..))")
    public void webLog() {
    }
    /**
     * @Before 在方法执行之前执行
     */
    @Before("webLog()")
    public void before(JoinPoint joinPoint) {
        //设置方法调用前时间
        timeThreadLocal.set(System.currentTimeMillis());
    }

    /**
     * @After在方法执行之后执行
     */
    @AfterReturning(value = "webLog()", returning = "returnData")
    public void doAfterReturning(JoinPoint joinPoint, Object returnData) {
        //获取本次请求日志实体
        long beginTime = timeThreadLocal.get();
        System.out.println(System.currentTimeMillis() - beginTime);
    }
}
如果使用静态变量存储timeThreadLocal,会被别的进程给修改,如果用普通变量存储,因为spring默认是单例的,也会被修改。

3. CountDownLatch

3.1 介绍

可以看成是一个计数器,其内部维护着一个count计数,只不过对这个计数器的操作都是原子操作,同时只能有一个线程去操作这个计数器,CountDownLatch通过构造函数传入一个初始计数值,调用者可以通过调用CounDownLatch对象的cutDown()方法,来使计数减1;如果调用对象上的await()方法,那么调用者就会一直阻塞在这里,直到别人通过cutDown方法,将计数减到0,才可以继续执行

3.2 应用场景

计数器功能,不能复位

3.3 常用方法

//总数减一
public void countDown()
//总数减count
public CountDownLatch(int count)
//线程阻塞,一直到总数为0才解除阻塞
public void await()
//等待某个时间后继续执行
public boolean await(long timeout, TimeUnit unit)
方法说明
countDown()总数减一
await()线程阻塞,一直到总数为0才解除阻塞

3.4 实例

说明:主程序启动时,启动了另外5个线程进行初始化数据,主线程必须等到所有的线程完成初始化后才能提示启动成功
public class CountDownLatchDemo {
    public static void main(String[] args) throws InterruptedException {
        System.out.println("开始初始化执行完毕 ");
        //5个线程需要初始化
        CountDownLatch countDownLatch = new CountDownLatch(5);
        //用于睡眠的秒数递增
        AtomicInteger atomicInteger = new AtomicInteger(0);
        for (int i = 0; i < 5; i++) {
            new Thread(() -> {
                try {
                    int sleepTime = atomicInteger.incrementAndGet();
                    TimeUnit.SECONDS.sleep(sleepTime);
                    System.out.println(String.format("睡眠%d秒的线程初始化完成", sleepTime));
                } catch (InterruptedException e) {
                    e.printStackTrace();
                }
                countDownLatch.countDown();  //递减
            }).start();
        }
        countDownLatch.await(); //阻塞
        System.out.println("初始化执行完毕 ");
    }
}

4. CyclicBarrier

4.1 介绍

字面意思回环栅栏,通过它可以实现让一组线程等待至某个状态之后再全部同时执行。叫做回环是因为当所有等待线程都被释放以后,CyclicBarrier可以被重用。我们暂且把这个状态就叫做barrier,当调用await()方法之后,线程就处于barrier了

4.2 应用场景

等待一组线程统一到某一个状态后,再全部同时执行

4.3 常用方法

构造方法:

//参数parties指让多少个线程或者任务等待至barrier状态;
//参数barrierAction为当这些线程都达到barrier状态时会执行的内容
public CyclicBarrier(int parties, Runnable barrierAction) {
}
 
public CyclicBarrier(int parties) {
}

普通方法:

//用来挂起当前线程,直至所有线程都到达barrier状态再同时执行后续任务
public int await()
//超时时间,如果该时间后还没有线程达到barrier状态就直接让到达barrier状态的线程执行后续任务
public int await(long timeout, TimeUnit unit)

4.4 实例

**说明:**当4个线程都完成数据写入的时候,再一起执行后续操作。

public class CyclicBarrierTest {
    public static void main(String[] args) {
        int N = 4;
        CyclicBarrier barrier  = new CyclicBarrier(N);
        for(int i=0;i<N;i++){
            new Writer(barrier).start();
        }
    }
    static class Writer extends Thread{
        private CyclicBarrier cyclicBarrier;
        public Writer(CyclicBarrier cyclicBarrier) {
            this.cyclicBarrier = cyclicBarrier;
        }
        @Override
        public void run() {
            System.out.println("线程"+Thread.currentThread().getName()+"正在写入数据...");
            try {
                Thread.sleep(5000);      //以睡眠来模拟写入数据操作
                System.out.println("线程"+Thread.currentThread().getName()+"写入数据完毕,等待其他线程写入完毕");
                cyclicBarrier.await();
            } catch (InterruptedException e) {
                e.printStackTrace();
            }catch(BrokenBarrierException e){
                e.printStackTrace();
            }
            System.out.println("所有线程写入完毕,继续处理其他任务...");
        }
    }
}

5. Semaphore

5.1 介绍

翻译成字面意思为 信号量,Semaphore可以控同时访问的线程个数,通过 acquire() 获取一个许可,如果没有就等待,而 release() 释放一个许可。

5.2 应用场景

并发线程数阈值的控制

5.3 常用方法

构造方法:

//参数permits表示许可数目,即同时可以允许多少线程进行访问
public Semaphore(int permits) 
//参数fair表示是否是公平的,即等待时间越久的越先获取许可
public Semaphore(int permits, boolean fair)

普通方法:

//获取一个许可
public void acquire() throws InterruptedException {  }     
 //获取permits个许可
public void acquire(int permits) throws InterruptedException { }   
 //释放一个许可
public void release() { }         
//释放permits个许可
public void release(int permits) { }    

5.4 实例

说明:假设有10个人请求数据库连接,但是数据库只有5个连接资源,只能等有连接释放后等待的请求才能连接数据库
public static void main(String[] args) {
    int N = 8;            //请求数
    Semaphore semaphore = new Semaphore(5); //数据库连接数目
    for(int i=0;i<N;i++)
        new Connect(i,semaphore).start();
}

static class Connect extends Thread{
    private int num;
    private Semaphore semaphore;
    public Connect(int num, Semaphore semaphore){
        this.num = num;
        this.semaphore = semaphore;
    }

    @Override
    public void run() {
        try {
            semaphore.acquire();
            System.out.println("线程"+this.num+"占用一个数据库连接");
            Thread.sleep(2000);
            System.out.println("线程"+this.num+"释放出连接");
            semaphore.release();
        } catch (InterruptedException e) {
            e.printStackTrace();
        }
    }
}

6. Exchanger

6.1 介绍

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

6.2 应用场景

两个线程交互数据

6.3 常用方法

//交互数据,当数据没接收到时,线程阻塞
public V exchange(V x)
//避免阻塞,一定时间未收到数据继续往下执行
public V exchange(V x, long timeout, TimeUnit unit)

6.4 实例

说明:
我们需要将纸制银行流水通过人工的方式录入成电子银行流水,为了避免错误,采用AB岗两人进行录入,录入到Excel之后,系统需要加载这两个Excel,并对两个Excel数据进行校对,看看是否录入一致

public class ExchangerTest {
    private static final Exchanger<String> exgr = new Exchanger<String>();
    public static void main(String[] args) {
        new Thread(()->{
            try {
                String A = "银行流水A";// A录入银行流水数据
                System.out.println("A收到的数据是:" + exgr.exchange(A));
            } catch (InterruptedException e) {
            }
        }).start();
        new Thread(()->{
            try {
                String B = "银行流水B";// B录入银行流水数据
                String A = exgr.exchange(B);
                System.out.println("A和B数据是否一致:" + A.equals(B) + ",A录入的是:"
                        + A + ",B录入是:" + B);
            } catch (InterruptedException e) {
            }
        }).start();
    }
}

7. AtomicInteger

7.1 介绍

当程序更新一个变量时,如果多线程同时更新这个变量,可能得到期望之外的值,通常我们会使用synchronized来解决这个问题,synchronized会保证多线程不会同时更新变量i。
而Java从JDK 1.5开始提供了java.util.concurrent.atomic包(以下简称Atomic包),这个包中的原子操作类提供了一种用法简单、性能高效、线程安全地更新一个变量的方式。
因为变量的类型有很多种,所以在Atomic包里一共提供了13个类,属于4种类型的原子更新方式,分别是原子更新基本类型、原子更新数组、原子更新引用和原子更新属性(字段)。Atomic包里的类基本都是使用Unsafe实现的包装类

7.2 应用场景

线程安全的更新变量

7.3 常用方法

//获取当前值
public final int get()
//设置当前值
public final void set(int newValue) 
//设置并获取当前值
public final int getAndSet(int newValue)
//将原先为expect的值设置为update
public final boolean compareAndSet(int expect, int update)
//原先数据+1后返回+1后的值
public final int incrementAndGet()
//返回当前值后再+delta,返回结果还是未+delta的值
public final int getAndAdd(int delta)

7.4 实例

public static void main(String[] args) {
    //初始值为1
    AtomicInteger ai = new AtomicInteger(1);
    System.out.println(String.format("当前值为:%d",ai.get()));
    System.out.println(String.format("当前值+1后的值为:%d",ai.incrementAndGet()));
    //先返回后+5
    System.out.println(String.format("+5前的的值为:%d",ai.getAndAdd(5)));
    System.out.println(String.format("+5后的值为:%d",ai.get()));
    //当前值由7设置为10,当前值如何实际值不匹配,设置失败
    ai.compareAndSet(7,10);
    System.out.println(String.format("当前值为:%d",ai.get()));
}

  • 0
    点赞
  • 1
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值