开篇介绍
大家好,公众号【Java极客思维】近期会整理一些Java高频面试题分享给小伙伴,也希望看到的小伙伴在找工作过程中能够用得到!本章节主要针对Java一些多线程高频面试题进行分享。
Q1:
乐观锁 和 悲观锁
乐观锁:
乐观锁(Optimistic Locking)其实是一种思想。相对悲观锁而言,乐观锁假设认为数据一般情况下不会造成冲突,所在数据进行提交更新的时候,才会正式对数据的冲突与否进行检测,如果发现冲突了,则让返回用户错误的信息,让用户决定如何去做。
悲观锁:
Java在JDK1.5之前都是靠 synchronized 关键字保证同步的,这种通过使用一致的锁定协议来协调对共享状态的访问,可以确保无论哪个线程持有共享变量的锁,都采用独占的方式来访问这些变量。独占锁就是一种悲观锁,所以可以说:synchronized是悲观锁 。
Q2:
synchronized内置锁
用处:
synchronized作为线程同步的关键字,设计到锁的概念,Java内置锁是一个互斥锁,这就说明最多只有一个线程能够获得该锁,例如线程A 和 线程B,如果线程A尝试去获得线程B的内置锁,则线程A必须等待或者阻塞,直到线程B释放这个锁为止;如果线程B永不释放这个锁,那么线程A将永远处于等待或者阻塞状态。Java的对象锁和类锁在锁的概念上,与内置锁几乎是一致的,但是对象锁和类锁的区别是非常大的。
对象锁:
用synchronized关键字修饰非静态方法、用synchronized(this)作为同步代码块,用synchronized(非this对象)的用法,锁的是对象,线程想要执行对应的同步代码,需要先获得对象锁。
类锁:
用synchronized修饰静态方法、用synchronized(类.class)的用法,锁的是类,线程想要执行对应的同步代码,需要先获得对象锁。
对象锁:锁的是类的对象实例;
类锁:锁的是每个类的Class对象,每个类的Class对象在虚拟机中只有一个,所以类锁也只有一个。
Q3:
乐观锁一定就是好的吗?
乐观锁避免了悲观锁独占对象的现象,同时也提高了并发性能,但是也有以下几个缺点:
1. 乐观锁只能保证一个共享变量的原子操作。如果多一个或者几个变量,乐观锁将变得力不从心;
(但互斥锁能轻易解决,不管对象数量多少以及对象颗粒度大小。)
2. 长时间自旋可能导致开销大。假如CAS长时间不成功就会一直自旋,会给CPU带来非常巨大的开销
3. ABA问题。CAS的核心思想是通过对比内存值与预期值是否一致而判断内存值是否被改动过,但这个判断逻辑不够严谨,例如:假如A同学倒了一杯水放桌子上,然后有事去忙,此时B同学经过,看到桌子上的水,然后喝了半杯,喝完后再将水打满,实际看起来还是一杯水,但是这杯水已经不是A同学的那一杯水了,且实际上水是已经被B同学改过了,这种情况对依赖过程值的情景的运算结果影响很大。
解决的方案:通过引入版本号,每次变量更新都把版本号加1.
Q4:
线程池的启动策略?
线程池的执行过程.png
1. 线程池刚创建时,里面没有一个线程。任务队列是作为参数传进来的。不过就算队列里面有任务,线程池也不会马上执行它们。
2. 当调用 execute() 方法添加一个任务时,线程池会做如下判断:
-
如果正在运行的线程数量小于 corePoolSize ,那么马上创建线程运行这个任务;
-
如果正在运行的线程数量大于或等于 corePoolSize , 那么将这个任务放入队列;
-
如果这时候队列满了,而且正在运行的线程数量小于 maximumPoolSize ,那么还是要创建线程运行这个任务;
-
如果队列满了,而且正在运行的线程数量大于或等于 maximumPoolSize ,那么线程池会抛出异常,告知调用者"我不能再接受任务了"。
3. 当一个线程完成任务时,它会从队列中取下一个任务来执行。
4. 当一个线程无事可做,超过一定的时间 (keepAliveTime) 时,线程池会进行判断,如果当前运行的线程数大于 corePoolSize ,那么这个线程就会被停掉。所以线程池的所有任务完成后,它最终将会收缩到 corePoolSize 大小。
明天,会介绍多线程一些深入的知识,长按二维码关注我吧~
祝大家都能拿到心仪的offer!
点关注、不迷路
如果觉得文章不错,欢迎关注、点赞、收藏,你们的支持是我创作的动力,感谢大家。
如果文章写的有问题,请不要吝啬,欢迎留言指出,我会及时核查修改。
如果你还想更加深入的了解我,可以微信搜索「Java极客思维」进行关注。每天8:00准时推送技术文章,让你的上班路不在孤独,而且每月还有送书活动,助你提升硬实力!