并发编程(Java面试题)

1、什么是进程?什么是线程?

进程是资源分配的最小单位,线程是CPU调度的最小单位,一个进程包含多个线程

2、讲一讲线程的生命周期

线程有五种状态:创建、等待、运行、阻塞、结束,线程有三种创建方式,两种没有返回值,一种有返回值,第一种是继承Thread类,第二种是实现Runnable接口,第三种是通过Callable和Future创建(重写Callable类的Call方法,通过get方法获取Call中返回值),线程创建好之后,通过调用start方法让线程进入等待状态,当线程被CPU调度的时候进入运行状态。线程如果调用sleep方法或者wait方法进入阻塞状态。然后通过中断方法interrupt抛出异常去唤醒被sleep的线程,或者通过notify、notifyAll去唤醒被wait的线程,进入对象锁池中。有三种情况线程会结束,第一种是run方法运行结束,第二种是调用stop方法强制结束,第三种是使用中断方法interrupt在合适的地方结束线程

3、wait()和sleep()的五个区别是什么?

有五个区别:第一个是sleep不会让出cpu资源,wait会,第二个是sleep不会释放锁,wait会,第三个是sleep可以在任意位置调用,wait方法只能在同步块或者同步方法中调用,第四个是sleep唤醒之后是进入等待状态,而wait唤醒之后是在对象的锁池中争抢锁。第五个区别是sleep方法是线程的方法,而wait是Object的方法。

4、wait()、notify()、notifyAll()的区别是什么?

wait方法让线程释放锁且让出CPU资源,notify会唤醒对象锁池中的随机一个线程,notifyall唤醒对象锁池中所
有的线程,优先级越高的线程争抢到锁的概率越大(setPriority()1的优先级最低,10的优先级最高,默认值是5
,thread.setPriority(1))

5、Volatile的作用是什么?

保证线程之间的可见性,防止指令重排,每次读被修饰的变量的时候直接从主内存(线程共享区)中读取,而不
是从工作内存(线程私有区,栈)中读取,这样线程就能保证每次读取到的都是最新值。但是Volatile只能保证
线程间的可见性,而不能保证原子性,如果两个线程同时把变量拷贝到工作内存中,同时修改之后复制到主内存
,这时候就出问题了。像刚刚说的读取到工作内存其实就是被拷贝工作缓存区,工作缓存区是一个抽象的概念,
在jvm中是没有划分这块区域的,具体放在了哪里,是由虚拟机决定的,只要符合JMM规范即可。

6、Volatile应用场景?

不需要保证线程安全的场景,比如run方法中有一个以被Volatile修饰的变量为开关,当开关为false的时候退出循环

7、Volatile和Synchronized的三个区别是什么?

第一个是volatile是轻量级同步(不排他,不保证原子性),synchronized是重量级同步(排他,CPU切换消耗资源)第二个是区别主要是volatile不保证原子性,而synchronized保证原子性,从而保证了可见性,第三个是volatile只能修饰变量,synchronized可以修饰代码块和方法

8、什么时候会发生死锁?如何避免死锁?

发生死锁有四个条件:互斥使用、不可抢占、请求和保持、循环等待。避免死锁有两种方式:第一个是加锁顺序要一致,第二个是给锁设置超时时间。

9、如何给锁设置超时时间?

10、如何用代码实现生产者消费者问题?

创建一个资源类,设置资源的最大个数为10,当前资源个数为0,类中有remove和put方法,remove方法中去判断当前资源个数是否为0,为0调用wait方法进行等待,不为0资源–,最后使用notify去唤醒其他线程(为什么不用notifyall唤醒全部呢?因为唤醒一个也是随机的,唤醒全部也是随机争抢锁的,当然数量越少越不消耗资源),put方法也是同理,去判断资源是否达到最大值,达到则等待,未达到则资源++,最后唤醒其他线程。消费者和生产者类分别使用组合调用资源类的remove和put方法,分别创建和启动消费者生产者线程

11、Synchronized的实现原理?

在jdk1.6之前synchronized是重量级的,但是经过不断的优化在jdk1.6之后就变得很强大了,synchronized的锁有四种状态:无锁、偏向锁、轻量级锁、重量级锁,随着竞争状态逐渐升级,不能降级在第一次获取锁的时候会通过CAS去修改对象头Mark word中的锁信息,锁会从CAS升级为偏向锁,Mark Word记录偏向线程的Id,如果没有其他线程竞争,就不需要去通过CAS加锁和解锁的,偏向线程会一直拥有偏向锁。当有线程要竞争偏向锁的时候会发生锁撤销,如果持有偏向锁的线程已经退出同步代码块或者不在活动状态,那么就会让持有偏向锁的线程在安全的地方暂定,然后释放锁,再唤醒持有偏向锁的线程(中断?),竞争的线程通过CAS去修改Mark word中的偏向线程Id,获取到偏向锁。如果发生竞争,持有偏向锁的线程未退出同步代码块,那么偏向锁就会升级成轻量锁,JVM会在偏向线程的栈中开辟一个存放锁信息的空间,存放对象头中MarkWord拷贝,在对象头中的MarkWord替换成指向栈中锁信息的指针。轻量级锁释放
的时候重新把MarkWord替换到对象头中,然后唤醒原持有偏向锁的线程。竞争线程通过尝试用指向栈中所信息的指针替换MarkWord,替换成功则获取轻量级锁,替换失败自旋,自旋次数过多升级成重量级锁,就是使用monitor重量级锁是基于monitorenter和monitorexit指令实现的,monitorenter插入到开始位置,monitorexit插入到结束位置,JVM需要保证每一个monitorenter都有一个monitorexit相关联,每一个对象都有一个monitor与之相关联,当一个monitor被持有之后,对象将处于锁定状态。当线程执行到monitorenter指令时会尝试获取对象锁对应的monitor所有权,即尝试获取对象锁。

12、this锁、对象锁、类锁的区别?

this锁锁住的是当前实例,对象锁锁住的是指定对象,类锁锁住的是class对象,Synchronized修饰普通方法时是this锁,User.class.wait()释放锁,修饰静态方法时是类锁,修饰代码块时可以指定锁类型,本质使用的都是monitor

13、常见的锁类型有哪些?

CAS锁:AS机制当中使用了3个基本操作数:内存地址V,旧的预期值A,要修改的新值B。更新一个变量的时候,只有当变量的预期值A和内存地址V当中的实际值相同时,才会将内存地址V对应的值修改为B。自旋锁:就是通过CAS不断去判断是否满足锁条件。乐观锁:一开始就认为一定会执行成功,通过版本号或者时间戳去控制,CAS就是乐观锁。悲观锁:一开始就认为一定不会执行成功,先争抢锁,再执行业务逻辑。重入锁:外层获取锁之后内层就不需要再次去获取锁了,避免了死锁,ReentrantLock和sychronized就是可重入锁。不可重入锁:外层获取锁之后,内层也要重新获取锁,容易产生死锁。公平锁:不同时间段进入对象锁池的按照先到先获取锁的规则获取锁。非公平锁:不同时间段进入对象锁池的按照一起争抢锁的规则获取锁
偏向锁、轻量级锁、重量级锁,谈Synchronized的实现原理。分段锁:谈ConcrrountHashMap。分布式锁:Redis、ZK等。读写锁:写的时候不让其他读和写,和zk同步的时候服务不让用一个道理。

14、谈一谈ReetranLock,和Synchronized的三个区别?

ReentrantLock把所有Lock接口的操作委派给Sync类上,Sync类继承了AQS抽象类,Sync有两个子类,一个支持公平锁,
一个支持非公平锁。默认使用非公平锁。ReentrantLock.lock调用的是NonFairSync.lock方法(AQS只把尝试获取锁的
方法交给子类实现lock unlock holdLock()判断是否获取到锁)一般会在finally块中写unlock()以防死锁

15、ReetranLock和Synchronized的四个区别?

第一个区别:一个是类,一个是关键字,第二个区别:一个可以判断是否获取到锁,一个不可以,第三个区别:一个是自动挡(指定开始位置和结束位置),一个是手动挡(修饰方法和代码块)
,第四个区别:lock是乐观锁,Synchronized原始用的是悲观锁,jdk1.6之后就变得很强大了可以锁升级。

16、join()的作用

阻塞线程,一般用来保证线程的执行顺序

17、run()和start()的区别?

run()只是一个单纯的方法,并没有去创建线程,而start方法是去创建线程后执行run方法

18、CycliBarriar和CountdownLatch的两个区别?

第一个区别是侧重点不同,CycliBarriar一般是用来阻塞一组线程至某个状态才执行,CountdownLatch是让某个线程等待其他线程
执行完任务之后再执行,第二个区别是CycliBarriar可以循环利用CountdownLatch是一次性的,到0之前都会阻塞线程

19、多线程环境的常见问题?

竞争条件:同时去操作一个非线程安全的数据,造成预期之外的结果
死锁:互相抢资源
活锁:互相让资源,解决,先到先服务
饥饿:资源不足

20、什么是原子类?

比如AtomicInteger类,可以调用方法进行自增或者自减,保证线程安全

21、什么是CAS锁?

CAS有3个操作数,内存值V,旧的预期值A,要修改的新值B。当且仅当预期值A和内存值V相同时,将内存值V修改为B,否则返回V。
这是一种乐观锁的思路,它相信在它修改之前,没有其它线程去修改它;而Synchronized是一种悲观锁,它认为在它修改之前,一
定会有其它线程去修改它,悲观锁效率很低。
CAS存在一个很明显的问题,即ABA问题;
如果变量V初次读取的时候是A,并且在准备赋值的时候检查到它任然是A,那能说明它的值没有被其他线程修改过了吗?
如果在这段时间曾经被改成B,然后有改回A,那CAS操作就会误任务它从来没有被修改过。正对这种情况,java并发包提供了一
个带有标记的原子应用类AtomicStampedRefernce,它可以通过变量值的版本来保证CAS的正确性;

22、在应用层有哪些常见锁?

synchronized锁、lock

23、悲观锁的应用场景?

并发量高的时候,如果追求响应快就用乐观锁,要么快速成功要么快速失败,如果重试代价大,使用悲观锁,悲观
锁可以保证成功率。
悲观锁适合读少写多的情况,乐观锁适合读多写少的情况

24、IO什么情况会出现死锁?

25、怎么快速判断是IO死锁问题而不是其他问题?

26、CLH知道么?虚拟队列。

虚拟队列不是真实的队列,是由指针指向下一个节点,看起来类似队列

27、双重校验加锁为什么需要volatile?

28、多线程为什么能提高效率?

29、假如我无限的开新线程,这个程序的效率是一直上升,还是下降,还是维持不变?

30、Synchronized是单服务器处理并发问题,那多服务器怎么办?

31、为什么wait()、notify()、notifyall()要在同步代码块中被调用

32、java虚拟机对synchronized的优化

在1.6之前synchronized是重量级锁,1.6之后引入了偏向锁、轻量级锁、自适应自旋锁

33、如何在两个线程之间共享数据

创建一个全局静态变量
通过方法传递引用

34、如何正确的使用wait?if还是while?

应该使用while,不然第一次使用wait,被唤醒之后就直接走下面的流程了,
需要再判断一下是否满足条件才能往下走。

35、为什么要使用多线程?多线程应用场景?

多线程可以提高CPU利用率,比如注册的时候发送短信,可以一边发送
短信,一边处理业务逻辑还有就是多文件上传、aop异步写日志到es中

36、如何检测死锁?

jstack可以提示哪个地方死锁了,jvm自带的工具

37、多线程中如何保证只有一个run()方法在执行?(*******)

38、什么是阻塞队列?如何实现?

生产者消费者问题

39、什么是ThreadLocal?作用是什么?(*******)

40、同步和异步的区别

同步:所有操作都做完,才返回给用户,例如:银行转账
异步:不用等所有操作都做完,就返回结果,达到局部刷新。

发布了51 篇原创文章 · 获赞 2 · 访问量 1850

猜你喜欢

转载自blog.csdn.net/qq_42972645/article/details/105658158