java.util.concurrent.*下的常见类你了解多少?

Posted 你这家伙

tags:

篇首语:本文由小常识网(cha138.com)小编为大家整理,主要介绍了java.util.concurrent.*下的常见类你了解多少?相关的知识,希望对你有一定的参考价值。

java.util.concurrent.*是标准库提供的一组工具类,帮我们简化并发编程

1.concurrent下的locks锁

1.1.locks锁下常见的锁

![在这里插入图片描述](https://img-blog.csdnimg.cn/b9bd751d7875497ba5693a76c5da8025.png?x-oss-process=image/watermark,type_ZmFuZ3poZW5naGVpdGk,shadow_10,text_aHR0cHM6Ly9ibG9nLmNzZG4ubmV0L3draDE4ODkxODQzMTY1,size_16,color_FFFFFF,t_70)

1.2.lock和sychronized的之间的区别

  1. lock锁的加锁和解锁操作是分开的
  2. lock体系是Java语言层面实现的,sychronized是JVM实现的
  3. lock提供了一些更灵活的方法(如trylock,如果尝试获取失败就直接放弃,或等待指定等待时间再放弃)
    大部分情况下还是在使用sychronized,特殊情况下载使用其他锁,如果是自定制的锁,那么就需要lock系列

2.concurrent.atomic下的原子类

如:AtomicInterger

public class Test{
    public static AtomicInteger num = new AtomicInteger(0);

    public static void main(String[] args) {
        //1.获取值
        int ret = num.get();
        //2.修改值
        num.set(100);
        //3.进行自增/自减
        int num1 = num.getAndIncrement(); // n++
        int num2 = num.incrementAndGet(); // ++n
    }
}

3.concurrent下的Callable

通常Callable/Future/FutureTask是搭配使用的,就是把线程封装成了能够返回结果的样子,我们之前创建的Thread都是没有返回值的,如果想多线程进行一些计算,就需要自己来汇总

上代码
如:求1—100的和


/**
 * Callable和Runnable很像,都是来描述一个具体的“任务”,
 * 但是Runnable是没有返回值的,而Callable是有返回值的
 */
public class Test{
    public static void main(String[] args) throws ExecutionException, InterruptedException {
        //1.创建Callable
        Callable<Integer> callable = new Callable<Integer>() {
            @Override
            public Integer call() throws Exception {
                //实现call方法,来求值
                int num = 0;
                for (int i = 0; i <= 100; i++) {
                    num += i;
                }
                return num;
            }
        };
        //2.创建一个FutureTask
        FutureTask<Integer> futureTask = new FutureTask<>(callable);
        //3.创建一个个线程
        Thread thread = new Thread(futureTask);
        thread.start();
        //4.获取线程执行结果,get方法会发生阻塞,一直到call方法执行完毕,get方法才会返回
        Integer integer = futureTask.get();
        System.out.println(integer);
    }
}

如果创建的线程不需要返回值,就可以使用Runnable,如果需要返回值,就可以使用Callable(搭配Future或者FutureTask使用)。因为Thread里面没有直接获取结果的方法

4.Semaphore(信号量)

相当于一个“计数器”,表示当前可用资源的个数
每次有人申请一个资源,信号量的值就+1(P操作)
每次有人释放一个资源,信号量的值就 -1(V操作)
当信号量的值为0的时候,那么此时就有两种策略:1.等待直到有人释放资源;2.不再等待,去找其他的资源

多线程在进行·PV操作的时候,此时线程都是安全的(信号量计数加减都是原子的)

信号量中有一个特殊的情况,当信号量的值只有0和1两种取值的时候,这种成为“二元信号量”,所谓的“二元信号量”本质上就是一个互斥锁(synchronized就可以视为一个“二元信号量”,同一时刻一个线程能获取到锁,相当于资源为1)

比如:停车场就会有一个计数器,门口就会有一个显示牌,上面显示还剩下多少车位,每当有车进入停车场,计数就-1,每当有人开出停车场,计数牌就+1,但是如果此时停车位为0的时候,在当有人进来,此时有两种情况:要么等待里面有人开出来,要么扭头就走,去找其他停车场

上代码

public class Test{
    public static void main(String[] args){
       //表示可用资源的个数
        Semaphore semaphore = new Semaphore(5);
        Runnable runnable = new Runnable() {
            @Override
            public void run() {
                try {
                    long threadId = Thread.currentThread().getId();
                System.out.println("申请资源:"+threadId);
                //P 操作申请资源,计数器就-1
                    semaphore.acquire();
                    System.out.println("我获取到资源了:"+threadId);
                    Thread.sleep(1000);
                    System.out.println("我释放资源了:"+threadId);
                    //释放资源,计数器就+1
                    semaphore.release();
                } catch (InterruptedException e) {
                    e.printStackTrace();
                }
            }
        };
        //创建20个线程去获取这5个资源
        for (int i = 0; i < 20; i++) {
            Thread thread = new Thread(runnable);
            thread.start();
        }
    }
}

执行结果

在有多个共享资源的时候,就可以使用信号量(比如实现一个线程安全的阻塞队列)

5.线程池ThreadPoolExecutor

线程池系列相关的类还有Executors,ExecutorService
线程池存在的意义,就是为了频繁创建/销毁线程的开销

面试常考:线程池构造方法里面的参数含义

corePoolSize:核心线程数大小,当线程数<corePoolSize ,会创建线程执行runnable

maximumPoolSize: 最大线程数, 当线程数 >= corePoolSize的时候,会把runnable放入workQueue中

keepAliveTime:保持存活时间,当线程数大于corePoolSize的空闲线程能保持的最大时间。
unit :时间单位
workQueue: 保存任务的阻塞队列
threadFactory :创建线程的工厂
handler :拒绝策略

任务执行顺序:

1、当线程数小于corePoolSize时,创建线程执行任务。

2、当线程数大于等于corePoolSize并且workQueue没有满时,放入workQueue中

3、线程数大于等于corePoolSize并且当workQueue满时,新任务新建线程运行,线程总数要小于maximumPoolSize

4、当线程总数等于maximumPoolSize并且workQueue满了的时候执行handler的rejectedExecution。也就是拒绝策略。

拒绝策略
1、ThreadPoolExecutor.AbortPolicy() 直接抛出异常RejectedExecutionException

2、ThreadPoolExecutor.CallerRunsPolicy() 直接调用run方法并且阻塞执行

3、ThreadPoolExecutor.DiscardPolicy() 直接丢弃后来的任务

4、ThreadPoolExecutor.DiscardOldestPolicy() 丢弃在队列中队首的任务

6.CountDownLatch

好像跑步比赛,10个选手依次就位,哨声响才同时出发;所有选手都通过终点,才能公布成绩。

public class Test{
    public static void main(String[] args) throws InterruptedException {
        //表示有10个选手
        CountDownLatch countDownLatch = new CountDownLatch(10);
        Runnable runnable = new Runnable() {
            @Override
            public void run() {
                try {
                    Thread.sleep(1000);
                    //某个选手到达终点
                    countDownLatch.countDown();
                } catch (InterruptedException e) {
                    e.printStackTrace();
                }
            }
        };
        for (int i = 0; i < 10; i++) {
            new Thread(runnable).start();
        }
        //必须要等到10人全部到达终点,await才会返回,否则就会阻塞
        countDownLatch.await();
        System.out.println("比赛结束");
    }
}

还比如:下载一个1G大小的文件,就可以分成10个线程,每个线程负责100MB,等待10个线程下载完,这个文件才算下载完成

面试常考

  1. Thread,Runnable,Callable之间的区别和联系
  2. 线程同步的方式有哪些?
  3. 你平时用过哪些线程同步的方式?
  4. 为什么有了 synchronized 还需要 juc (java.util.concurrent)下的 lock?
  5. AtomicInteger 的实现原理是什么?
  6. 信号量听说过么?之前都用在过哪些场景下?
  7. 说一下并发包下有哪些并发类,concurrentHashmap,unsafe,原子类,都分别讲一下怎么支持并发 的?

以上是关于java.util.concurrent.*下的常见类你了解多少?的主要内容,如果未能解决你的问题,请参考以下文章

JUC包(java.util.concurrent)下的常用子类

Java源码之 java.util.concurrent 学习笔记01

Java多线程20:多线程下的其他组件之CountDownLatchSemaphoreExchanger

Thinking in Java---Concurrent包下的新构件学习+赛马游戏仿真

java 多线程 27 :多线程组件之CountDownLatch

java.util.concurrent.CopyOnWriteArrayList