并发题

线程池使用的是哪种

ThreadPoolExecutor: 核心线程数、最大线程数、存活时间、ThreadFactory、BlockingQueue任务队列、RejectedExecutionHandler拒绝策略

4种拒绝策略:当队列(有界队列)任务已满时的处理方式

1、AbortPolicy,丢弃任务并抛出RejectedExecutionExecption

2、DiscardPolicy,丢弃任务但不抛出异常

3、DiscardOldestPolicy,丢弃队列最前面的任务,然后尝试重新执行任务

4、CallerRunsPolicy,由调用线程处理任务

线程池参数怎么配置

参上

线程池各个参数的作用

参上

线程池的参数配置要注意什么

创建线程池建议采用new ThreadPoolExecutor,而不是采用Executor提供的方法来创建,因为Executor提供的创建方式线程最大数量是Integer.MAX,可能会造成OOM。

线程池的工作流程

线程池执行流程

JDK 中的并发类知道哪些

Java并发包concurrent包下的:

1、Lock, 乐观锁操作

2、阻塞队列

3、原子操作类

4、线程池:Executors

5、信号量:CountDownLatch、CyclicBarrier等

AQS 的底层原理

介绍下悲观锁和乐观锁

使用过哪些锁?

syncronized

RenrtrrentLock:重入锁,sync和RentrentLock都是重入锁,重入锁是为了解决线程死锁问题。

CAS

synchronized 和 Lock 的区别、使用场景

1、sync是jvm层面处理的,是一个关键字,而Lock是一个类

当使用sync同步代码块的时候,可以通过查看class字节码看到:

```

public class SyncDemo{

    public synchronized void test1(){

    }

    public void test2(){

        synchronized (this){

        }

    }

}

```

可以看到,同步代码块的方法,字节码添加了monitorenter与monitorexit命令

2、sync是直接通过加锁,属于悲观锁,而Lock使用CAS机制,是乐观锁。

3、sync会自动释放锁,而lock需要手动释放,并且需要try、catch、finally中最终释放(防止死锁)

synchronized 原理

Java的对象存储主要分为以下三个部分:

实例对象内存结构,其中实例变量存储对象的属性信息以及父类的属性信息(如果是数组还会存储数组的长度),填充数据(JVM要求对象的起始地址必须是8字节的倍数,所以用来字节对齐,知道即可),而对象头则是synchornized实现的基础

对象头包括两部分数据:MarkWord以及类型指针(Class MetaData Address)指向当前对象的类元数据

MarkWord是非固定结构的数据,主要存储了对象的HashCode、锁状态、锁标记位等,根据对象的锁状态,锁存储的数据也有所不同。

MarkWord64位的存储结构,根据对象状态的不同,存储的结构会发生变化

JDK1.6之前,仅有重量级锁的概念,重量级锁也就是同步锁,会造成线程阻塞,并且会使线程在用户态与核心态频繁切换,影响效率。而在JDK1.6,JVM对synchnorized做了较大的优化,引入了偏向锁、轻量锁。对象不会一创建就处于重量级锁的状态,而是按照一定的规则升级。

锁升级过程:

1、对象在创建初期,此时没有任何线程竞争,lock的标记位是01,偏向锁标记位0


正常状态的对象MarkWord

2、当有线程来竞争锁时,将升级为偏向锁,在标记字(MarkWord)中记录该线程的ID,此后该线程访问此对象时不需要再做任何检验和切换,此时的线程执行效率是非常高的。


偏向锁状态的MarkWord

3、当有两个线程来竞争锁时,将升级为轻量锁,即两个线程公平竞争锁,谁先获得锁就获得了代码的执行。MarkWord将不再记录偏向线程,而是记录栈帧中的锁记录。


轻量锁的MarkWord

4、当更多的线程来竞争锁时,轻量锁将升级为重量锁,此时MarkWord记录一个监视器的对象(ObjectMintor)


重量锁的MarkWord

synchronized 作用于静态方法、普通方法、this、Lock.class 的区别

静态方法,锁的是this.class(类对象),普通方法锁的是this

为什么引入偏向锁、轻量级锁,介绍下升级流程

升级流程如上上个问题

死锁的必要条件,如何预防死锁

1、互斥条件:一个资源只能被一个线程占有

2、请求与保持条件:至少保持了一个占有资源,又去请求其它的资源,此时线程阻塞,但是对已获得的资源不释放

3、不可剥夺条件:线程已经获取的资源,不能被其它线程占有,只能等待当前线程主动释放

4、循环等待条件:若干线程形成收尾相接循环等待的关系

破坏死锁:由于线程的互斥是不可改变的,那么只能从其它三个方面去破坏死锁。

1、破坏不可剥脱条件:当发现不能获取执行所需的全部资源时,则处于等待状态,当可以获取到全部资源时,才执行。

2、破坏请求保持:

3、破坏循环等待:将资源进行编号,按照资源紧缺,越紧缺编号越大,只有当线程可以获取到编号最小的资源时,才执行。

介绍下 CountDownLatch 和 CyclicBarrier

两者都是用来做同步辅助控制的,CountDownLatch,执行的当前线程会处于阻塞状态,等到所有线程都执行完毕,才会进入下一步,不可循环。而CyclicBarrier执行的当前线程不会阻塞,是一种异步操作,而且是可循环的。

介绍下 CAS,存在什么问题

 CAS,Compare And Swap 比较并替换。

是一种乐观锁,在更新数据的时候,将数据与内存中的数据进行比较,如果发现一致,则可以更新为新的值,不一致则获取最新的值并重新执行以上流程。

1、ABA问题,内存中的值可能经过A 修改为B 后又修改为A,如果仅仅比较值,可能是一样的,那么这种改变对程序是不可见的,可以通过加版本号解决,每次修改,将版本号加1

2、自旋问题:某个线程可能会一直处于自旋(循环)状态,此时的CPU开销是比较大的

介绍下 ThreadLocal,存在什么问题

同一个ThreadLocal对象在不同线程中有不同的数据副本,其数据结构采用ThreadLocalMap实现,每个线程创建的时候,set数据的时候,会有线程作为key,vlaue作为值存入ThreadLocalMap中

存在问题:

1、ThreadLocalMap的key采用的是弱引用,可以保证在在线程执行完毕之后key可以被回收,但是value是强引用,任然需要手动回收,否则可能会造成内存泄漏。

2、线程池数据错乱:因为现在使用的基本都是线程池,那么在不同的时间执行的方法使用的线程可能是同一个,如果在ThreadLocal中set了值而没有清理,则可能会造成数据错乱,使用remove来清理数据保证数据干净。

使用场景: 

1、Spring采用ThreadLocal解决事务控制的问题:一个业务方法中,可能调用了很多个Mapper接口的方法,要实现事务控制,必须保证这些Mapper使用的数据库连接是同一个。 同时Spring的事务传播机制不同,事务的处理逻辑也是不一样的。比如:A方法调用B方法,  A的事务处理与B的事务处理逻辑分开,那么A方法和B方法所使用的数据库连接则不能是同一个。

2、数据传输:一个变量在多个类、方法都使用到,要保证其线程安全,可以通过方法传递的方式,也可以通过ThreadLocal来实现。

你可能感兴趣的:(并发题)