Java面试-多线程并发(JUC并发编程包)

1.并发,并行,串行的解释

并发:一个CPU执行多个任务,但是会发生上下文切换,让我们看到的好像同时执行,但实际上不是同时执行,就好比一台咖啡机对应两个队列
并行:多个CPU执行多个任务,这才是真正意义上的同时执行,就好比两台咖啡机对应两个队列
串行:按照顺序执行,好比一台咖啡机对应一个队列

2.什么是多线程,优缺点是什么

多线程就是指代一个程序中含有多个执行流,运行程序可以让不同的线程执行不同的任务
好处:可以提高CPU的利用率,因为加入一个线程进入了阻塞状态,CPU不会去等待,而是会进行执行其他的线程,让CPU忙起来,提高了效率
坏处:我们使用了多线程,需要考虑很多东西,比如说资源共享的一些问题
线程也是程序,需要占用内存

3.什么是线程和进程

我理解的就是进程是一个大的程序,比如说360,而线程就是里面的一些小功能,多线程执行就相当于一边检测一边杀毒的功能并行

4.什么是上下文切换

我们的程序一般就是线程的数量大于CPU的核数,这样的话就会发生上下文切换,就是指一个CPU正在执行一个线程,执行了一部分,会保存这一部分,去执行其他的线程,此时会给用户一个错觉,同时执行的错觉,实际上就发生了上下文切换

5.守护线程和用户线程区别

用户线程就是我们所看到的程序的功能,也就是说main方法的线程就属于用户线程
守护线程就相当于佣人,不会影响任何,随着JVM的消失也就销毁

6.什么是死锁,如何避免

死锁就是指两个或多个线程同时获取到对方想要的资源,并且同时进入阻塞状态,来等待对方先释放这个资源,这样就产生了死锁

7.创建线程的方式有哪些

1.继承Thread类,重写run()
2.实现Runnable接口,实现run()
3.实现Callable接口,实现call()
4.使用Executors工具类创建线程池

8.start()和run()的区别

start()是用于启动一个线程的方法
run()是用于执行线程体中的内容的方法
我们执行了start()之后,线程就会进入就绪的状态,去等待CPU调度执行run()

9.为什么我们调用start()会执行run(),而不能直接调用run()

因为start()是为了启动线程的,而run()是为了执行线程里面的代码的,如果没有调用start(),而直接调用了run(),那么程序就会把run()当成main线程中的一个普通方法来执行,这样的话就不是多线程了

10.线程的生命周期

1.新建:创建一个线程
2.就绪:执行start(),等待CPU调度
3.运行:执行完start(),CPU调度成功,执行run()
4.阻塞:线程由于某种原因,暂停了对CPU的使用权,比如执行sleep(),yield()等,都会放弃对CPU的使用权,从而进入阻塞状态,必须进入就绪状态,才会有机会让CPU再次调度
5.死亡:执行完run(),或者执行过程中发生了异常都会死亡

11.Java中用到的线程调度算法,及线程调度的策略

12.多线程里面都有哪些方法,各个方法的作用

1.sleep():使线程进入睡眠状态,不释放锁
2.wait():是线程进入等待,进入阻塞,释放锁
3.notify():唤醒某一个处于阻塞状态的线程,不确定要唤醒哪一个
4.notifyAll():唤醒所有阻塞的线程
5.yield():线程礼让,主动进入阻塞状态,让出CPU的使用权
6.join():线程抢占
7.stop();结束线程,使线程进入死亡状态

13.sleep()和wait()的区别

1.sleep()是属于Thread类里面的方法,wait()输入object里面的方法
2.sleep()是使线程进入睡眠状态,不会释放锁,wait()使线程进入阻塞状态,会释放锁
3.sleep()可以设置过期时间,wait()只能等待执行notify()或者notifyAll()才会使线程进入就绪状态

14.如何停止一个正在运行的线程

使用stop()
等待执行完run()里面的内容
执行过程中发生异常

15.synchronized的作用和使用

它是Java的一个内置关键字,属于一种悲观锁
使用synchronized就是 加锁的操作,synchronized是一个锁
可以加在方法上,也可以加在同步代码块上,指定锁的对象
要想进入方法或代码块就需要先获得到锁

16.什么是可重入锁,可重入锁的原理

可重入锁就是值当一个线程获取到该锁时,之后可以继续获得该锁,里面会有一个计数器,获得锁一次,计数器加一,再次获得该锁继续加一,释放锁时计数器减一,当计数器为0时就说明没有线程占用该锁,此时其他的线程可以继续竞争锁

17.什么是自旋

由于很多锁里面的代码都是一些很简单的代码,此时等待的线程都加锁很不值得,所以就有了这种自旋的机制,简单来说就是线程在程序里面有个循环,当线程想获取锁的时候进入这个循环,循环一段时间后如果还没有获取到锁的话,则再进入阻塞,这样可能是一种很好的选择

18.synchronized,volatile,CAS的比较

19.synchronized和lock有什么区别

1.synchronized是一个Java内置关键字,lock是个接口
2.synchronized和lock都是加锁的操作
3.synchronized不用释放锁,lock必须使用unlock释放锁资源
4.synchronized可以作用在方法上和代码块上,lock只能作用在方法内部

20.synchronized和reentrantlock的区别

21.解释一下volatile

在我的理解volatile属于一个轻量级的锁,
特点:1.保证可见性,简单来说就是如果有两个线程操作一个变量,如果一个线程改变了这个变量,则另外一个线程就会发现这个变量已经被更改
不保证原子性,要想保证原子性则需要加synchronized或lock锁,或者使用原子类
禁止指令重排序:计算机底层为了提高效率,会进行指令的重排,重排不影响结果,我们使用了volatile的话就可以禁止指令重排序,因为会加一层内存屏障

22.synchronized和volatile的区别

1.synchronized是一个重量级的可重入悲观锁,volatile是一个轻量级的锁
2.synchronized可以作用在普通方法和静态方法和代码块上,也就是加锁的操作,volatile作用在变量上,可以保证可见性,但是不能保证原子性,可以禁止指令重排

23.什么是乐观锁和悲观锁

乐观锁顾名思义很乐观,认为别人不会修改数据,所以就不会加锁,采用的是版本号机制,如果别人修改了数据,就会相应的修改版本号,最后提交时发现版本号不一致,就提交失败
悲观锁,顾名思义很悲观,认为别人一定会修改数据,所以不管怎样都会加锁,这样如果其他的线程想要修改数据,则必须拿到锁才能摆脱阻塞的状态,从而进行操作

24.什么是CAS

CAS就是一种基于锁的操作,比较并交换
它的方法里面有三个参数,第一个是内存位置,第二个是期待值,第三个是新值
机制就是如果内存位置里面的值和期待值相同,则说明可以更改,就会变为新值,如果和期待值不同,则不能更改,进入自旋状态

25.CAS会产生什么问题

1.会导致ABA问题,就是假如有两个线程,一个线程将数据更改了,用完之后又将数据改回原来的数据,则另一个线程进行操作的时候发现还是原来的数据,就可以进行操作,但是它不知道这个数据已经被用过了,这就是ABA问题,解决这个问题我们可以使用版本号机制,更改一次随之修改版本号,这样的话其他线程再次操作就会比对版本号,看看数据曾经是否被用过
2.一直自旋也会消耗资源
3.还有一个问题就是CAS只能保证对一个共享变量的操作,如果想对多个共享变量进行操作就要加锁

26.什么是AQS,原理分析,设计模式

AQS是一个抽象同步队列器,它的原理就是里面有一个state变量,如果一个线程获取到锁,则state+1,然后其他线程在AQS队列里面等待锁的释放,锁被释放,state-1,此时就可以进行争抢资源了,使用的是模板方法模式

27.什么是公平锁和非公平锁

公平锁就是很公平,按照队列里面的顺序来获取锁,
非公平锁就是非常不公平,线程可以直接去争抢,而不排队,一般的锁都是非公平锁

28.什么是读写锁ReadWriteLock

读写锁就是当我们加这个锁的时候读和读可以并存,读和写,写和写就会互斥不能并存,提升了读写的性能

29.并发容器之concurrentHashMap

见集合容器面试

30.synchronizedMap和concurrentHashMap的区别

31.什么是CopyOnWriteList原理,场景

从字面意思来讲就是写时复制,是一种读写分离的思想,当我们需要向集合里面添加数据的时候,首先将原来的数据复制出来,然后长度加一,再将我们想要添加的数据添加进去,然后改变原来的引用
适用于读操作多,写操作少的情况下
缺点:一直复制集合降低效率,代价大

32.什么是ThreadLocal

ThreadLocal字面意思就是本地的线程,它会创建一个本地的私有变量,只能某一个线程去访问,其他的线程访问不到,它的里面有一个set方法和一个get方法,set方法可以存储键值对,键就是threadLocal对象,值就是map中的value,它的底层是一个map,但是没有实现map接口,是自己创建了一个threadLocalMap
它可能导致内存泄漏

33.ThreadLocal造成内存泄漏的原因

ThreadLocal里面map的key是属于弱引用,而value属于强引用,当发生gc的时候可以会被清理掉,value还会存在,key变为null,此时堆积越来越多,则就会发生内存泄漏

34.ThreadLocal内存泄漏的解决办法

ThreadLocal的内存泄漏分为两种,
一种是key的内存泄漏:解决办法就是让map继承弱引用
第二种是value的内存泄漏,解决办法就是最后手动remove

35.并发容器之blockingQueue

blockingQueue属于是阻塞队列
有四组方法
第一种add()remove():当队列已空在进行取数据或者队列已满进行添加数据就会抛出异常
第二种:poll()pick():当队列已空在进行取数据就会返回null队列已满进行添加数据就会返回false
第三种:put()take():当队列已空在进行取数据或者队列已满进行添加数据就会进入阻塞状态
第四种:offer()poll():当队列已空在进行取数据或者队列已满进行添加数据就会超时等待,可以设置过期时间,过了这个时间就会不再等待

36.线程池创建方式

线程池的创建方式有两种:
第一种:使用Executors类的方法来创建
一共有四个方法分别是:
1.newSingleThreadExecutor():创建一个单线程的线程池
2.newFixedThreadPool():创建一个固定大小的线程池
3.newCacheThreadPool():创建一个可缓存的线程池
4.newScheldedThreadPool():创建一个无限大小的线程池
第二种:使用ThreadPoolExecutor

37.线程池的优点

1.提高线程的可重用性:因为线程放在线程池里面,当我们用完之后再放回去,可以重用
2.提高了现成的可管理性

38.Executors和ThreadPoolEcecutor创建线程池的区别

Executors创建线程池所用的方法都可能导致oom
而ThreadPoolExecutor创建线程池是调用它的构造方法,不会出现oom
阿里巴巴开发手册规定不能用Executors创建,必须用ThreadPoolExecutor创建

39.常用的并发工具类都有哪些

CountDownLatch:减法计数器,就是我们可以设置一个数,使用CountDownLatch,进行自减的操作,当这个数减为0的时候就让下一个线程运行
CyclicBarrier:加法计数器,我们设置一个数,从0开始自增,到达这个数时,就让下一个线程执行
Semaphore:可以限制线程并发的数量

猜你喜欢

转载自blog.csdn.net/weixin_44219219/article/details/114319760