Java线程面试5问(2)

350 阅读4分钟

上一篇文章:Java线程面试5问(1)

线程有哪些锁,各种锁的区别?

主要是根据锁的特性,锁的设计,锁的状态进行归纳整理

  • 公平锁,非公平锁:公平锁是指多个线程需要按照申请锁的顺序排队,不允许插队,公平锁的优点是等待锁的线程不会被饿死。非公平锁是多个线程依靠竞争的方式获取锁,不需要排队。synchronized就是非公平锁,ReetrantLock通过构造参数可以决定是公平锁还是非公平锁
  • 可重入锁和非可重入锁:可重入锁又名递归锁。是指同一个线程在外层方法获取锁的时候,再进入该线程的内层方法会自动获取锁(前提锁对象得是同一个对象或者class),Java中ReetrantLock和synchronized都是可重入锁。可重入锁得一个优点是可以一定程度避免死锁。
  • 独享锁,共享锁。独享锁指的是锁一次只能被一个线程所持有。synchronized和JUC中的Lock的实现类就是独享锁。而ReetrantReadWriteLock的写锁是独享锁,读锁是共享锁。读锁的共享锁可保证并发读非常高效,而读写、写读、写写的过程互斥,因为读锁和写锁是分离的。所以ReentrantReadWriteLock的并发性相比一般的独享锁有了很大提升。
  • 乐观锁,悲观锁:是一种概念。悲观锁认为对于同一个数据的并发操作一定是会发生修改的,不加锁的操作一定有问题。乐观锁则认为对于同一个数据的并发操作是不会发生修改的,在更新数据的时候会采用不断地尝试更新,乐观锁认为不加锁地并发操作是没事地。乐观锁其实就是基于CAS的无锁编程,比如java 的原子类就是通过CAS自旋实现的。而sychronized和JUC的Lock都是悲观锁。
  • 偏向锁,轻量级锁,重量级锁:这种分类是按照锁的状态来归纳的,并且是针对synchronized的,java1.6为了减少获取锁和释放锁带来的性能的问题而引入的一种状态,其状态会随着竞争状况而逐渐升级,锁可以升级但不能降级,意味着偏向锁升级成轻量级锁后无法降为偏向锁,这种升级无法降级的策略目的就是为了提高获得锁和释放锁的效率
  • 自旋锁 :其实是相对于互斥锁的概念,互斥锁线程会进入WAITING状态和RUNNABLE状态的切换,设计上下文切换,cpu抢占等开销,自旋锁的线程一直是RUNNABLE状态的,一直在那循环检测锁的标志位,机制不重复,但是自旋锁全程消耗cpu,起始开销虽然低于互斥锁,但是持锁事件的加锁开销是线性增长。

sleep 、wait、yield 的区别,wait 的线程如何唤醒它?

首先要讲sleep,join,和yield这三个方法。

这三个方法都有一个共同点,它们都是从CPU运行层次来操作运行。用于控制CPU调度器。

1. Thread#sleep简述

暂停当前线程,进入超时等待状态,TIMED_WAITING状态

线程在休眠时不会释放锁

2. Thread#join

当前线程进入WAITING/TIMED_WAITING状态,直到join的线程结束或超时。当前线程不会释放已经持有的对象锁。

线程2,通过调用线程1的join方法,那么就会让线程2直接进入等待状态,直到线程1线程结束

3. Thread#yield

一定是当前线程调用此方法,当前线程放弃获取的CPU时间片,但不释放锁资源,由运行状态变为就绪状态,让OS再次选择线程。作用:让相同优先级的线程轮流执行,但并不保证一定会轮流执行。实际中无法保证yield()达到让步目的,因为让步的线程还有可能被线程调度程序再次选中。Thread.yield()不会导致阻塞。该方法与sleep()类似,只是不能由用户指定暂停多长时间。

4.Object#wait

object#wait方法是针对锁层次的。

在线程t1调用wait()方法时,会释放t1持有的锁,让t1进入等待或者超时等待状态,直到其他线程调用notify()方法或notifyAll()方法让t1进入同步队列状态。当t1获得锁会进入就绪状态Runnable,获得CPU调度权后会继续执行。

既然是释放当前线程的锁,那么必须有锁才行,而且必须用该锁的对象调用wait方法,否则会抛出异常。

注:其实就是线程将自己所持有的锁给交出来,然后停止运行,直到有线程调用notify或notifyAll方法通知它。

wait()notify(), notifyAll()方法在synchronized修饰的方法或者片段中调用,

如果使用ReetrantLock,等同的方法是condition.await,如果要唤醒该线程,则可以使用signal()或者signalAll()方法

更新:

  • 2.18日修改了错误的部分描述