JUC并发编程面试题突击复习


1.JUC是什么

JUC是java.util.concurrent.*三个包

2.java可以自己开启线程吗

不可以,因为Thread的底层源码最后使用了native调用c++的方法,所以其实不是java开启的线程,java运行在虚拟机上的,不可以直接操控硬件。

3.线程有几种状态

新生状态:NEW
运行状态:RUNABLE
阻塞:BLOCKED
等待:WAITTING
超时等待:TIMED_WAITTING
终止:TERMINATED

4.wait/sleep的区别

  1. 来自不同的类
    wait => Object
    sleep=> Thread
  2. 关于锁的释放
    wait会释放锁,sleep不会释放
  3. 使用范围不同
    sleep可以在任何地方使用
    wait必须在同步代码块中,因为要获取锁才能释放

5.lock锁和Synchronized锁的区别

Synchronized是java内置关键字,lock是java类。
Synchronized无法判断锁的状态,Lock可以判断是否获取锁等多个信息
Synchronized锁是自动的,不需要手动释放锁。而lock锁需要手动lock和unlock,不然会造成死锁
Synchronized拿不到锁会造成线程阻塞,Lock就不会一直等待锁。
Synchronized适合少量的同步代码块,Lock适合大量的

6.虚假唤醒

在并发环境中必须使用while来做判断语句,假如用if,在多个线程同时判断的时候,if只会执行第一个线程的判断,其他线程则可以直接进入if语句里面不用判断,进行一些操作很有可能让不该唤醒的锁被唤醒。用while就可以杜绝这样的事情

7.集合不安全

在并发环境下,集合,set,map等都是不安全的,有可能会出现java.util.ConcurrentModificationException异常。解决方法可以使用官方加了锁的集合类,比如vector等,但是都已经过时。可以使用Collection下的synchronize集合方法,或者锁和juc下的CopOnWriteArraylist方法,set也同理,但是map只有ConcurrentHashMap

8.如何使用callable

由于callable不能直接使用,所以需要创建一个FutureTask的实例,用里面的构造方法讲callable对象传入,最后用new Thread启动FutureTask。如果需要获取返回值,可以用FutureTask的get方法。
但是结果可能会被缓存并且造成阻塞。

9.CountDownLatch、CyclicBarrier、Semaphore是什么,怎么用

CountDownLatch和CyclicBarrier都是计数器,前者是减法,后者是加法。
CountDownLatch构造传入数字阈值,使用countDown方法让数字-1,await可以等待数字为0的时候进行下面语句的执行
CyclicBarrier构造传入数字阈值,用await方法自增数字
Semaphore是信号量,用来做限流,构造方法传入最大流量,使用acquire增加流量,如果满了则会等待释放。release释放,并且唤醒其他线程

//限流两个,此代码结果为一次只能打印两个join,一秒后打印两个out并且释放,
//唤醒其他线程,然后再次打印两个join依次循环
 Semaphore semaphore = new Semaphore(2);
        for (int i = 1; i <= 6 ; i++) {
            new Thread(()->{
                try {
                //增加一个流量
                    semaphore.acquire();
                } catch (InterruptedException e) {
                    e.printStackTrace();
                }
                try {
                    System.out.println(Thread.currentThread().getName()+"join");
                    TimeUnit.SECONDS.sleep(1);
                    System.out.println(Thread.currentThread().getName()+"out");
                    //释放流量
                    semaphore.release();

                } catch (InterruptedException e) {
                    e.printStackTrace();
                }
            },String.valueOf(i)).start();
        }

10.阻塞队列

关系图
关系图

如果队列满了,就需要让队列进入阻塞

添加数据移除数据检测队首元素
抛出异常addremoveelement
不会抛异常offerpollpeek
阻塞等待pulltake
超时等待offer(元素,超时时间,时间单位)poll(超时时间,时间单位)
 ArrayBlockingQueue queue = new ArrayBlockingQueue(1);
        queue.add("1");
        queue.remove();
        queue.offer("1");
        queue.poll();
        queue.put("1");
        queue.take();
        queue.offer("1",1, TimeUnit.SECONDS);
        queue.poll(1,TimeUnit.SECONDS);

11.线程池三个方法,七个参数,四个拒绝策略

创建方法(不建议用三种方法来创建线程池)

 // 单线程
        Executors.newSingleThreadExecutor();
        // 固定线程
        Executors.newFixedThreadPool(3);
        // 可根据并发量扩展线程数
        Executors.newCachedThreadPool();

源码中可以发现,这三个方法都调用了ThreadPoolExecutor方法来创建线程池,一般我们不用三个方法创建,而是用ThreadPoolExecutor来创建线程池。

public static ExecutorService newSingleThreadExecutor() {
    return new FinalizableDelegatedExecutorService
        (new ThreadPoolExecutor(1, 1,
                                0L, TimeUnit.MILLISECONDS,
                                new LinkedBlockingQueue<Runnable>()));
}

public static ExecutorService newFixedThreadPool(int nThreads) {
        return new ThreadPoolExecutor(nThreads, nThreads,
                                      0L, TimeUnit.MILLISECONDS,
                                      new LinkedBlockingQueue<Runnable>());
}

public static ExecutorService newCachedThreadPool() {
        return new ThreadPoolExecutor(0, Integer.MAX_VALUE,
                                      60L, TimeUnit.SECONDS,
                                      new SynchronousQueue<Runnable>());
}


 public ThreadPoolExecutor(int corePoolSize, //核心线程池大小
                              int maximumPoolSize,//最大核心线程池大小
                              long keepAliveTime,//超时释放时间
                              TimeUnit unit,//超时单位
                              BlockingQueue<Runnable> workQueue,//阻塞队列
                              ThreadFactory threadFactory,//线程工厂
                              RejectedExecutionHandler handler//拒绝策略) {
        if (corePoolSize < 0 ||
            maximumPoolSize <= 0 ||
            maximumPoolSize < corePoolSize ||
            keepAliveTime < 0)
            throw new IllegalArgumentException();
        if (workQueue == null || threadFactory == null || handler == null)
            throw new NullPointerException();
        this.acc = System.getSecurityManager() == null ?
                null :
                AccessController.getContext();
        this.corePoolSize = corePoolSize;
        this.maximumPoolSize = maximumPoolSize;
        this.workQueue = workQueue;
        this.keepAliveTime = unit.toNanos(keepAliveTime);
        this.threadFactory = threadFactory;
        this.handler = handler;
    }

创建线程池的七个参数,阻塞队列和线程工厂一般用linkedBlockingQueue和defaultThreadFactory

int corePoolSize, //核心线程池大小
int maximumPoolSize,//最大核心线程池大小
long keepAliveTime,//超时释放时间
TimeUnit unit,//超时单位
BlockingQueue<Runnable> workQueue,//阻塞队列
ThreadFactory threadFactory,//线程工厂
RejectedExecutionHandler handler//拒绝策略	

四个拒绝策略
AbortPolicy:超出最大承载,不处理最后一次请求并且抛出异常
DiscardPolicy:超出最大承载,不处理最后一次请求,不抛出异常
DiscardOldestPolicy:超出最大承载后,接下来的请求,尝试和最早的线程进行竞争,如果失败就会不处理请求但是不抛出异常。
CallerRunsPolicy:请求从哪里给的线程池,就从哪里返回,让原来那个线程去处理请求

12.线程池大小在生产环境下如果定义

主要看io密集和cpu密集。
可以设置成在程序中占用io的资源的总数✖️2
或者cpu最大的进程数,一般我们不会写死,会使用Runtime.getRuntime().availableProcessors()获取当前cpu的最大进程数来动态获取。

13.四大函数式接口

函数式接口是什么:只有一个方法的接口

@FunctionalInterface
public interface Runnable {
    public abstract void run();
}
// FunctionalInterface
// 简化编程模型,在新版本的框架底层大量应用
Function

Function:传入参数T类型,返回R类型
在这里插入图片描述

 public static void main(String[] args) {
//        Function<String, String> function = new Function<String, String>() {
//            @Override
//            public String apply(String s) {
//                return s;
//            }
//        };
        Function function = str-> str;
        System.out.println(function.apply("t"));
    }
Predicate

Predicate:传入T,返回一个布尔在这里插入图片描述

Predicate<String> predicate = new Predicate<String>() {
//            @Override
//            public boolean test(String o) {
//                return o.isEmpty();
//            }
//        };

Predicate predicate = str-> str.equals("");
System.out.println(predicate.test(""));
Consumer

在这里插入图片描述

        /**
         * 消费型接口
         */
//        Consumer<String> consumer = new Consumer<String>() {
//            @Override
//            public void accept(String s) {
//                System.out.println(s);
//            }
//        };
        
        Consumer consumer = str-> System.out.println(str);
        consumer.accept("ABC");
      
Supplier

在这里插入图片描述

 /**
         * 供给型接口
         */
//        Supplier<String> supplier = new Supplier<String>() {
//            @Override
//            public String get() {
//                return "sd";
//            }
//        };
        Supplier supplier = ()-> {return "ss";};
        System.out.println(supplier.get());

14.Stream流式计算

public static void main(String[] args) {
    User a = new User(1, 10, "a");
    User b = new User(2, 20, "b");
    User c = new User(3, 30, "c");
    User d = new User(4, 40, "d");
    User e = new User(5, 50, "e");
    User f = new User(6, 60, "f");
    List<User> users = Arrays.asList(a, b, c, d, e, f);
    /**
     * 包括lambda , 链式编程, 函数式接口, stream流式计算
     */
    users.stream()
            .filter(user -> user.getId()%2==0) // 偶数ID
            .filter(user -> user.getAge()>=23)  // age大于23
            .map(user -> user.getName().toUpperCase())  //name大写
            .sorted((u1,u2) -> {return u2.compareTo(u1);}) //name倒序
            .limit(1) //首条数据
            .forEach(System.out::println); // 遍历输出
}

15.JMM

谈谈你对Volatile的理解

Volatile是一个jvm虚拟机提供的一个轻量级的同步机制,保证了可见性,并且在cpu层面的内存屏障避免了指令的重排序,但是不能保证原子性。

什么是JMM

JMM是一个java内存模型,并不是实际存在的,是一个约束概念,有大概以下几点。
1.线程解锁前必须把工作内存的变量立即同步到主内存。
2.线程加锁前,必须从主内存中的最新值读取到工作内存中。
3.保证加锁和解锁用的同一把锁。
线程读取储存数据的jvm指令步骤有以下几步。
lock->read->load->use->assign->store->write
->unlock
因为有可能在一个线程进行数据操作的时候,另一个线程已经将主内存的数据改掉,然而数据不存在可见性,那个线程使用的还是老的数据,所以这时候就需要Volatile来解决

在这里插入图片描述

如何解决原子性问题

最简单的方式可以加锁,比如lock或者synchronize锁都可以,也可以使用原子类,原子类的底层用的是CAS,使用操作系统来保证原子性

什么是指令重排序

写的程序j的vm虚拟机指令不会以我们想的顺序来执行,会根据优化来进行一些重新排序。例如修改变量a两次,正常的需要load,assign,store两次,而指令重排序只需要load一次,进行两次修改,会减少一次load store的操作。

16.单例模式

单例模式有几种

两种,饿汉式,当运行的时候就会创建,懒汉式,只有调用的时候才会创建。

并发环境下单例模式是线程安全的吗,不安全怎么解决

不安全的,可以进行加锁操作或者使用DCL懒汉式,就是双重锁判断,先判断对象是否为空,再加锁,再判断对象是否为空,并将单例对象加上volatile防止指令重排序的影响。这样子效率安全都会更高

17.CAS

CAS是什么

是比较当前工作内存的值和主内存的值。如果这个值是期望的,就进行操作,要不然就会一直循环,底层是自旋锁,所以循环会很耗时。而且一次性只能保证一个共享变量的原子性,而且有可能产生ABA问题

ABA问题是什么

当线程A用CAS把变量变动之后再,在变动成原来的值,这时候线程B,进行CAS操作进行更改,因为拿到的值还是原来锁期望的值,所以这次修改会成功,但是虽然值还是原来的值,其实这个值已经是变了的,在特定业务情况会造成影响。

ABA问题的解决方法

进行带版本号的原子操作

18 可重入锁

拿到了最外层的锁,就自动获得里面的锁,通过计数器来判断拿到了几个锁,当计数器为0的时候才会释放所有的锁。但是当用lock锁的时候锁必须一一配对,不然会造成死锁。

19.死锁和死锁的判断

A线程获取了A锁,B获取了B锁,但是此时AB线程互相识图抢对方的锁,就造成死锁

可以使用jps -l定位进程号,找到程序的进程,使用jstack加进程号来排查。

  • 2
    点赞
  • 5
    收藏
    觉得还不错? 一键收藏
  • 1
    评论
评论 1
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值