Java并发编程底层实现原理(不了解这个不敢说懂并发),java校招面试问题大全及答案大全

关闭偏向锁延迟

-XX:BiasedLockingStartupDelay=0

关闭偏向锁 程序默认进入轻量级锁状态

-XX:-UseBiasedLocking=false

2.1.2 轻量级锁

2.1.2.1 轻量级锁加锁

线程在执行同步代码块之前,JVM会在当前线程的栈帧中创建用于存储锁记录的空间,并将对象头中Mark Word复制到锁记录中,官方称为Displaced Mark Word。然后线程尝试使用CAS将对象头中的Mark Word替换为指向锁记录的指针。如果成功,当前线程获得锁;如果失败,表示其他线程竞争锁,当前线程便尝试自旋获得锁。

《一线大厂Java面试题解析+后端开发学习笔记+最新架构讲解视频+实战项目源码讲义》

【docs.qq.com/doc/DSmxTbFJ1cmN1R2dB】 完整内容开源分享

2.1.2.2 轻量级锁解锁

轻量级锁解锁时,使用的是CAS操作将Displaced Mark Word替换回对象头,如果成功则表示没有竞争;如果失败,表示当前锁存在竞争,锁就会膨胀为重量级锁。 注意:由于自旋过程消耗CPU,为了避免无用的自旋,一当升级为重量级锁,那么就不会再恢复到轻量级锁状态。当前锁出于重量级锁状态时,其他线程尝试获取锁,都会被阻塞,当持有锁的线程释放锁后会唤醒这些线程,重新进行锁的争夺。

2.1.3 锁的优缺点对比

锁的优缺点对比

| 锁 | 优点 | 缺点 | 适用场景 |

| — | — | — | — |

| 偏向锁 | 加锁和解锁过程不需要额外的消耗,和执行非同步方法相比仅存在纳秒级别的差异 | 如果线程存在锁竞争,会带来额外的锁撤销开销 | 适用于只有一个线程访问同步代码块 |

| 轻量级锁 | 竞争的线程不会阻塞,提高了程序的响应速度 | 如果始终得不到锁竞争的线程,使用自旋会消耗CPU | 追求响应时间,同步代码块执行速度非常快 |

| 重量级锁 | 线程竞争不适用自旋,不会消耗CPU | 线程阻塞,响应时间慢 | 追求吞吐量,同步块执行速度较长 |

3、Java中如何实现原子操作


在Java中可以通过锁和循环CAS的方式实现原子操作

3.1 使用循环CAS实现原子操作

JVM中的CAS操作利用了处理器提供的CMPXCHG指令实现。自旋CAS实现的基本思路就是循环进行CAS操作知道成功为止,示例代码实现一个安全的计数器和非安全的计数器。

package com.liziba;

import java.util.ArrayList;

import java.util.List;

import java.util.concurrent.atomic.AtomicInteger;

/**

  • @auther LiZiBa

  • @date 2021/2/28 17:39

  • @description: 计数器实现

**/

public class Counter {

// 安全计数器统计数

private AtomicInteger atomicInteger = new AtomicInteger(0);

// 非安全计数器统计数

private int i = 0;

public static void main(String[] args) {

final Counter cas = new Counter();

List threads = new ArrayList<>(600);

long start = System.currentTimeMillis();

for (int j = 0; j < 100; j++) {

Thread t = new Thread(() -> {

for (int i = 0; i < 10000; i++) {

// 非安全计数器

cas.count();

// 安全计数器

cas.safeCount();

}

});

threads.add(t);

}

// 启动线程

threads.forEach(t -> t.start());

// join等待所有线程执行完毕

for (Thread t : threads) {

try {

t.join();

} catch (InterruptedException e) {

e.printStackTrace();

}

}

// 输出不安全计数器结果、安全计数器结果、程序执行时间

System.out.println(cas.i);

System.out.println(cas.atomicInteger.get());

System.out.println(System.currentTimeMillis() - start);

}

/**

  • 安全计数器

*/

private void safeCount() {

i++;

}

/**

  • 非安全计数器

*/

private void count() {

for (;😉 {

int i = atomicInteger.get();

// CAS 增加

// 注意使用 ++i

boolean set = atomicInteger.compareAndSet(i, ++i);

// 设置成功退出死循环

if (set) {

break;

}

}

}

}

运行结果

711319

1000000

265

Process finished with exit code 0

JDK 1.5开始,JDK并发包提供了一些类支持原子操作,如AtomicBoolean,AtomicInteger,AtomicLong,对应不同类型的原子操作,这些类提供了非常有用的工具方法,比如原子自增和自减等等。

3.1.1 CAS 实现原子操作的三大问题
  • ABA问题:因为CAS需要在操作值的时候,检查值是否发生了变化,如果没有变化则更新,但是如果一个值从A修改为B又修改为A,那么使用CAS就无法发现值发生了变化,但实际上发生了变化。解决方案如下

  • 使用版本号解决,将原本的A–>B–>A问题变成1A–>2B–>3A则可以解决

  • 使用JDK Atomic包里提供的AtomicStampedReference来解决ABA问题,这个类compareAndSet方法会首先比较当前引用是否等于预期引用,并且检查当前标志是否等于预期标志,如果全部相等则以原子方式替换,源码如下。

public boolean compareAndSet(V expectedReference,

V newReference,

int expectedStamp,

int newStamp) {

Pair current = pair;

return

expectedReference == current.reference &&

expectedStamp == current.stamp &&

((newReference == current.reference &&

newStamp == current.stamp) ||

casPair(current, Pair.of(newReference, newStamp)));

}

  • 循环时间长开销大:自旋CAS如果长时间不成功,会给CPU带来非常大的开销。解决这个问题需要JVM能支持处理器提供的pause指令,效率会有一定的提升。pause指令的两个作用如下

  • 延迟流水线执行指令(de-pipeline),使CPU不会消耗过多执行资源

  • 避免在退出循环时内存顺序冲突(Memory Order Violation)而引起CPU流水线被清空

  • 只能保证一个共享变量的原子操作:对多个共享变量进行操作的时候CAS无法保证原子性,解决方案如下

  • 使用锁

  • 变量合并

  • 使用JDK提供的AtomicReference类来保证引用对象之间的原子性,将多个变量放置于对象中

3.2 使用锁机制实现原子操作

锁机制保证了只有获得了锁的线程才能操作锁定的内存区域。JVM内部实现了很多锁,偏向锁、轻量级锁、重量级锁。但是除了偏向锁,JVM实现锁的方式都使用了循环CAS机制,即一个线程想进入同步代码块的时候,使用循环CAS的方式获取锁,当它退出同步代码块的时候使用循环CAS来释放锁

参考资料

  • 《Java并发编程的艺术》-- 方腾飞 魏鹏 程晓明 著
  • 百度百科
  • CSDN部分博客
  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
Java并发编程是指在Java程序中使用多线程来实现并发操作的编程技术。在面试中,通常会涉及以下几个方面的问题: 1. 什么是线程和进程? 线程是程序执行的最小单位,是进程中的一个执行流程。进程是一个正在执行中的程序,它具有独立的内存空间和系统资源。 2. 如何创建线程? 在Java中,可以通过继承Thread类或实现Runnable接口来创建线程。继承Thread类需要重写run()方法,实现Runnable接口需要实现run()方法,并将其作为参数传递给Thread类的构造方法。 3. 什么是线程安全? 线程安全是指多线程环境下,对共享资源的访问不会导致数据的不一致或不正确的结果。可以通过使用同步机制(如synchronized关键字、Lock接口)来保证线程安全。 4. 什么是锁? 锁是用于控制多个线程对共享资源进行访问的机制。常见的锁包括synchronized关键字、ReentrantLock类等。 5. 什么是死锁? 死锁是指两个或多个线程互相持有对方所需的资源,导致它们都无法继续执行的状态。 6. 什么是线程池? 线程池是一种管理和复用线程的机制,它可以避免频繁创建和销毁线程的开销,提高程序的性能和资源利用率。 7. 什么是并发集合? 并发集合是一组线程安全的数据结构,可以在多线程环境下进行高效的并发访问。常见的并发集合包括ConcurrentHashMap、ConcurrentLinkedQueue等。 8. 什么是原子操作? 原子操作是指不可被中断的一个或一系列操作,要么全部执行成功,要么全部不执行。Java提供了一些原子操作类(如AtomicInteger、AtomicLong),可以保证对共享变量的操作是原子性的。 9. 什么是线程间通信? 线程间通信是指多个线程之间通过共享内存或消息传递等方式进行信息交换和同步的机制。常见的线程间通信方式包括wait/notify机制、管道、信号量等。 10. 如何避免线程安全问题? 可以通过使用同步机制、使用线程安全的数据结构、使用原子操作、避免共享资源等方式来避免线程安全问题

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值