多线程(二):JUC理解(持续更新中)

JUC包下各种类的用法

  • CountDownLatch
    适用场景举例:一个线程等待其他线程完成任务后再继续任务
    门栓机制,维护一个变量,每次调用门栓的countdown()方法变量递减,直到减至0,线程从等待中释放。
public class CountdownLatchExample {
    /**
     * @param args
     * @throws Exception
     *
     * CountDownLatch用来控制一个线程等待多个线程。
     * 维护了一个计数器 cnt,每次调用 countDown() 方法会让计数器的值减 1,减到 0 的时候,那些因为调用 await() 方 法而在等待的线程就会被唤醒。
     *
     * 下面这段代码:
     * 主线程开启了一个线程thread,用来开启10个线程打印"run 1 time",此时阻塞主线程
     * 每打印一次CountDownLatch维护的count - 1
     * 当count降至0时,主线程被唤醒并打印"unlock"
     */
    public static void main(String[] args) throws Exception{
        CountDownLatch countDownLatch = new CountDownLatch(10);

        Thread thread = new Thread(new Runnable() {
            @Override
            public void run() {
                for(int i = 0; i < 10;i++) {
                    Thread t = new Thread(new Runnable() {
                        @Override
                        public void run() {
                            System.out.println("Thread ID :" + Thread.currentThread().getId() + " run 1 times");
                            countDownLatch.countDown();
                        }
                    });
                    t.start();
                }
            }
        });
        thread.start();
        System.out.println("main is locked");
        countDownLatch.await();
        System.out.println(Thread.currentThread().getName() + " unlock");

    }
}
  • CyclicBarrier
    适用场景举例:多个线程等待多个线程完成任务一起继续任务
    和CountDownLatch类似,可以调用reset()方法重新使用
public class CyclicBarrierExample {

    /**
     *
     * @param args
     * 用来控制多个线程互相等待,只有当多个线程都到达时,这些线程才会继续执行。
     * 和 CountdownLatch 相似,都是通过维护计数器来实现的。线程执行 await() 方法之后计数器会减 1,并进行等待,
     * 直到计数器为 0,所有调用 await() 方法而在等待的线程才能继续执行。
     * CyclicBarrier 和 CountdownLatch 的一个区别是,CyclicBarrier 的计数器通过调用 reset() 方法可以循环使用,所以
     * 它才叫做循环屏障。
     *
     * CyclicBarrier 有两个构造函数,其中 parties 指示计数器的初始值,barrierAction 在所有线程都到达屏障的时候会执行一次。
     *
     */
    public static void main(String[] args){
        CyclicBarrier cyclicBarrier = new CyclicBarrier(10);

        for(int i = 0; i < 10;i++) {
            Thread t = new Thread(new Runnable() {
                @Override
                public void run() {
                    System.out.println("i am the "+ Thread.currentThread().getId() + ", I have to wait "+cyclicBarrier.getParties()+" guys then i can go");
                    try {
                        cyclicBarrier.await();
                        System.out.println("i am the "+ Thread.currentThread().getId() + ", I can go");
                    } catch (InterruptedException e) {
                        e.printStackTrace();
                    } catch (BrokenBarrierException e) {
                        e.printStackTrace();
                    }
                }
            });
            t.start();
        }
    }
}
  • Semaphore
    适用场景举例:类似于操作系统中的信号量,可以控制对互斥资源的访问线程数。
    维护一个信号量,线程纷纷调用acquire()获取信号量,当信号量为0时,其与线程等待,直到其他线程调用release()方法后可以重新获得信号量
public class SemaphoreExample {

    /**
     *
     * @param args
     *
     * Semaphore 类似于操作系统中的信号量,可以控制对互斥资源的访问线程数。
     * 以下代码模拟了对某个服务的并发请求,每次只能有 3 个客户端同时访问,请求总数为 10。
     * acquire() -> 获得许可的线程才能进行下面的操作
     * release() -> 释放许可
     * Semaphore没有维护真正的许可证,只是维护了一个int变量,当它为0的时候,线程就等待了,直到其他线程释放将变量增加后才唤醒。
     */
    public static void main(String[] args) {
        Semaphore semaphore = new Semaphore(3);
        for (int i = 0;i < 10;i++) {
            Thread thread = new Thread(()->{
                try {
                    semaphore.acquire();
                    System.out.println("Thread id " +Thread.currentThread().getId() + " takes one, left num is " + semaphore.availablePermits());

                    Thread.currentThread().sleep(1000);
                    semaphore.release();
                } catch (InterruptedException e) {
                    e.printStackTrace();
                }
            });
            thread.start();
        }
    }
}

  • FutureTask
    适用场景举例:用作一个任务线程并可以返回结果。
    其他线程可以通过.get()操作获得任务执行结果。
public class FutureTaskExample {


    /**
     *
     * @param args
     * @throws InterruptedException
     * @throws ExecutionException
     * 在介绍 Callable 时我们知道它可以有返回值,返回值通过 Future 进行封装。
     * FutureTask 实现了 RunnableFuture 接 口,该接口继承自 Runnable 和 Future 接口,这使得 FutureTask 既可以当做一个任务执行,也可以有返回值。
     * FutureTask 可用于异步获取执行结果或取消执行任务的场景。
     * 当一个计算任务需要执行很长时间,那么就可以用 FutureTask 来封装这个任务,主线程在完成自己的任务之后再去获取结果。
     *
     *
     * 下面这段代码就是主线程启动了一个计算线程后自己做自己的任务(这里是睡1秒)
     * 计算线程在后台计算,并得出结果
     * 主线程做完自己的事后去拿计算线程的结果
     */
    public static void main(String[] args) throws InterruptedException, ExecutionException {
        FutureTask computeThread = new FutureTask(new Callable() {
            @Override
            public Integer call() throws Exception {
                int res = 0;

                for(int i = 0; i < 100;i++) {
                    Thread.sleep(10);
                    res += i;
                }
                return Integer.valueOf(res);
            }
        });
        computeThread.run();
        System.out.println("one task is running");
        Thread.sleep(1000);
        System.out.println("the result is :" + computeThread.get());
    }
}

  • blockingQueue
    适用场景举例:生产者消费者问题
    是个接口,提供了一个阻塞队列,当大于capacity值时,队列将不能put;相反capacity等于0时,队列不能take。
/**
 *
 * java.util.concurrent.BlockingQueue 接口有以下阻塞队列的实现:
 * FIFO 队列 :LinkedBlockingQueue、ArrayBlockingQueue(固定长度) 优先级队列 :PriorityBlockingQueue
 * 提供了阻塞的 take() 和 put() 方法:如果队列为空 take() 将阻塞,直到队列中有内容;如果队列为满 put() 将阻塞, 直到队列有空闲位置。
 * 使用 BlockingQueue 实现生产者消费者问题
 */
public class BlockingQueueExample {


    private static ArrayBlockingQueue<String> queue = new ArrayBlockingQueue<>(5);


    private static class producter extends Thread{
        @Override
        public void run() {
            while(true) {
                try {
                    queue.put("product");
                    System.out.println("product。。。");
                } catch (InterruptedException e) {
                    e.printStackTrace();
                }
            }
        }
    }
    private static class consumer extends Thread {
        @Override
        public void run() {
            while(true) {
                try {
                    Thread.sleep(2000);
                    String product = queue.take();
                    System.out.println("consumer。。。");
                }catch (InterruptedException e) {
                    e.printStackTrace();
                }
            }
        }
    }
    public static void main(String[] args) throws InterruptedException {
        producter producter = new producter();
        consumer consumer = new consumer();
        producter.start();
        Thread.sleep(5000);
        consumer.start();
    }
}
  • ForkJoin
    适用方法举例:将一个大任务切分成小任务根据CPU核数并行执行,最后以Future的方式返回结果
    ForkJoin任务需要继承RecursiveTask,然后自己写任务切割条件和计算方法,最后由ForkJoinPool负责提交
/**
 *
 * 和MR类似,就是将大任务切割成多个小任务,然后分配给多个线程去做,线程数取决于CPU核数。
 * 首先写一个ForkJoin任务继承RecursiveTask,然后编写计算方法。
 * 在compute()中,要将任务执行条件和切割条件写出来,例如本例中的if,else
 * fork是分叉的意思,个人理解为就是分解为子任务然后开始执行。
 * join个人理解就是将子任务的结果汇合。
 *
 * 一个job要用ForkJoinPool负责执行,所以先创建一个ForkJoinPool实例后执行ForkJoin任务,然后将结果拿到。
 */
public class ForkJoinExample extends RecursiveTask<Integer> {


    private final int THRESHOLD = 5;
    private int first;
    private int last;

    public ForkJoinExample(int first, int last) {
        this.first = first;
        this.last = last;
    }

    @Override
    protected Integer compute() {
        int result = 0;
        if(last - first <= THRESHOLD) {
            for(int i = first;i <= last;i++) {
                result += i;
            }
        }else {
            int mid = first + (last - first >> 1);
            ForkJoinExample left = new ForkJoinExample(first,mid);
            ForkJoinExample right = new ForkJoinExample(mid,last);
            left.fork();
            right.fork();
            result = left.join() + right.join();
        }
        return result;
    }

    public static void main(String[] args) throws ExecutionException, InterruptedException {
        ForkJoinExample f = new ForkJoinExample(1,10000);
        ForkJoinPool forkJoinPool = new ForkJoinPool();
        Future res = forkJoinPool.submit(f);
        System.out.println(res.get());
    }
}
发布了352 篇原创文章 · 获赞 73 · 访问量 1万+

猜你喜欢

转载自blog.csdn.net/weixin_43777983/article/details/105067928