Java并发JUC(四)

119 阅读4分钟

开启掘金成长之旅!这是我参与「掘金日新计划 · 12 月更文挑战」的第8天,点击查看活动详情
本文主要讲解Java并发中的CountDownLatch、CyclicBarrier、Semaphore和读写锁ReadWriteLock。

8. 常用的辅助类

CountDownLatch:减法计数器。里面的参数表示总数量。

await方法等待计数器归0,再往下执行;cutDown使计数减1。

每次有线程调用countDown数量减1,假设计数器变为0,counrDownLatch.await()就会被唤醒,继续执行!

下面看一个例子。

  • 【示例作用】可以理解教室内6个学生要离开教室,用CountDownLatch设置一个学生总数量,也就是6个。离开一个学生,计数就减1,等教室的6个学生都离开了,再将门关上。
  • 用法:1.CountDownLatch countDownLatch = new CountDownLatch(6);2.业务逻辑和计数减一,countDownLatch.countDown();3.等待计数归0继续向下执行`countDownLatch.await();相当于是阻塞线程。
public static void main(String[] args) throws InterruptedException {
    //总数是6,必须要执行任务的时候,再使用!
    CountDownLatch countDownLatch = new CountDownLatch(6);
    for (int i = 1; i <= 6; i++) {
        new Thread(()->{
            System.out.println(Thread.currentThread().getName() + "Go out");
            countDownLatch.countDown(); //计数-1
        },String.valueOf(i)).start();
    }
    countDownLatch.await();//等待计数器归0,然后再向下执行
    //倒计时操作
    System.out.println("Close Door");
}

CyclicBarrier加法计数器。两个功能:

1.单纯的加一计数2.计数加一,加到设定值后触发(runnable)Lambda表达式的内容。

await();等待计数器变成设定值,然后执行上面计数器里面那个runnable

【示例理解】:通过new CyclicBarrier初始化一个要计数到7之后再执行Runnable(集齐七颗龙珠再召唤神龙)。

public static void main(String[] args) {
    /**
     * 集齐7颗龙族召唤神龙
     */
    //召唤龙族的线程
    CyclicBarrier cyclicBarrier = new CyclicBarrier(7,()->{
        System.out.println("召唤神龙成功!");
    });//可以传一个参数(计数),也可以传一个计数一个线程两个参数。(计数完执行线程)
    for (int i = 1; i <= 7; i++) {
        //下面这个lambda怎么拿到上面循环的这个i
        final int temp = i;
        new Thread(()->{
            System.out.println(Thread.currentThread().getName() + "收集" + temp +"个龙珠");
            try {
                cyclicBarrier.await();//等待计数器变成7,然后执行上面计数器里面那个runnable
            } catch (InterruptedException e) {
                e.printStackTrace();
            } catch (BrokenBarrierException e) {
                e.printStackTrace();
            }
        }).start();
    }
}

Semaphore信号量:解决的类似于限流的问题。比如有n个槽位,槽位满了就会等待槽位被释放为止。

  • acquire获取,假设槽位已经满了,就会等待被释放为止。
  • release释放,会讲当前的信号量释放+1,然后唤醒等待的线程!
  • 作用:多个共享资源互斥的使用!并发限流,控制最大的线程数。

【停车位例子】:这个例子主要实现的功能:6辆车需要停车,但只有三个车位,三个车先停进去(acquire),然后停两秒后再出来(release)。

/**
 * Semaphore原理:
 *  acquire获取,假设槽位已经满了,就会等待被释放为止。
 *  release释放,会讲当前的信号量释放+1,然后唤醒等待的线程!
 *  作用:多个共享资源互斥的使用!并发限流,控制最大的线程数。
 */
//Semaphor信号量
public class SemaphoreDemo {
    public static void main(String[] args) {
        //线程数量:停车位!限流!
        Semaphore semaphore = new Semaphore(3);
        //6辆车
        for (int i = 1; i <= 6; i++) {
            new Thread(()->{
                //acquire得到
                try {
                    semaphore.acquire();
                    System.out.println(Thread.currentThread().getName() + "抢到车位");
                    TimeUnit.SECONDS.sleep(2);//停两秒钟
                    System.out.println(Thread.currentThread().getName() + "离开车位");
                } catch (InterruptedException e) {
                    e.printStackTrace();
                } finally {
                    semaphore.release();
                }
                //release释放
            }).start();
        }
    }
}

9. 读写锁

独占锁(写锁):一次只能被一个线程占有。

共享锁(读锁):多个线程同时占有。

对于普通无锁的读写缓存,会存在1写入a,中间插入2写入b,甚至会插入读取1,然后再是1写入完成,这种非常不安全。这里就可以加锁。普通lock锁ReentrantLock与读写锁ReadWriteLock控制的粒度不同,普通的Lock锁使用方式前面也讲过了。下面主要举读写锁的例子

  • 与普通lock使用方法一样。第一步:new ReentrantReadWriteLock(); 第二步加锁:readWriteLock.writeLock().lock();第三步在finally中释放锁:readWriteLock.writeLock().unlock();
  • 业务代码是在try语句中。
  • 读写锁有两种情况,读锁加锁和写锁加锁,分别是(写锁)readWriteLock.writeLock()和(读锁)readWriteLock.readLock().lock()。
private volatile Map<String,Object> map = new HashMap<>();
//读写锁,更加细粒度的控制
private ReadWriteLock readWriteLock= new ReentrantReadWriteLock();
//存(想要在写的时候只有一个线程写)
public void put(String key,Object value){
  readWriteLock.writeLock().lock();//写锁加锁
  try {
    System.out.println(Thread.currentThread().getName() + "写入"+ key);
    map.put(key,value);
    System.out.println(Thread.currentThread().getName() + "写入OK");
  } catch (Exception e) {
    e.printStackTrace();
  } finally {
    readWriteLock.writeLock().unlock();//写锁解锁
  }
​
}
public void get(String key){
    readWriteLock.readLock().lock();//读锁加锁
    try {
        System.out.println(Thread.currentThread().getName() + "读取"+ key);
        Object o = map.get(key);
        System.out.println(Thread.currentThread().getName() + "读取OK");
    } catch (Exception e){
        e.printStackTrace();
    } finally {
        readWriteLock.readLock().unlock();//读锁解锁
    }
​
}