并发八股 资料

乐观锁和悲观锁

乐观锁即认为不会发生并发竞争,直接去修改数据,知道发现存在竞争后再重试。悲观锁则是认为肯定会发生竞争,直接先上锁后修改数据。乐观锁的实现方式有CAS,但是需要跟版本号相结合使用,避免ABA问题。悲观锁可以通过各种独占锁来实现。

Violate

        - 用来声明字段具有可见性(而非原子性):被violate声明过的字段可以保证被其他线程访问到。但是不能保证原子性:例如每个线程都能访问count,但是不能保证大家都按顺序执行count++, 有可能重复或者缺少++的次数。

        - 禁止JVM重排序:JVM是有可能重排序我们的代码指令顺序的,violate关键字可以保证按顺序执行,例如new一个对象,第一步是为对象创建一个内存地址,第二步时初始化对象,第三步是将对象指向内存的地址。我们期望是1,2,3.但是有可能变成1,3,2.这在多线程时就可能存在问题:有可能第一个线程执行了1,3.然后第二个线程执行了2,导致认为对象是null。

Synchronize

        - 用来修饰方法,代码块,保证同一时间只有一个线程能够执行对应的方法和代码块。

                - 用来修饰静态方法时,锁的是当前的类。

                - 用来修饰非静态方法时,锁的是当前的实例。

                -用来修饰代码块时,取决于synchronize(xxx)括号里的内容。如果是object,则是锁的对应的对象,如果是xx.class则是锁的xx类。

        - 不能用来修饰构造方法,但是可以修饰构造方法内的代码块。

       - 原理:本质上都是通过Monitor对象来实现的,会维护一个owner来对应持有锁的线程,还有一个entityList来维护竞争锁的线程, 还有一个waiteSet来维护等待唤醒的线程,唤醒后会将线程从waiteSet移入entityList。

                - 修饰代码块时,原理是使用了monitorenter和monitorexit来实现的,enter时锁计数器+1, exit时-1。若计数器不为0则不能enter。

                - 修饰方法时,原理是使用了ACC_SYNCHRONIZED 标志

锁升级

        - 无锁状态、偏向锁状态、轻量级锁状态、重量级锁状态

        - 第一次共享资源被访问时,会进入偏向锁状态,此时对象头会记录当前的线程ID,只有该线程能够访问共享资源。

        - 如果偏向锁状态时存在另外一个线程尝试访问共享资源并产生了竞争,则会进入轻量级锁状态,会不断自旋尝试获取锁,此时会在当前线程的栈中创建一个锁记录,会尝试拷贝锁对象的头到自己的栈中。

        - 如果自旋超过一定次数则会升级为重量锁,此时JVM会将线程阻塞,对象头指向monitor对象。

ReentrantLock

        -原理:基于AQS实现,可以对队列中的线程进行精细的控制,所以可以实现公平锁。而synchronize是基于enittyList对用户不可见,无法保证FIFO,所以不能实现公平锁。

        - ReentrantLock与Synchronize都是可重入锁,即可以重复获取同一个对象的锁。

        - ReentrantLock可以实现公平锁,而Synchronize只能是非公平锁。(即不能保证按照顺序获取锁)

        - ReentrantLock支持设置超时。但是synchronize不支持,因为Monitor对象没有该功能且无法修改。

        - ReentrantLock是可中断锁,即在等待锁的过程中可以停止。但是Synchronize是不可中断锁,即等待过程中不能终止。

        - ReentrantLock是独占锁,但是ReentrantReadLock是共享锁,即会阻塞写操作,不会阻塞其他线程的读操作

ThreadLocal 

        - 用来给每个线程维护一个独立的变量池子,单独读写,不会受到其他线程的影响

        - ThreadLocal是ThreadLocalMap的封装,变量都存在map中以ThreadLocal为key,object微value的键值对,通过get和set方法读写。

        - 如果没有调用remove方法,会造成内存泄漏。因为map中的value被entry强引用。

        - 父子线程如何传递ThreadLocal的值? 使用InheritThreadLocal,会自动继承父线程的Threadlocal。但是缺点是只会在创建子线程是复制一次。如果要在之后再次通信,可以使用TransmittableThreadLocal 

线程池

        - 线程池与连接池等一样是将用完的线程不会马上销毁,而是保留在池中可以复用以提高效率。

        - ThreadPoolExecutor参数:

                - corePoolSize 线程池核心线程数量(即一直保持存活的线程数量)

                - maximumPoolSize 最大数量 (即任务超过核心线程数量时,可以创建出来的最大线程数量)

                - keepAliveTime 超过核心线程数的线程空闲时存活时间, unit 时间单位

                - workQueue 用来储存等待执行的任务队列

                - handler  用来定义拒绝策略,即队列满了之后如何处理

                        - 拒绝策略分为几种,默认是Abort,即抛出异常。还有discard,即直接丢弃。还有一种CallerRuns,即退还给调用execute的线程处理。但是有风险,即如果该任务耗时很长,而且是由主线程调用的execute,有可能造成主线程阻塞。

                        - 那么又没有一种既不丢弃任务,又不阻塞主线程的方法呢?

                               - 消息队列,redis缓存任务,或者将任务存入数据库中,等待空闲时再取出来用

        - 线程池中的线程异常后,是销毁还是复用?如果是通过execute提交创建的线程会销毁重新创建,适用于调用方不在意抛出的异常。如果是submit则会将异常存在Future中供调用方处理异常后再处理。

        - 如果线程池数量设置的太大,会导致上下文切换频繁。(线程数如果大于CPU核数,会导致CPU需要来回切换以处理每个线程,每次切换都会切换上下文)

### Java并发常见面试题 #### 什么是Java内存模型(JMM)? Java内存模型(JMM)定义了Java程序中各个变量(包括实例字段、静态字段和构成数组元素的变量)的访问规则。JMM规定了线程对共享变量的所有操作都必须通过主内存进行,同时引入了“工作内存”的概念,每个线程都有自己私有的工作内存,用于存储从主内存复制过来的共享变量副本[^2]。 #### 创建新线程的方式有哪些? 创建新线程主要有两种方式:继承`Thread`类重写其`run()`方法;实现`Runnable`接口并将其传递给`Thread`对象。需要注意的是,直接调用`Thread`对象的`run()`方法并不会启动新的线程,而是作为主线程中的普通方法被执行。只有通过调用`start()`方法才能真正开启一个新的线程来执行`run()`方法内的逻辑[^3]。 #### 如何理解volatile关键字的作用? `volatile`修饰符可以确保被多个线程访问的成员变量能够及时得到更新。具体来说,在每次读取时都会强制刷新最新的值而不是缓存旧版本的数据;而在每次修改之后也会立即将更改同步回主存当中以便其他线程可见。不过要注意的是,虽然它可以解决可见性和有序性问题,但对于原子性的保障仍然有限制,比如对于复合赋值运算(`++`)就不适用。 #### 解释下CAS机制及其应用场景 比较并交换(Compare And Swap, CAS),是一种无锁算法的核心思想之一。它允许在不加锁的情况下完成某些特定条件下的数据更新动作。典型的应用场景是在高并发环境下实现高效的计数器或者队列结构等。例如,在`java.util.concurrent.atomic`包里提供了许多基于CAS原理设计出来的工具类,像`AtomicInteger`, `AtomicLong`等等。 #### 多线程编程中如何处理死锁现象? 预防死锁的方法主要包括破坏形成死锁四个必要条件中的任何一个——互斥条件、请求保持条件、不可剥夺条件以及循环等待条件。实际开发过程中可以通过合理规划资源获取顺序、减少持有锁的时间长度等方式降低发生概率。另外还可以利用一些辅助手段如超时机制、尝试锁定模式(`tryLock()`)来进行规避。 ```java // 使用 tryLock 方法防止死锁的例子 ReentrantLock lockA = new ReentrantLock(); ReentrantLock lockB = new ReentrantLock(); if (lockA.tryLock()) { try { if (lockB.tryLock()) { try { // 执行临界区代码... } finally { lockB.unlock(); } } } finally { lockA.unlock(); } } ```
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值