首页 > 编程语言 >java 并发编程面试(1)

java 并发编程面试(1)

时间:2024-06-24 11:12:39浏览次数:29  
标签:java synchronized CAS 编程 public 并发 线程 CPU 偏向

一、单例模式的DCL为啥要加volatile?

避免指令重排,获取到未初始化完成的对象。

单例模式的懒汉模式确保线程安全的机制DCL

public class MyTest {

  private static MyTest myTest;

  public static MyTest getInstance(){

    if(myTest == null) { // check

      synchronized (MyTest.class) { // lock

        if(myTest == null) { // check

          myTest = new MyTest();

         }

      }

    }

    return myTest;

   }

}

DCL正常可以解决单例模式的安全问题,但是由于CPU可能会对程序的一些指令做出重新的排序,导 致出现拿到一些未初始化完成的对象去操作,最常见的就是出现了诡异的NullPointException。

(扩展一下)volatile修饰myTest对象后,可以禁止CPU做指令重排。volatile的生成字节码指令后 有内存屏障(指令),内存屏障会被不同的CPU翻译成不同的函数,比如X86的CPU,会对 StoreLoad内存屏障翻译成mfence的函数,最终的指令就是lock前缀指令。

Java中new对象,可以简单的看成三个指令的操作。

1、开辟内存空间

2、初始化对象内部属性

3、将内存空间的地址赋值给引用

二、CAS实现原理

public class MyTest {

  private int value = 1;

  public static void main(String[] args) throws Exception {

    MyTest test = new MyTest();

    Unsafe unsafe = null;

    Field field = Unsafe.class.getDeclaredField("theUnsafe");

    field.setAccessible(true);

    unsafe = (Unsafe) field.get(null);

    // 获取内存偏移量

     long offset = unsafe.objectFieldOffset(MyTest.class.getDeclaredField("value"));

    // 执行CAS,这里的四个参数分别代表什么,你也要清楚~

    System.out.println(unsafe.compareAndSwapInt(test, offset, 0, 11));

    System.out.println(test.value);

  }

}

CAS就是将内存中的某一个属性,从oldValue,替换为newValue。保证原子性。

1、Java层面如何实现的CAS以及使用。

在Java中,是基于Unsafe类提供的native方法实现的。native是走的C++的依赖库

public final native boolean cas(Object 哪个对象, long 内存偏移量, Object 旧值, Object 新值);

public final native boolean compareAndSwapInt(Object var1, long var2, int var4, int var5);

public final native boolean compareAndSwapLong(Object var1, long var2, long var4, long var6);

Unsafe类,不能直接new,只能通过反射的形式获取到Unsafe的实例去操作,不过一般业务开发 中,基本不会直接使用到Unsafe类。

2、Java的CAS在底层是如何实现的。

Java层面的CAS,只到native方法就没了。底层是C++实现的,但是其实比较和交换(CAS),是CPU支持的原语。cmpxchg指令就是CPU支持的原语。

如果在CPU层面,多核CPU并行执行CAS修改同一个属性,可能会导致出现问题。C++内部就可以看到针对cmpxchg指令前追加了lock前缀指令(多核CPU)

3、CAS存在的一些问题

ABA问题: 要修改的数据,最开始是A,但是你没修改成功,期间经过一些列的操作,后来又 变回了A,此时你CAS会成功。 但是这个数据在最开始的A ---- 最后的A,这期间发生了什么事 情,咱不清楚。如果业务有要求这个期间发生的问题也要纠结一下,那么你就需要换一种CAS的 实现实现。利用版本号来确认。Java中提供了解决这种ABA问题的原子类。

public class AtomicStampedReference<V> {

   private static class Pair<T> {

    final T reference; // 你要修改的值

    final int stamp; // 版本号,你可以自行制定 戳~

  }

}

性能问题: CAS的性能嘎嘎快,一个层面是他属于CPU原语层面上的指令。还有一个优点, CAS会返回成功还是失败,不会挂起线程。但是如果基于while这种循环操作去调度CAS直到成 功,那可能会优点消耗CPU的资源了,一直执行CAS指令,但是一段是时间无法成功。 如果你 感觉短期内就能ok,那就上CAS,如果不成,使用悲观锁(synchronized,lock锁)

自旋锁,CAS,乐观锁,自适应自旋锁。

(1)乐观锁:是一种泛指,Java有Java的乐观锁实现,MySQL也有自己的乐观锁实现。(不会挂起 线程)

(2)悲观锁:也是一种泛指,认为拿不到资源,拿不到就挂起线程。

(3)CAS:Java中的乐观锁实现,是CAS。CAS对于Java来说,就是一个方法,做一次比较和交换。 (不会挂起线程,线程的状态从运行到阻塞)

(4)自旋锁:你可以自己实现,就是循环去执行CAS,知道成功为止。

   while(!cas()){} 

   for(;;;){ if(cas) return }

(5)自适应自旋锁: 这个东西就是synchronized的轻量级锁用到了,相对智能的自旋锁,如果上次 CAS成功了,这次CAS循环次数,加几次。如果上次失败了,这次CAS就减几次。 4、synchronized的实现原理。

synchronized应该不陌生,这东西就是JVM层面最原始的互斥锁。

使用方式,就同步代码块,同步方法。

这个是重量级锁的原理。

synchronized因为是互斥锁,只能有一个线程持有当前锁资源。所以synchronized底层有一个 owner属性,这个属性是当前持有锁的线程。如果owner是NULL,其他线程就可以基于CAS将 owner从NULL修改为当前线程,只要这个CAS动作成功了,就可以获取这个synchronized锁资源。 如果失败了,会再尝试几次CAS,没拿到就park挂起当前线程。

5、synchronized的锁升级过程。

这个问题属于常识性问题,不深究特别底层的东西。

无锁:当前对象没有被作为锁资源存在 && 在JDK1.8中,会有一个4s的偏向锁延迟,这段时间的对 象就处于无锁状态。

偏向锁:如果撇去4s的偏向锁延迟,那么刚new出来的对象,基本都是偏向锁。

Ps:如果某一个线程,反复的去获取同一把锁,此时偏向锁的优势就出现了,无需做CAS操作,比较一下指向的是否是当前线程

(1)如果没有被所谓锁资源,那么这个偏向锁,没有偏向某一个线程。哪个线程都没偏向(匿名偏向)

(2)作为锁资源存在了,同时指向着某一个线程,这个就是偏向锁(普通偏向)

轻量级锁:如果偏向锁状态下,出现了竞争,那么升级为轻量级锁。轻量级锁状态下,会执行多次 CAS,默认初始次数是10次,这种CAS是采用的自适应自旋锁。

重量级锁:如果轻量级锁状态下,CAS完毕获取锁失败,直接升级到重量级锁。到了重量级锁的状态 下,就是再次基于几次CAS尝试修改owner属性,成功,拿锁走人。 失败,挂起线程。等到其他线程释放锁后,再被唤醒。

一般来说,锁只有升级,没有降级。

但是有点特殊情况,比如偏向锁可以退到无锁。因为偏向锁无法保存对象的hashcode,如果在偏向 锁状态,并且没有作为锁的情况,执行了hashcode方法,会从偏向锁到无锁。、

下面是JIT优化导致的轻量级锁降级到无锁的状态

public class LockTest {

public static void main(String[] args) throws Exception {

  synchronizedTest();

}

public static void synchronizedTest() throws InterruptedException {

   Thread.sleep(5000);

  Object o = new Object();

  // 00000101 无锁/匿名偏向

  System.out.println(ClassLayout.parseInstance(o).toPrintable());

  Thread thread = new Thread(() -> {

    synchronized (o) {

    // 10000010 重量级锁

    System.out.println(Thread.currentThread().getName() + "-1:" + ClassLayout.parseInstance(o).toPrintable());

    }

    // 00000010 重量级锁

    System.out.println(Thread.currentThread().getName() + "-2:" + ClassLayout.parseInstance(o).toPrintable());

    try {

      // 等待锁降级

      Thread.sleep(5000);

    } catch (InterruptedException e){

      e.printStackTrace();

    }

    // 00000001 无锁

    System.out.println(Thread.currentThread().getName() + "-3:" + ClassLayout.parseInstance(o).toPrintable());

    synchronized (o) {

      // 00010000 轻量级锁

      System.out.println(Thread.currentThread().getName() + "-4:" + ClassLayout.parseInstance(o).toPrintable());

    }

  });

  thread.start();

  synchronized (o) {

    // 00000101 无锁/匿名偏向

    System.out.println(Thread.currentThread().getName() + ":" + ClassLayout.parseInstance(o).toPrintable());

  }

  while (thread.isAlive()) {

   }

}

}

6、AQS是什么?

AQS本质就是个抽象类,AbstractQueuedSynchronizer。AQS是JUC包下的一个基础类,没有具体 的并发功能的实现,不过大多数JUC包下的工具都或多或少继承了AQS去做具体的实现。

比如ReentrantReadWriteLock,ReentrantLock,CountDownLatch,线程池之类的,都用继承 了AQS做自己的实现。

AQS有三个核心点:

(1)volatile修饰int属性state。(如果作为锁,state == 0,代表没有线程持有锁资源,如果大于 0,代表有线程持有锁资源)

(2)基于Node对象组成的一个同步队列(如果线程想获取lock锁,结果失败了,会被挂起线程,线 程会被封装为Node对象,扔到这个同步队列中)

(3)基于Node对象组成的单向链表(当线程持有锁资源时,如果执行了await,线程会释放锁资 源,并且将线程封装为Node对象,扔到这个单向链表中。如果其他线程执行了signal,那就会 将单向链表的Node节点扔到同步队列)

7、ReentrantLock释放锁时为什么要从后往前找有效节点?

在释放锁唤醒排队的Node时,会先找head.next唤醒,如果head.next是取消状态,那么AQS的逻 辑是从tail往前找,一直找到里head最近的有效节点。

为什么不从前往后找,更快。

因为节点在取消时,为了更方便GC回收,会做一个操作,将Node的next指针指向自己,形成一个 循环引用,这样更容易被GC发现。另外AQS全局是以prev指针为基准的,所有操作都是prev准, next不一定准。

8、公平锁和公平锁的区别

语言层面上,区分很简单,就是一个公平,一个不公平。 这个问题最好从源码的维度来聊。

可以扩展说一下,synchronized支持持非公平锁,ReentrantLock既有公平,也有非公平。

在ReentrantLock中,有两个方法的实现有公平和非公平之分。

(1)lock方法

    非公平锁:直接执行CAS,尝试将state从0改为1,如果CAS成功了,拿锁走人,失败了走 后续逻辑。

    公平锁:直接走后续逻辑(后续逻辑包含tryAcquire方法)。

(2)tryAcquire方法:

    非公平锁:如果state为0,会直接执行CAS,尝试将state从0改为1,如果CAS成功了,拿 锁走人,失败就准备排队。

     公平锁:如果state为0,先查看一下,是否有排队的节点,如果有排队的,那就不抢,直接 去排队。

标签:java,synchronized,CAS,编程,public,并发,线程,CPU,偏向
From: https://www.cnblogs.com/northli/p/18264610

相关文章

  • Java毕业设计基于SSM的网月科技公司门户网站
    随着信息技术在管理上越来越深入而广泛的应用,管理信息系统的实施在技术上已逐步成熟。本文介绍了网月科技公司门户网站的开发全过程。通过分析高校学生综合素质评价管理方面的不足,创建了一个计算机管理网月科技公司门户网站的方案。文章介绍了网月科技公司门户网站的系统......
  • Java毕业设计基于SSM的超好听乐器销售购物商城系统
    现代经济快节奏发展以及不断完善升级的信息化技术,让传统数据信息的管理升级为软件存储,归纳,集中处理数据信息的管理方式。本缪斯乐器购物网站就是在这样的大环境下诞生,其可以帮助管理者在短时间内处理完毕庞大的数据信息,使用这种软件工具可以帮助管理人员提高事务处理......
  • Java毕业设计基于SSM的社区智慧养老监护管理平台
    随着信息技术在管理上越来越深入而广泛的应用,管理信息系统的实施在技术上已逐步成熟。本文介绍了社区智慧养老监护管理平台的开发全过程。通过分析高校学生综合素质评价管理方面的不足,创建了一个计算机管理社区智慧养老监护管理平台的方案。文章介绍了社区智慧养老监护管......
  • Java毕业设计基于SSM的社区物业管理系统
    使用旧方法对社区物业信息进行系统化管理已经不再让人们信赖了,把现在的网络信息技术运用在社区物业信息的管理上面可以解决许多信息管理上面的难题,比如处理数据时间很长,数据存在错误不能及时纠正等问题。这次开发的社区物业管理系统管理员功能有个人中心,用户管理,楼盘......
  • Java毕业设计基于SSM的封闭式学校高校师生外出请假管理系统
    现代经济快节奏发展以及不断完善升级的信息化技术,让传统数据信息的管理升级为软件存储,归纳,集中处理数据信息的管理方式。本疫情期间高校师生外出请假管理系统就是在这样的大环境下诞生,其可以帮助管理者在短时间内处理完毕庞大的数据信息,使用这种软件工具可以帮助管理......
  • Java毕业设计基于SSM的留守儿童帮助救助管理系统
    随着信息技术在管理上越来越深入而广泛的应用,管理信息系统的实施在技术上已逐步成熟。本文介绍了葛溪乡留守儿童信息管理系统的开发全过程。通过分析高校学生综合素质评价管理方面的不足,创建了一个计算机管理葛溪乡留守儿童信息管理系统的方案。文章介绍了葛溪乡留守儿童......
  • Java毕业设计基于SSM的手机电脑测评系统
    随着信息技术在管理上越来越深入而广泛的应用,作为一个一般的用户都开始注重与自己的信息展示平台,实现基于SSM框架的电脑测评系统在技术上已成熟。本文介绍了基于SSM框架的电脑测评系统的开发全过程。通过分析用户对于基于SSM框架的电脑测评系统的需求,创建了一个计算机管......
  • Java毕业设计基于SSM的电子病历系统
    SpringMVC属于SpringFrameWork的后续产品,已经融合在SpringWebFlow里面。Spring框架提供了构建Web应用程序的全功能MVC模块。使用Spring可插入的MVC架构,从而在使用Spring进行WEB开发时,可以选择使用Spring的SpringMVC框架或集成其他MVC开发框架,如Struts1,Strut......
  • Java进阶封装、继承、多态、抽象
    一、封装(Encapsulation)概念封装是面向对象编程的基本特性之一,它通过将对象的状态(属性)和行为(方法)包装在一起,对外隐藏对象的内部细节,只暴露必要的接口,以实现数据保护和隐藏实现细节的目的。优点数据保护:防止外部代码直接访问和修改对象的内部数据,确保数据的完整性和有效性。隐......
  • 基于JavaWeb+Spring Boot的校友录管理与数据分析系统(论文)
    目录1绪论11.1研究背景11.2国内外研究现状21.2.1国内研究现状21.2.2国外研究现状31.3研究的目的和意义41.3.1研究目的41.3.2研究意义41.4论文的内容和结构52系统相关技术概述72.1Java技术简介72.2SpringBoot框架82.3MySQL数据库技术简介9......