Java并发相关面试题

1、ConcurrentHashMap原理,如何保证线程安全
参考
使用CAS和synchronized保证线程安全
2、红黑树原理
参考
3、synchronized
(1)synchronized是可重入锁。
可重入锁顾名思义表示可重新反复进入的锁,但仅限于当前线程。是为了解决自己锁死自己的情况。
一个类中的同步方法调用另一个同步方法,假如 Synchronized 不支持重入,进入 method1 方法时当前线程获得锁,method1 方法里面执行 method2 时当前线程又要去尝试获取锁,如果不支持重入,它就要等释放,把自己阻塞,导致自己锁死自己。
(2)synchronized锁对象是什么?
当synchronized作用在实例方法时,监视器锁(monitor)便是对象实例(this);
当synchronized作用在静态方法时,监视器锁(monitor)便是对象的Class实例,因为Class数据存在于永久代,因此静态方法锁相当于该类的一个全局锁;
当synchronized作用在某一个对象实例时,监视器锁(monitor)便是括号括起来的对象实例;
synchronized 内置锁 是一种 对象锁(锁的是对象而非引用变量),作用粒度是对象。
(3)synchronized底层原理
synchronized代码块反编译后会发现代码块被monitorenter和monitorexit包裹。当monitor被占用时就会处于锁定状态,线程执行monitorenter指令时尝试获取monitor的所有权,过程如下:
A、如果monitor的进入数为0,则该线程进入monitor,然后将进入数设置为1,该线程即为monitor的所有者;
B、如果线程已经占有该monitor,只是重新进入,则进入monitor的进入数加1;
C、如果其他线程已经占用了monitor,则该线程进入阻塞状态,直到monitor的进入数为0,再重新尝试获取monitor的所有权;
参考
4、synchronized与ReentrantLock
相同点:都是阻塞式的同步,都可重入
不同点:
A、ReentrantLock更加灵活。
a、synchronized是非公平锁,ReentrantLock默认非公平,可选择为公平锁。
b、等待可中断,持有锁的线程长期不释放的时候,正在等待的线程可以选择放弃等待,这相当于Synchronized来说可以避免出现死锁的情况。通过lock.lockInterruptibly()来实现这个机制。
c、锁绑定多个条件,一个ReentrantLock对象可以同时绑定对个对象。ReenTrantLock提供了一个Condition(条件)类,用来实现分组唤醒需要唤醒的线程们,而不是像synchronized要么随机唤醒一个线程要么唤醒全部线程。
B、对于Synchronized来说,它是java语言的关键字,是原生语法层面的互斥,需要jvm实现。而ReentrantLock它是JDK 1.5之后提供的API层面的互斥锁,需要lock()和unlock()方法配合try/finally语句块来完成
C、在Synchronized优化以前,synchronized的性能是比ReenTrantLock差很多的,但是自从Synchronized引入了偏向锁,轻量级锁(自旋锁)后,两者的性能就差不多了,在两种方法都可用的情况下,官方甚至建议使用synchronized,其实synchronized的优化我感觉就借鉴了ReenTrantLock中的CAS技术。都是试图在用户态就把加锁问题解决,避免进入内核态的线程阻塞。
D、ReentrantLock是一种自旋锁,通过循环调用CAS操作来说实现,性能比较好也是因为避免了使线程进入内核态的阻塞状态。
参考
5、CAS的原理
CAS(Conmpare And Swap)是用于实现多线程同步的原子指令。它将内存位置的内容与给定值进行比较,只有在相同的情况下,将该内存位置的内容修改为新的给定值。 这是作为单个原子操作完成的。 原子性保证新值基于最新信息计算; 如果该值在同一时间被另一个线程更新,则写入将失败。 操作结果必须说明是否进行替换。
CAS操作都是通过sun包下Unsafe类实现,而Unsafe类中的方法都是native方法,是CPU指令级的操作,本身可以保证原子性。
6、ABA问题
CAS可以有效的提升并发的效率,但同时也会引入ABA问题。
ABA问题的解释:如线程1从内存X中取出A,这时候另一个线程2也从内存X中取出A,并且线程2进行了一些操作将内存X中的值变成了B,然后线程2又将内存X中的数据变成A,这时候线程1进行CAS操作发现内存X中仍然是A,然后线程1操作成功。虽然线程1的CAS操作成功,但是整个过程就是有问题的。比如老公银行卡里原来有5000,工资发了5000,老婆转走5000,余额还是5000,这时候老公就会认为工资没发。
解决办法:AtomicStampReference
AtomicStampReference在cas的基础上增加了一个标记stamp,使用这个标记可以用来觉察数据是否发生变化,相当于给数据增加了一个版本号。
7、强引用、软引用、弱引用、虚引用
强引用:默认,如果一个对象具有强引用,那就类似于必不可少的物品,不会被垃圾回收器回收。当内存空间不足,Java虚拟机宁愿抛出OutOfMemoryError错误,使程序异常终止,也不回收这种对象。
软引用:可以理解为有用但并不是必需。对于软引用关联着的对象,只有在内存不足的时候JVM才会回收该对象。很适合用来实现缓存:比如网页缓存、图片缓存等。
弱引用:弱引用也是用来描述非必需对象的,当JVM进行垃圾回收时,无论内存是否充足,都会回收被弱引用关联的对象。在java中,用java.lang.ref.WeakReference类来表示。
应用场景:如果一个对象是偶尔的使用,并且希望在使用时随时就能获取到,但又不想影响此对象的垃圾收集,那么应该用 Weak Reference 来记住此对象。或者想引用一个对象,但是这个对象有自己的生命周期,你不想介入这个对象的生命周期,这时候就应该用弱引用,这个引用不会在对象的垃圾回收判断中产生任何附加的影响。
虚引用:不影响对象的生命周期。在java中用java.lang.ref.PhantomReference类表示。如果一个对象与虚引用关联,则跟没有引用与之关联一样,在任何时候都可能被垃圾回收器回收。虚引用主要用来跟踪对象被垃圾回收的活动。必须和引用队列关联使用,当垃圾回收器准备回收一个对象时,如果发现它还有虚引用,就会把这个虚引用加入到与之 关联的引用队列中。
参考
8、volitile是做什么的?
volatile 是一个类型修饰符。volatile 的作用是作为指令关键字,确保本条指令不会因编译器的优化而省略。
特点:

  • 保证了不同线程对这个变量进行操作时的可见性,即一个线程修改了某个变量的值,这新值对其他线程来说是立即可见的。(实现可见性)
  • 禁止进行指令重排序。(实现有序性)
  • volatile 只能保证对单次读/写的原子性。i++ 这种操作不能保证原子性。
    参考

9、公平锁与非公平锁
10、乐观锁与悲观锁
11、wait、notify为什么是Object的方法
个人认为wait、notify都是对象锁的功能,放在Object中更加符合单一职责原则。
参考
12、wait和sleep的区别

  • 这两个方法来自不同的类分别是Thread和Object
  • 最主要是sleep方法没有释放锁,而wait方法释放了锁,使得其他线程可以使用同步控制块或者方法。
  • wait,notify和notifyAll只能在同步控制方法或者同步控制块里面使用,而sleep可以在任何地方使用(使用范围)
  • sleep必须捕获异常,而wait,notify和notifyAll不需要捕获异常
  • sleep是Thread类的静态方法。sleep的作用是让线程休眠制定的时间,在时间到达时恢复,也就是说sleep将在接到时间到达事件事恢复线程执行。wait是Object的方法,也就是说可以对任意一个对象调用wait方法,调用wait方法将会将调用者的线程挂起,直到其他线程调用同一个对象的notify方法才会重新激活调用者。
    挂起(sleep)与阻塞(wait)的区别
    参考

13、Java内存模型
关键点:区分主内存和工作内存、volitile
参考

发布了26 篇原创文章 · 获赞 8 · 访问量 1万+

猜你喜欢

转载自blog.csdn.net/weixin_36142042/article/details/104887895