java后台面试题整理及解答(三)Java 并发

线程安全,是Java并发编程中的重要关注点,应该注意到的是,造成线程安全问题的主要原因有两点:
1,存在共享数据(也称临界资源)
2,存在多条线程,共同操作共享数据。

1、synchronized 的实现原理以及锁优化?

     synchronized的作用主要有三个:

    1. 确保线程互斥的访问代码
    2. 保证共享变量的修改能够及时可见(可见性)
    3. 可以阻止JVM的指令重排序

      在Java中所有对象都可以作为锁,这是synchronized实现同步的基础。
      synchronized主要有三种应用方式:

    1. 普通同步方法,锁的是当前实例的对象
    2. 静态同步方法,锁的是静态方法所在的类对象
    3. 同步代码块,锁的是括号里的对象。(此处的可以是实例对象,也可以是类的class对象。)

详细可见:https://blog.csdn.net/qq_26222859/article/details/53786134

2、volatile 的实现原理?

Java语言规范对volatile的定义如下:

Java编程语言允许线程访问共享变量,为了确保共享变量能被准确和一致地更新,线程应该确保通过排他锁单独获得这个变量。

通俗点讲就是说一个变量如果用volatile修饰了,则Java可以确保所有线程看到这个变量的值是一致的,如果某个线程对volatile修饰的共享变量进行更新,那么其他线程可以立马看到这个更新,这就是所谓的线程可见性,也相当于大家实时共享这个变量,变量以改变大家都会知道。

反正我个人到现在使用起来也还不是很说的清楚。

底层具体如何我就直接整理他人的链接:https://blog.csdn.net/xuxile/article/details/73732647 

3、Java 的信号灯?

先知道一下Semaphore的概念(往往结合线程池使用)
Semaphore是Java1.5之后提供的一种同步工具,Semaphore可以维护访问自身线程个数,并提供了同步机制。使用Semaphore可以控制同时访问资源的线程个数,通过 acquire() 获取一个许可,如果没有就等待,而release() 释放一个许可。

引用:https://blog.csdn.net/u011613354/article/details/51150248

4.怎么实现所有线程在等待某个事件的发生才会去执行?

方案一:读写锁
  刚开始主线程先获取写锁,然后所有子线程获取读锁,然后等事件发生时主线程释放写锁;

方案二:CountDownLatch
  CountDownLatch初始值设为1,所有子线程调用await方法等待,等事件发生时调用countDown方法计数减为0;

方案三:Semaphore
  Semaphore初始值设为N,刚开始主线程先调用acquire(N)申请N个信号量,其它线程调用acquire()阻塞等待,等事件发生时同时主线程释放N个信号量;

原文:https://blog.csdn.net/yangguosb/article/details/80339001

5.什么是CAS?

CAS是compare and swap的缩写,即我们所说的比较交换。cas是一种基于锁的操作,而且是乐观锁。在java中锁分为乐观锁和悲观锁。悲观锁是将资源锁住,等一个之前获得锁的线程释放锁之后,下一个线程才可以访问。而乐观锁采取了一种宽泛的态度,通过某种方式不加锁来处理资源,比如通过给记录加version来获取数据,性能较悲观锁有很大的提高。

CAS 操作包含三个操作数 —— 内存位置(V)、预期原值(A)和新值(B)。如果内存地址里面的值和A的值是一样的,那么就将内存里面的值更新成B。CAS是通过无限循环来获取数据的,若果在第一轮循环中,a线程获取地址里面的值被b线程修改了,那么a线程需要自旋,到下次循环才有可能机会执行。

原文:https://blog.csdn.net/u011381576/article/details/79922538

6.HashMap 的并发问题

多线程put后可能导致get死循环

我们就知道HashMap被多个线程操作。而Java的文档说HashMap是非线程安全的,应该用ConcurrentHashMap。

转自:https://my.oschina.net/xianggao/blog/393990?fromerr=OPResiVf

7.AQS

AQS(AbstractQueuedSynchronizer),AQS是JDK下提供的一套用于实现基于FIFO等待队列的阻塞锁和相关的同步器的一个同步框架。这个抽象类被设计为作为一些可用原子int值来表示状态的同步器的基类。如果你有看过类似 CountDownLatch 类的源码实现,会发现其内部有一个继承了 AbstractQueuedSynchronizer 的内部类 Sync。可见 CountDownLatch 是基于AQS框架来实现的一个同步器.类似的同步器在JUC下还有不少。

8.如何检测死锁?怎么预防死锁?

首先什么是死锁

所谓死锁是指多个线程因竞争资源而造成的一种僵局(互相等待),若无外力作用,这些进程都将无法向前推进。

就是A等B,B等C,C却要等A。三个人就无线等了。

产生死锁的四个必要条件

(1) 互斥条件:一个资源每次只能被一个进程(线程)使用。
(2) 请求与保持条件:一个进程(线程)因请求资源而阻塞时,对已获得的资源保持不放。
(3) 不剥夺条件 : 此进程(线程)已获得的资源,在末使用完之前,不能强行剥夺。
(4) 循环等待条件 : 多个进程(线程)之间形成一种头尾相接的循环等待资源关系。

可以使用 jstack或者pstack 和 gdb 工具对死锁程序进行分析。

pstack: 功能是打印输出此进程的堆栈信息。可以输出所有线程的调用关系栈

jstack:jstack是java虚拟机自带的一种堆栈跟踪工具,所以仅适用于java程序,功能跟pstack一样,但是更强大,可以提示哪个地方可能死锁了。

pstack和jstack判断死锁,都需要多执行几次命令,观察每次的输出结果,才能推测是否死锁了。

具体::https://blog.csdn.net/ls5718/article/details/51896159

9.Java 内存模型?

定义Java内存模型并不是一件容易的事情,这个模型必须定义得足够严谨,才能让Java的并发操作不会产生歧义;但是,也必须得足够宽松,使得虚拟机的实现能有足够的自由空间去利用硬件的各种特性(寄存器、高速缓存等)来获取更好的执行速度。经过长时间的验证和修补,在JDK1.5发布后,Java内存模型就已经成熟和完善起来了。

 Java内存模型的主要目标是定义程序中各个变量的访问规则,即在虚拟机中将变量存储到内存和从内存中取出变量这样底层细节。此处的变量与Java编程时所说的变量不一样,指包括了实例字段、静态字段和构成数组对象的元素,但是不包括局部变量与方法参数,后者是线程私有的,不会被共享。

  Java内存模型中规定了所有的变量都存储在主内存中,每条线程还有自己的工作内存(可以与前面将的处理器的高速缓存类比),线程的工作内存中保存了该线程使用到的变量到主内存副本拷贝,线程对变量的所有操作(读取、赋值)都必须在工作内存中进行,而不能直接读写主内存中的变量。不同线程之间无法直接访问对方工作内存中的变量,线程间变量值的传递均需要在主内存来完成,线程、主内存和工作内存的交互关系

具体可见:https://www.cnblogs.com/nexiyi/p/java_memory_model_and_thread.html

10.阻塞队列以及各个阻塞队列的特性

先说说什么叫做阻塞队列:阻塞队列是一个支持两个附加操作的队列。这两个附加的操作支持阻塞的插入和移除方法。

支持阻塞的插入方法:意思是当队列满时,队列会阻塞插入元素的线程,知道队列不满。

支持阻塞的移除方法:意思是当队列为空时,获取元素的线程会等待队列变为非空。

阻塞队列主要分为以下7类:

ArrayBlockingQueue:一个由数组结构组成的有界阻塞队列。

LinkedBlockingQueue:一个由链表结构组成的有界(默认是无界的,可以自行设置和可重入锁相似,默认是非公平锁,但是可以进行设置)阻塞队列。

PriorityBlockingQueue:一个支持优先级排序的无界阻塞队列。

DelayQueue:一个使用优先级队列实现的无界阻塞队列

SynchronousQueue:一个不存储元素的阻塞队列。

LinkedTransfer:一个链表结构组成的无界阻塞队列。

LinkedBlockingDeque:一个由链表结构组成的双向阻塞队列。

原文:https://blog.csdn.net/jarvan_song/article/details/52218110

11.线程池如何调优,最大数目如何确认?

   在JAVA中,线程可以使用定制的代码来管理,应用也可以利用线程池。在使用线程池时,有一个因素非常关键:调节线程池的大小对获得最好的性能至关重要。线程池的性能会随线程池大小这一基本选择而有所不同,在某些条件下,线程池过大对性能也有很多不利的影响。

    所有线程池的工作方式本质是一样的:有一个任务队列,一定数量的线程会从该任务队列获取任务然后执行。任务的结果可以发回客户端,或保存到数据库,或保存到某个内部数据结构中,等等。但是在执行完任务后,这个线程会返回任务队列,检索另一个任务并执行。

    线程池有最小线程数和最大线程数。池中会有最小数目的线程随时待命,等待任务指派给它们。因为创建线程的成本非常高昂,这样可以提高任务提交时的整体性能。线程池的最小线程数称作核心池大小,考虑ThreadPoolExecutor最简单的情况,如果有个任务要执行,而所有的并发线程都在忙于执行另一个任务,就会启动一个新线程,直到创建的线程达到最大线程数。

原文:https://www.cnblogs.com/jianzh5/p/6437315.html

12.LockSupport工具

 LockSupport是JDK中比较底层的类,用来创建锁和其他同步工具类的基本线程阻塞原语,包含一个park(Object blocker)和unpark(Object blocker)方法,分别用于阻塞和唤醒。

简书:https://www.jianshu.com/p/ceb8870ef2c5

猜你喜欢

转载自www.cnblogs.com/YjfDIY/p/9835667.html