一次删数据而认识的CountDownLatch和CyclicBarrier

公司之前有个任务,要求删除一张数据库表里面2018/2/1之前的数据。这张表里面存放的是车辆定位数据,一辆车每天能产生4000+条定位数据,所以整个表蛮大的,有65亿+条数据。而且还有要求:根据每个地区要统计出来这个地区删除了多少条数据。其中2月1号之前的有10亿多条。当然这是删除完之后才统计出来的。


一开始是这样做的:查询某个地区,时间在2018/2/1之前的数据,用Mongo游标hasNext()遍历,当遍历出的结果有500条时,用线程池删除。大概代码是这样的:

        while (mongoCursor.hasNext()) {
         i++;
            list.add(mongoCursor.next());
            //批量删除500条数据
            if (i >= 500) {
                pool.execute(new DeleteRunnable(list));
                i=0;
                list = new ArrayList<>();
            }
        }

程序运行时发现:mongoCursor.hasNext()方法时常"卡死",导致后面的语句无法执行,但又不抛出异常,导致一天删不了多少数据。原因到现在未知...


但是后面改成了这个方法也有问题:主线程每次查找1w条数据,分给线程池去做删除操作,线程每次删除500条数据。这种方法的问题是:主线程第1次查询出A数据后,线程池还没来得及删除,主线程第2次查询又把A数据查询出来了,交给线程池做删除操作。程序运行后发现:1w条数据,大约有一半数据已经不存在,导致删除失败...

查询1w条数据大概需要70s左右(好慢,而且还是根据索引查询...),删除500条数据大约需要10s左右(根据主键_id删除),所以我改成了每次查询1w条数据后,休眠10s...情况好多了...


前面讲了这么多还是为了引出CountDownLatch和CyclicBarrier,CountDownLatch是后面公司的同事告诉我的。他的提议是,用sleep休眠也只能粗略地估算删除的时间,主线程查询数据后,可以用CountDownLatch的await方法阻塞主线程,等待线程池中的线程都执行了删除方法后,再重新查询数据库。大概代码:

        //findDatas为查找出来的1w条数据
        int count = findDatas.size() % 500 == 0 ? (findDatas.size() / 500) : (findDatas.size() / 500 + 1);
        CountDownLatch countDownLatch = new CountDownLatch(count);
        List<String> deleteIDs = new ArrayList<>();
        for (int i = 0; i < findDatas.size(); i++) {
            deleteIDs.add(findDatas.get(i));

            //批量删除500条数据
            if (deleteIDs.size() >= 500 || i == findDatas.size() - 1) {
                pool.execute(new DeleteRunnable(deleteIDs, countDownLatch));
                deleteIDs = new ArrayList<>();
            }
        }

        System.out.println("任务分配完成,主线程等待任务完成");
        try {
            //主线程阻塞等待
            countDownLatch.await();
            continue;
        } catch (InterruptedException e) {
            e.printStackTrace();
        }

后来去网上找了一下CountDownLatch这个类,发现很多大佬的博客都是把它和CyclicBarrier放在一起讲,那我也就拜读了几篇大佬的文章,自己做下总结。


CountDownLatch

public CountDownLatch(int count):构造方法,声明有count把锁。

public void countDown():锁的个数减一。

public void await():此方法会阻塞当前线程,直到锁的个数减为0。

public boolean await(long timeout, TimeUnit unit):此方法会阻塞当前线程,直到锁的个数减为0 或者 超时。(对比Future类,此方法超时不会抛出异常)

public long getCount():当前还剩多少把锁。(返回值 >=0,不会出现为负数的情况)


CyclicBarrier

public CyclicBarrier(int parties):构造方法,声明CyclicBarrier屏障要拦截的数量parties。

public CyclicBarrier(int parties, Runnable barrierAction):构造方法,声明屏障要拦截的数量parties,并且在拦截完parties数量后,执行barrierAction方法。

public int getParties():返回障要拦截的数量,也就是构造方法中的parties的值。

public int await():此方法会阻塞当前线程,直到到达屏障的数量为parties。

public int await(long timeout, TimeUnit unit):此方法会阻塞当前线程,直到到达屏障的数量为parties 或者 超时。(超时会抛出TimeoutException,导致其他线程调用await方法会抛出BrokenBarrierException)

public boolean isBroken():当前屏障是否处于broken状态。

public int getNumberWaiting():返回已经到达屏障被阻塞的数量。

public void reset():重置屏障为初始状态,如果有线程正在等待障碍,会抛出BrokenBarrierException。处于broken状态的屏障重置会很复杂,所以如果当前屏障处于broken状态,建议创建一个新的CyclicBarrier对象会更好。


CyclicBarrier和CountDownLatch对比

当然是相比于CountDownLatch只能用一次,CyclicBarrier能复用啦....

CyclicBarrierTest类中,进行了两次比赛,用的都是同一个CyclicBarrier对象,代码:

public class MyRunnable1 implements Runnable {
    CyclicBarrier cyclicBarrier;

    public MyRunnable1(CyclicBarrier cyclicBarrier) {
        this.cyclicBarrier = cyclicBarrier;
    }

    @Override
    public void run() {
        System.out.println(Thread.currentThread().getName() + String.format(":我前面有%s人已经准备好了。", cyclicBarrier.getNumberWaiting()));
        try {
            cyclicBarrier.await();
        } catch (InterruptedException e) {
            e.printStackTrace();
        } catch (BrokenBarrierException e) {
            e.printStackTrace();
        }
    }
}
public class CyclicBarrierTest {
    public static void main(String[] args) throws InterruptedException {
        CyclicBarrier cyclicBarrier = new CyclicBarrier(5, new Runnable() {
            @Override
            public void run() {
                System.out.println("所有人到齐,比赛开始.....");
                System.out.println("恭喜rng...");
            }
        });

        System.out.println(String.format("五排需要几个人?\t答:%s人", cyclicBarrier.getParties()));

        for (int i = 0; i < 5; i++) {
            TimeUnit.SECONDS.sleep(1);
            new Thread(new MyRunnable1(cyclicBarrier)).start();
        }

        try {
            TimeUnit.SECONDS.sleep(5);
        } catch (InterruptedException e) {
            e.printStackTrace();
        }
        System.out.println("finished...");

        System.out.println("\n=============第二次比赛开始...===================\n");
        for (int i = 0; i < 5; i++) {
            TimeUnit.SECONDS.sleep(1);
            new Thread(new MyRunnable1(cyclicBarrier)).start();
        }

        try {
            TimeUnit.SECONDS.sleep(5);
        } catch (InterruptedException e) {
            e.printStackTrace();
        }
        System.out.println("finished...");

    }
}
 

结果:

 


 

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值