并发编程面试题

1.Synchronized的原理

https://www.cnblogs.com/zaizhoumo/p/7700161.html

   Java的每一个对象都可以作为锁,当一个线程访问时,首先要获取锁对象,退出代码块或抛出异常时才会释放锁。

   常见的三种用法:  

     普通方法,

     静态方法

       同步代码块

  普通方法和静态方法实现锁 是通过ACC_SYNCHRONIZED标志,同步代码块通过MonitorEnter和MoniterExit指令实现的,在编译代码的过程中将MonitorEnter指令插入到开始的位置。MoniterExit指令被插入到代码结束和抛出异常的位置 

2.对象锁,这个锁到底是什么,如何确定对象锁。

  对象锁锁住的是,同样有Synchronized修饰的方法和代码段,这里排除类锁

3.什么是可重入,为什么说Synchronized 是可重入锁

  当线程请求一个由其它线程持有的对象锁时,该线程会阻塞,而当线程请求由自己持有的对象锁时,如果该锁是重入锁,请求就会成功,否则阻塞。

  同一线程在调用自己类中其他 synchronized 方法/块或调用父类的 synchronized 方法/块都不会阻碍该线程的执行。就是说同一线程对同一个对象锁是可重入的,而且同一个线程可以获取同一把锁多次,也就是可以多次重入

4.JVM对Java的原生锁做了哪些优化。

轻量级锁、偏向锁、适应性自旋、锁粗化、锁消除

5.为什么说Synchronized是非公平锁。

是非公平锁

  当一个线程想获取锁时,先试图插队,如果占用锁的线程释放了锁,下一个线程还没来得及拿锁,那么当前线程就可以直接获得锁;如果锁正在被其它线程占用,则排队,排队的时候就不能再试图获得锁了,只能等到前面所有线程都执行完才能获得锁。

6.什么是锁消除和所粗化?

  锁粗化就是告诉我们任何事情都有个度,有些情况下我们反而希望把很多次锁的请求合并成一个请求,以降低短时间内大量锁请求、同步、释放带来的性能损耗。

  锁消除是Java虚拟机在JIT编译是,通过对运行上下文的扫描,去除不可能存在共享资源竞争的锁,通过锁消除,可以节省毫无意义的请求锁时间。

7.为什么说Synchronized是一个悲观锁?乐观锁的实现原理?什么是cas,他有什么特性?

  1.不管是否会产生竞争,任何的数据操作都必须要加锁、用户态核心态转换、维护锁计数器和检查是否有被阻塞的线程需要被唤醒等操作

  2.先进行操作,如果没有其他线程征用数据,那操作就成功了;如果共享数据有征用,产生了冲突,那就再进行其他的补偿措施。这种乐观的并发策略的许多实现不需要线程挂起,所以被称为非阻塞同步。

  3.乐观锁的核心算法是CAS(CompareandSwap,比较并交换),它涉及到三个操作数:内存值、预期值、新值。当且仅当预期值和内存值相等时才将内存值修改为新值。这样处理的逻辑是,首先检查某块内存的值是否跟之前我读取时的一样,如不一样则表示期间此内存值已经被别的线程更改过,舍弃本次操作,否则说明期间没有其他线程对此内存值操作,可以把新值设置给此块内存。

  4.CAS具有原子性,它的原子性由CPU硬件指令实现保证,即使用JNI调用Native方法调用由C++编写的硬件级别指令,JDK中提供了Unsafe类执行这些操作。

8.乐观锁一定就好嘛?

9.跟Synchronized相比可重入锁ReentranLock其实现原理有什么不同?

  可重入性

    两个都是可重入锁,都是同一线程没进入一次,锁的计数器都自增1,所以要等到所得计数器下降为0才能释放锁。
  锁的实现
    Synchronized是依赖于JVM实现的,而ReenTrantLock是JDK 实现的
  性能的区别
    在Sunchronized优化以前,synchronized的性能势必ReenTrantLock差很多,但是从Sychronized引入了偏向锁,轻量级锁(自旋锁)后,两者的性能就差不多,其实synchronized的优化我感觉借鉴了ReenTrantLock中的CAS 技术。都是试图在用户台就把加锁问题解决,避免进入内核态的线程阻塞
  功能区别
    便利性:很明显Synchronized的使用比较简洁,并且由编译器去保证所得加锁和释放,而ReenTrantLock需要手工声明加锁和释放锁,为了避免忘记手工释放锁造成死锁,所以最好在finally中声明释放锁
    锁的细粒度和灵活度:很明显高ReenTrantLock优于Synchronized
  ReenTrantLock独有的能力
    1.ReenTrantLock可以指定是公平锁还是非公平锁。而synchronized只是非公平锁。所谓的公平锁就是先等待的线程先获取锁
    2.ReenTrantLock提供了一个Condition(条件)类,用来实现分组唤醒需要唤醒的线程们,而不是像synchronized要么随机唤醒一个线程要么唤醒全部线程
    3.ReenTrantLock提供了一种能够终端等待所得线程机制,通过lock.lockInterruptibly()来实现这个机制
  ReenTrantLock实现的原理:
    ReenTrantLock的实现是一种自旋锁,通过循环调用CAS操作来实现加锁。它的性能比较好也是因为避免了使线程进入内核态的阻塞状态。想尽办法避免线程进入内核的阻塞状态是我们去分析和理解锁设计的关键钥匙。

10。AQS框架是怎么回事?

11.请尽可能详尽的对比下Synchronized和ReenTrantLock的异同

  1.reentrantLock比Synchronized增加了一些高级功能
    1.lockInterruotiby()的使用
      线程t1一直占用者锁不释放,此t2一直在等待着锁,通过lockInterrupt()可以通知线程t2停止等待,而使用synchronized方法,只能继续等待。
    2.使用tryLock进行尝试锁定,不管锁定与否,方法都将继续执行,可以根据tryLock的返回值来判断是否锁定,也可以置顶tryLock的时间。
  2.可实现公平锁
    公平锁是指多个线程在等待同一个锁时,必须按照申请所得时间顺序来一次获得锁,而非公平锁不保证这一点,在锁被释放时,任何一个等待锁的线程都有机会获得锁,ReenrantLock默认情况下也是非公平锁但是可以在构造函数中设置为公平锁。ReenTrantLock(boolean fair)
  3.锁可以绑定多个条件
    一个ReenTrantLock对象可以同时绑定多个Conndition(即对象监视器)实例,线程对象可以注册在指定的Condition中从而可以有选择性地进行线程通知。
  4.线程阻塞在进入synchronized关键字修饰的方法或代码块(获取锁)时的状态称为阻塞状态。但是阻塞在Lock接口的线程状态却是等待状态,因为Lock接口对于阻塞的实现均使用了LockSupport类中的相关方法。

12.ReenTrantLock是如何实现可重入性的?

  利用AQS的的state状态来判断资源是否已锁,同一线程重入加锁,state的状态+1; 同一线程重入解锁,state状态-1(解锁必须为当前独占线程,否则异常); 当state为0时解锁成功。

13.除了ReenTrantLock,你还接触过JUC中的那些并发工具?

14.ReadWriteLock和stampedLock

15.如何让java的线程彼此同步,你了解过哪些同步器?

16.CyclicBarrier和CountDownLatch看起来很相似?

https://www.cnblogs.com/twoheads/p/9555867.html

  CyclicBarrier 的字面意思是可循环使用(Cyclic)的屏障(Barrier)。它要做的事情是,让一组线程到达一个屏障(也可以叫同步点)时被阻塞,直到最后一个线程到达屏障时,屏障才会开门,所有被屏障拦截的线程才会继续干活

  • CountDownLatch:一个或者多个线程,等待其他多个线程完成某件事情之后才能执行;
  • CyclicBarrier:多个线程互相等待,直到到达同一个同步点,再继续一起执行。

17.java线程池是如何实现的?

  在Java中 所谓的线程池中的线程,其实是被抽象为一个静态内部类worker,它基于aqs实现,存放在线程池的hashSet<worker> workers成员变量中;而需要执行的任务则存放在成员变量workQueue(BlockingQueue<Runnable> workQueue)中。这样,整个线程池实现的基本思路就是从workQueue中不断取出需要执行任务,放在workers中进行处理。

18.创建线程池有几个核心构造参数?

  corePoolSize 核心线程数

  maxPoolSize 最大线程数

  keepAliveTime 当线程空闲时间达到keepAliveTime,该线程会退出,直到线程数量等于corePoolSize

  workQueue 任务执行前保存任务的队列保存由excute方法提交的Runnable

19.线程池中的线程是怎么创建的?是一开始就随着线程池的启动创建好的嘛?

  线程池默认初始化后不启动worker,等待有请求时才会启动

20。既然提到可以通过配置不同参数创建出不同的线程池,那么java中默认实现好的线程池有哪些,他们异同?

  1.singleTreadExecutor

  2.fixThreadpool

  3.cachedThreadPool

  4.scheduleThreadPool

21.如何在线程池中提交线程?

  execute() : ExecutorService.execute()方法接受一个Runable实例,它用来执行一个任务

  submit():ExecutorService.submit()方法返回的是future对象。可以用isDone()来查询Future是否已经完成,当任务完成时,它具有有一结果,可以调用get()来获取结果。也可以不用isDone()进行检查就直接调用get(),在这种情况下,get()会阻塞,直至结果准备就绪。

22.什么是java内存模型,java中各个线程是怎么彼此看到对方变量的?

  1.java内存模型定义了程序中各个变量的访问规则,即在虚拟机中将变量存储到内存和从内存中取出这样的底层细节。此处的变量包括实例字段、静态字段和构造成数组对象的元素,但是不包括局部变量和方法参数,因为这些是线程私有的,不会被共享,所以 不存在竞争问题。

  2.java中定义了主内存的概念:所有的变量都在存储在主内存,每条线程还有自己的工作内存,保存了被该线程使用到的变量的主内存副本拷贝。线程对变量的所有操作(读取、赋值)都必须在工作内存的变量。不同线程之间也无法直接访问对方的工作内存。

23.volatile有什么特点,为什么他能保证 变量对所有线程的可见性?

  关键字volatile是java虚拟机提供的最轻量级的同步机制。当一个变量被定义成volatile之后具备两种特性:

    1.保证此变量对所有线程的可见性。当一天线程修改了这个变量的值,心智UI与其他线程是可以立即得知的,而普通变量做不到这一点。

    2.禁止指令重排序优化,普通变量仅仅能保证在该方法执行过程中,得到正确的结果,但是不能保证代码的执行顺序。

24.既然volatile能够保证线程间的变量可见性,是不是volatile变量的运算就是并发安全的?

  volatile变量在各个线程的工作内存,不存在一致性问题(各个线程的工作内存中volatile变量,每次使用前都要刷新到主内存)。但是java里面的运算并非原子操作,导致volatile变量的 运算并发下一样是不安全

25.请对比一下volatile和synchronized的异同?

  volatile的本质是告诉jvm当前变量在寄存器(工作内存)中的值是不确定的需要从主内存去取;synchronized是锁定当前变量,只有当前线程可以访问该变量,其他线程都是被阻塞住的直到该线程完成变量的操作。

  volatile只能只用在变量级别,synchronized可以使用在变量,方法,类级别上

  volatile只能保证变量的修改可见性,不能保证原子性,synchronized可以保证变量修改的可见性也可以保证原子性

  volatile不会造成线程的阻塞,synchronized会造成线程阻塞

  volatile标记的变量不会被编译器优化,synchronized标记的变量可以被编译器优化

26.请谈谈threadLocal是怎么解决线程并发问题的?

一般用ThreadLocal都不会将一个共享变量放到线程的ThreadLocal中。一般来讲,存放到ThreadLocal中的变量都是当前线程
本身就独一无二的一个变量。其他线程本身就不能访问,存到ThreadLocal中只是为了方便在程序中同一个线程之间传递这个变量。

27.很多人都说要慎用ThreadLocal,谈谈你的理解,使用threadLocal需要注意什么?

 

posted @ 2019-07-23 14:47  333-  阅读(841)  评论(0编辑  收藏  举报