java并发面试题-基础

多线程

  1. java中有几种方法可以实现一个线程?
    1.直接继承thread类;2.实现runnable接口;
  2. 如何停止一个正在运行的线程?可以使用正在运行的线程,支持线程中断,通常是定义一个volatile的状态变量,在运行线程线程中读这个变量,其它线程中修改这个变量
  3. notify()和notifyAll()有什么区别?答:
    notify()和notifyAll()都是Object对象用于通知处在等待该对象的线程的方法。
    void notify(): 唤醒一个正在等待该对象的线程。
    void notifyAll(): 唤醒所有正在等待该对象的线程。
    两者的最大区别在于:
    notifyAll使所有原来在该对象上等待被notify的线程统统退出wait的状态,变成等待该对象上的锁,一旦该对象被解锁,他们就会去竞争。
    notify他只是选择一个wait状态线程进行通知,并使它获得该对象上的锁,但不惊动其他同样在等待被该对象notify的线程们,当第一个线程运行完毕以后释放对象上的锁,此时如果该对象没有再次使用notify语句,即便该对象已经空闲,其他wait状态等待的线程由于没有得到该对象的通知,继续处在wait状态,直到这个对象发出一个notify
    notifyAll,它们等待的是被notify或notifyAll,而不是锁。
  4. sleep()和 wait()有什么区别?

    a:对于sleep()方法,我们首先要知道该方法是属于Thread类中的。而wait()方法,则是属于Object类中的。

    b:sleep()方法导致了程序暂停执行指定的时间,让出cpu该其他线程,但是他的监控状态依然保持者,当指定的时间到了又会自动恢复运行状态。

    在调用sleep()方法的过程中,线程不会释放对象锁。

    而当调用wait()方法的时候,线程会放弃对象锁,进入等待此对象的等待锁定池,只有针对此对象调用notify()方法后本线程才进入对象锁定池准备

    获取对象锁进入运行状态。

  5. 什么是Daemon线程?它有什么意义?                                                                                                                                                                                                      在Java中有两类线程:User Thread(用户线程)、Daemon Thread(守护线程) 。Daemon的作用是为其他线程的运行提供便利服务,比如垃圾回收线程就是一个很称职的守护者。User和Daemon两者几乎没有区别,唯一的不同之处就在于虚拟机的离开:如果 User Thread已经全部退出运行了,只剩下Daemon Thread存在了,虚拟机也就退出了。
  6. java如何实现多线程之间的通讯和协作?同步、wait|notify、管道、

  1. 什么是可重入锁(ReentrantLock)?      重入锁指的是在某一个线程中可以多次获得同一把锁,在线程中多次操作有锁的方法。                                                                                 答:  Java.util.concurrent.lock 中的 Lock 框架是锁定的一个抽象,它允许把锁定的实现作为 Java 类,而不是作为语言的特性来实现。这就为 Lock 的多种实现留下了空间,各种实现可能有不同的调度算法、性能特性或者锁定语义。 ReentrantLock 类实现了 Lock ,它拥有与 synchronized 相同的并发性和内存语义,但是添加了类似锁投票、定时锁等候和可中断锁等候的一些特性。此外,它还提供了在激烈争用情况下更佳的性能。(换句话说,当许多线程都想访问共享资源时,JVM 可以花更少的时候来调度线程,把更多时间用在执行线程上。)
              reentrant 锁意味着什么呢?简单来说,它有一个与锁相关的获取计数器,如果拥有锁的某个线程再次得到锁,那么获取计数器就加1,然后锁需要被释放两次才能获得真正释放。这模仿了 synchronized 的语义;如果线程进入由线程已经拥有的监控器保护的 synchronized 块,就允许线程继续进行,当线程退出第二个(或者后续)synchronized 块的时候,不释放锁,只有线程退出它进入的监控器保护的第一个 synchronized 块时,才释放锁。

    ReentrantLock 相对于固有锁synchronized,同样是可重入的,在某些vm版本上提供了比固有锁更高的性能,提供了更丰富的锁特性,比如可中断的锁,可等待的锁,平等锁以及非块结构的加锁。从代码上尽量用固有锁,vm会对固有锁做一定的优化,并且代码可维护和稳定。只有在需要ReentrantLock的一些特性时,可以考虑用ReentrantLock实现。

    ReentrantLock 和synchronized比较。来自《java并发编程实战》

    1.为什么JUC框架出现LOCK?

    ReentrantLock并不是替代synchronized的方法,而是当内置锁不适用时,作为一种可选的高级功能。

    2.那么Synchronized有哪些缺点?

    ①. 只有一个condition与锁相关联,这个condition是什么?就是synchronized对针对的对象锁。

    ②. synchronized无法中断一个正在等待获得锁的线程,也即多线程竞争一个锁时,其余未得到锁的线程只能不停的尝试获得锁,而不能中断。这种情况对于大量的竞争线程会造成性能的下降等后果。

    3.我们面对ReentrantLock和synchronized改如何选择?

    Synchronized相比Lock,为许多开发人员所熟悉,并且简洁紧凑,如果现有程序已经使用了内置锁,那么尽量保持代码风格统一,尽量不引入Lock,避免两种机制混用,容易令人困惑,也容易发生错误。

    在Synchronized无法满足需求的情况下,Lock可以作为一种高级工具,这些功能包括“可定时的、可轮询的与可中断的锁获取操作,公平队列,以及非块结构的锁”否则还是优先使用Synchronized。

    最后,未来更可能提升Synchronized而不是Lock的性能,因为Synchronized是JVM的内置属性,他能执行一些优化,例如对线程封闭的锁对象的锁消除优化,通过增加锁的粒度来消除内置锁的同步,而如果基于类库的锁来实现这些功能,则可能性不大。

  2. 当一个线程进入某个对象的一个synchronized的实例方法后,其它线程是否可进入此对象的其它方法?
  3. synchronized和java.util.concurrent.locks.Lock的异同?                                                                                                                                                                  答:Lock 和 synchronized 有一点明显的区别 —— lock 必须在 finally 块中释放。否则,如果受保护的代码将抛出异常,锁就有可能永远得不到释放!这一点区别看起来可能没什么,但是实际上,它极为重要。忘记在 finally 块中释放锁,可能会在程序中留下一个定时炸弹,当有一天炸弹爆炸时,您要花费很大力气才有找到源头在哪。而使用同步,JVM 将确保锁会获得自动释放。
  4.    一个 Lock 对象和一个 synchronized 代码块之间的主要不同点是:
    • synchronized 代码块不能够保证进入访问等待的线程的先后顺序。
    • 你不能够传递任何参数给一个 synchronized 代码块的入口。因此,对于 synchronized 代码块的访问等待设置超时时间是不可能的事情。
    • synchronized 块必须被完整地包含在单个方法里。而一个 Lock 对象可以把它的 lock() 和 unlock() 方法的调用放在不同的方法里。
  5. 乐观锁和悲观锁的理解及如何实现,有哪些实现方式?

    乐观锁,每次操作时不加锁而是假设没有冲突而去完成某项操作,如果因为冲突失败就重试,直到成功为止

    悲观锁是会导致其它所有需要锁的线程挂起,等待持有锁的线程释放锁。

    乐观锁可以使用volatile+CAS原语实现,带参数版本来避免ABA问题,在读取和替换的时候进行判定版本是否一致

    悲观锁可以使用synchronize的以及Lock

并发框架

  1. SynchronizedMap和ConcurrentHashMap(效率高)有什么区别?                                                                                                                                                             答:java5中新增了ConcurrentMap接口和它的一个实现类ConcurrentHashMap。ConcurrentHashMap提供了和Hashtable以及SynchronizedMap中所不同的锁机制。比起synchronizedMap来,它提供了好得多的并发性。多个读操作几乎总可以并发地执行,同时进行的读和写操作通常也能并发地执行,而同时进行的写操作仍然可以不时地并发进行(相关的类也提供了类似的多个读线程的并发性,但是,只允许有一个活动的写线程)。Hashtable中采用的锁机制是一次锁住整个hash表,从而同一时刻只能由一个线程对其进行操作;而ConcurrentHashMap中则是一次锁住一个桶。ConcurrentHashMap默认将hash表分为16个桶,诸如get,put,remove等常用操作只锁当前需要用到的桶。这样,原来只能一个线程进入,现在却能同时有16个写线程执行,并发性能的提升是显而易见的。前面说到的16个线程指的是写线程,而读操作大部分时候都不需要用到锁。只有在size等操作时才需要锁住整个hash表。
           在迭代方面,ConcurrentHashMap使用了一种不同的迭代方式。在这种迭代方式中,当iterator被创建后集合再发生改变就不再是抛出ConcurrentModificationException,取而代之的是在改变时new新的数据从而不影响原有的数据 ,iterator完成后再将头指针替换为新的数据 ,这样iterator线程可以使用原来老的数据,而写线程也可以并发的完成改变。
     ConcurrentHashMap把实际map划分成若干部分来实现它的可扩展性和线程安全。这种划分是使用并发度获得的,它是ConcurrentHashMap类构造函数的一个可选参数,默认值为16,这样在多线程情况下就能避免争用。另外一个不同点是,在被遍历的时候,即使是 ConcurrentHashMap 被改动,它也不会抛 ConcurrentModificationException。尽管 Iterator 的设计不是为多个线程的同时使用。
  2. CopyOnWriteArrayList可以用于什么应用场景?                                                                                                                                                                                               多读少写的场景。读写并不是在同一个对象上。在写时会大面积复制数组,所以写的性能差,在写完成后将读的引用改为执行写的对象。CopyOnWriteArrayList(免锁容器)的好处之一是当多个迭代器同时遍历和修改这个列表时,不会抛出ConcurrentModificationException。在CopyOnWriteArrayList中,写入将导致创建整个底层数组的副本,而源数组将保留在原地,使得复制的数组在被修改时,读取操作可以安全地执行。
  3. 如何让一段程序并发的执行,并最终汇总结果?
     答:使用CyclicBarrier 和CountDownLatch都可以,使用CyclicBarrier 在多个关口处将多个线程执行结果汇总,CountDownLatch 在各线程执行完毕后向总线程汇报结果。

  4.  如何合理的配置java线程池?如CPU密集型的任务,基本线程池应该配置多大?IO密集型的任务,基本线程池应该配置多大?用有界队列好还是无界队列好?任务非常多的时候,使用什么阻塞队列能获取最好的吞吐量?

     答:1)配置线程池时CPU密集型任务可以少配置线程数,大概和机器的cpu核数相当,可以使得每个线程都在执行任务
            2)IO密集型时,大部分线程都阻塞,故需要多配置线程数,2*cpu核数
            3)有界队列和无界队列的配置需区分业务场景,一般情况下配置有界队列,在一些可能会有爆发性增长的情况下使用无界队列。
            4)任务非常多时,使用非阻塞队列使用CAS操作替代锁可以获得好的吞吐量。

线程安全

  1. 什么叫线程安全?servlet是线程安全吗?                                                                                                                                                                                                 答:如果你的代码所在的进程中有多个线程在同时运行,而这些线程可能会同时运行这段代码。如果每次运行结果和单线程运行的结果是一样的,而且其他的变量的值也和预期的是一样的,就是线程安全的。 或者说:一个类或者程序所提供的接口对于线程来说是原子操作或者多个线程之间的切换不会导致该接口的执行结果存在二义性,也就是说我们不用考虑同步的问题。
                servlet不是线程安全的,每个servlet都只被实例化一次,每个调用都是servlet的同一个实例,并且对类变量没有线程安全,数据量大的时候容易照成异常。
  2. 同步有几种实现方法?
    同步的实现方法有五种:1.同步方法;2.同步代码块;3.使用特殊域变量(volatile)实现线程同步;4.使用重入锁实现线程同步;5.使用局部变量实现线程同步 。
        同步的实现方面有两种,分别是synchronized,wait与notify wait():使一个线程处于等待状态,并且释放所持有的对象的lock。
      sleep():使一个正在运行的线程处于睡眠状态,是一个静态方法,调用此方法要捕捉InterruptedException异常。
      notify():唤醒一个处于等待状态的线程,注意的是在调用此方法的时候,并不能确切的唤醒某一个等待状态的线程,而是由JVM确定唤醒哪个线程,而且不是按优先级
      notifyAll():唤醒所有处入等待状态的线程。
  3. notify和notifyall的区别?    答:1)notify()和notifyAll()都是Object对象用于通知处在等待该对象的线程的方法。
                   2)void notify(): 唤醒一个正在等待该对象的线程。
                   3)void notifyAll(): 唤醒所有正在等待该对象的线程。
            两者的最大区别在于:
                   notifyAll使所有原来在该对象上等待被notify的线程统统退出wait的状态,变成等待该对象上的锁,一旦该对象被解锁,他们就会去竞争。
             notify他只是选择一个wait状态线程进行通知,并使它获得该对象上的锁,但不惊动其他同样在等待被该对象notify的线程们,当第一个线程运            行完毕以后释放对象上的锁,此时如果该对象没有再次使用notify语句,即便该对象已经空闲,其他wait状态等待的线程由于没有得到该对象的          通知,继续处在wait状态,直到这个对象发出一个notify或notifyAll,它们等待的是被notify或notifyAll,而不是锁。
  4. sleep()和 wait()有什么区别?
            答:sleep是线程类(Thread)的方法,导致此线程暂停执行指定时间,给执行机会给其他线程,但是监控状态依然保持,到时后会自动恢复。调用sleep不会释放对象锁。
    wait是Object类的方法,对此对象调用wait方法导致本线程放弃对象锁,进入等待此对象的等待锁定池,只有针对此对象发出notify方法(或notifyAll)后本线程才进入对象锁定池准备获得对象锁进入运行状态。
  5. volatile有什么用?能否用一句话说明下volatile的应用场景?                                                                                                                                                                       答:Volatile 变量具有 synchronized 的可见性特性,但是不具备原子特性。可以被看作是一种 “程度较轻的 synchronized”;与 synchronized 块相比,volatile 变量所需的编码较少,并且运行时开销也较少,但是它所能实现的功能也仅是 synchronized 的一部分。
              您只能在有限的一些情形下使用 volatile 变量替代锁。要使 volatile 变量提供理想的线程安全,必须同时满足下面两个条件:
                              A. 对变量的写操作不依赖于当前值。
                              B. 该变量没有包含在具有其他变量的不变式中。

    首先总结一下Volatile的特性:可见性,但不互斥.怎么理解这句话,首先可见性的原因是以为,这个关键字可以让变量不缓存在寄存器里面,每次取值都是直接从主存里面获取,所以每次都是最新的值.但是不互斥是因为没有锁,这里有个改变值的流程(读取-修改-写入),这是一个比读更耗时的一个操作,在没有加锁的情况下别的线程读取这个值可能是任何一个时刻的值;所以根据这个特性可以推导出使用Volatile在少写多读的情况下,性能非常好,当然首先要保证不会是多线程同时写.

    Volatile有五个使用场景(参考Brian Goetz (brian.goetz@sun.com)的文章):

    1.作为状态标志

    2.一次性安全发布

    3.独立观察

    4.volatile bean模式

    5.开销较低的读写锁策略

  6. 请说明下java的内存模型及其工作流程。                                                                                                                                                                                                         答:Java把内存划分成两种:一种是栈内存,一种是堆内存。
              栈内存:存放对象:函数中基本类型的变量和对象的引用变量、静态类方法 ;特点:栈有一个很重要的特殊性,就是存在栈中的数据可以共享。
             堆内存:存放对象:用来存放由new创建的对象和数组;特点:在堆中分配的内存,由Java虚拟机的自动垃圾回收器来管理。
              java 内存模型 ( java memory model ):根据Java Language Specification中的说明, jvm系统中存在一个主内存(Main Memory或Java Heap Memory),Java中所有对象成员变量都储存在主存中,对于所有线程都是共享的。每条线程都有自己的工作内存(Working Memory),工作内存中保存的是主存中某些对象成员变量的拷贝,线程对所有对象成员变量的操作都是在工作内存中进行,线程之间无法相互直接访问,变量传递均需要通过主存完成。
                    (1) 获取对象监视器的锁(lock)
                    (2) 清空工作内存数据, 从主存复制对象成员变量到当前工作内存, 即同步数据 (read and load)
                    (3) 执行代码,改变共享变量值 (use and assign)
                    (4) 将工作内存数据刷回主存 (store and write)
                    (5) 释放对象监视器的锁 (unlock)
  7. 为什么代码会重排序?
  8. Java线程池中submit() 和 execute()方法有什么区别?

    两个方法都可以向线程池提交任务,execute()方法的返回类型是void,它定义在Executor接口中, 而submit()方法可以返回持有计算结果的Future对象,它定义在ExecutorService接口中,它扩展了Executor接口,其它线程池类像ThreadPoolExecutor和ScheduledThreadPoolExecutor都有这些方法。

  9. thread类中的yield方法有什么作用?

    Yield方法可以暂停当前正在执行的线程对象,让其它有相同优先级的线程执行。它是一个静态方法而且只保证当前线程放弃CPU占用而不能保证使其它线程一定能占用CPU,执行yield()的线程有可能在进入到暂停状态后马上又被执行。

  10. .什么场景下可以使用volatile替换synchronized?

    只需要保证共享资源的可见性的时候可以使用volatile替代,synchronized保证可操作的原子性一致性和可见性。 volatile适用于新值不依赖于就值的情形

原文地址:https://www.cnblogs.com/esther-qing/p/7216037.html