多线程相关

创建线程方法

1,继承Thread类

重写run()方法,而不是start()方法,但是占用了继承名额,java中类为单继承。

2,实现Runnable接口

实现了Runnable接口,实现了run()方法,使用依然是Thread,这种方式更常用。(thread低层也是继承Runnable接口)

3,实现Cannable接口与FutrueTask

可以阻塞式的获得线程结果,FutrueTask本质上底层也是实现(继承)Runable,Future,实现Call方法

4,利用线程池创建线程

使用线程池创建一般使用Executors来创建。

为什么不推荐使用Executors来创建线程池?

使用Executors创建FixedThreadPool时,底层就会使用LinkedBlockingQueue,这是一个无界的阻塞队列(因为Linked就是为双向链表,没法阻塞),如果该线程执行任务,如果任务过多就会不断添加到队列中,任务多占用内存多,会耗尽内存导致(OOM)内存溢出。

当使用EXecutors时创建SingleThreadExecutor也是底层使用了Linked无界阻塞队列,同样会导致内存溢出(OOM).

总结

除开可能出现的OOM问题,使用Executors来创建线程池也不能自定义线程名字,不利于排查问题,建议使用ThreadPoolExecutor来定义线程池,可以灵活控制。

线程池有哪几种状态?每种状态代表什么?

Running表示线程正常运行,接受新任务也会正常处理线程,

shutdown状态是调用线程池的shutdown()方法时,线程进入到这个状态,表示此时正处在线程池关闭状态,此时不会接受新任务,但是会继续把队列中任务处理完,处理完成后中断运行时线程。会进入到tidying状态。

stop状态是调用shutdownnow()方法时,线程池进入到stop状态,表示线程池处于停止状态,不会接受新任务,也不会处理队列中任务,正在运行时线程也会中断。

没有线程运行后,进入tidying状态,调用空方法terminated(),方便拓展。

terminated()方法执行完之后进入terminated状态。

Sychronized和ReentrantLock区别

sychronized是锁的对象,锁信息保存在对象头

底层有锁升级过程

ReetranLock中int类型state标识来识别锁状态。

没有锁升级,tryLock()就是轻量级锁,偏向锁

Lock()就是重量级锁。

ThreadLocal有哪些应用场景,底层如何实现?

ThreadLocal底层是通过ThreadLocalMap实现的,每个Thread对象都有一个ThreadLocalMap,map的key为ThreadLocal对象,Map的value为需要缓存值。

线程池中使用ThreadLocal会造成内存泄漏,因为当ThreadLocal对象使用完成之后,要把设置的key,value也就是Entry对象进行回收,但是线程池线程不会回收,线程对象是强应用指向ThreadLocalMap,这个map强引用指向Entry对象,线程不被回收,Entry对象也不回收,从而出现内存泄漏,解决就是使用ThreadLocal对象后,手动调用ThreadLocal中的remove方法,手动清除Entry对象。

我们去执行其他任务,线程没有被删除,还保存在map中,导致内存溢出,手动清除,remove()

RenntrantLock分别为公平锁和非公平锁,底层分别是如何实现的?

底层实现都是由AQS来进行排队,区别在于使用lock()方法加锁时:

1.如果是公平锁,会先检查AQS队列中是否存在线程在排队,如果由线程在排队,当前线程也排队。

2.非公平锁,不会检查线程是否在排队,直接竞争锁,失败再去排队。

锁释放的时候都是唤醒排在最前面的线程,非公平锁只体现在线程加锁阶段,而不是线程被唤醒阶段。

ReentrantLock()是可重入锁。

sychronized锁升级过程

偏向锁 :就是锁对象的对象头中记录当前获取该锁的线程ID,下次再来获取就是直接获取到,也就是支持锁重入。

轻量级锁就是偏向锁升级而来,一个线程获取到锁后就是偏向锁,如果这个时候第二个线程来竞争锁,偏向锁就会升级为轻量级锁,为了与重量级锁分开,通过自旋来实现,不会阻塞线程。

重量级锁就是自旋次数过多依旧没有获取到锁,这就升级为了重量级锁,会导致阻塞线程。

自旋锁就是线程获取锁过程中不去阻塞线程,也就是无所谓唤醒线程,操作系统操作唤醒和阻塞比较消耗时间,自旋锁是线程通过CAS回去预期的一个标记,如果没有获取到就继续循环获取,如果获取到表示获取到锁,相对没有使用太多操作系统资源,比较轻量。

锁自旋就是加锁-->等待过程

对线程安全理解?

线程安全理解就是一段代码在多个线程执行情况下能否得到正确结果。

对守护线程理解?

线程分为用户线程和守护线程,用户线程就是普通线程,守护线程就是JVM后台线程,比如垃圾回收就是守护线程,他会在其他普通线程都停止运行后自动关闭,我们可以设置thread.setDaemo(true)来把一个线程设置为守护线程。

谈谈你对AQS理解,AQS如何实现可重入锁?

AQS是有关并发相关的底层的一些核心组件

1.AQS是一个java线程同步的框架,是JDK中很多锁工具的核心实现框架。

2.AQS中,维护了一个信号state和一个线程组成的双向链表队列。其中,这个线程队列就是用来给线程排队的,而state就像红绿灯,用来控制线程排队或者放行的。在不同场景有不同意义。(在RenntrantLock中通过代码int类型的state表示来识别锁状态)。

使用一次lock()就需要使用unlock()来解锁一下。

3.在可重入锁这个场景下,state就会用来表示枷锁次数。0表示无锁,每加一次锁,state就加1.释放锁state就减1.

线程池底层工作原理与FixedThreadPool用的阻塞队列是什么?

corePoolSize核心线程数

线程池底部是通过队列+线程实现,当我们利用线程池执行任务时:

1.线程池线程数量小于核心线程数(corePoolSize),即使线程池中线程都是空闲状态,也要创建新线程来处理被添加的任务。

2.线程池中线程数量等于核心线程数量,但是缓冲队列workQueue未满,那么任务放入缓存队列。

3.如果线程池中线程数大于等于核心线程数,缓冲队列满了,并且线程池中数量小于maximumPoolSize(最大线程数量),建新的线程来处理被添加的任务。

4.线程池中线程数量大于核心线程数,缓冲队列满,并且线程中数量等于最大线程数,那么通过handler所指定的删除策略来处理此任务。

5.当线程池线程数量=大于corePoolSize时,如果线程空闲时间超过keepAliveTime,线程将被终止。这样线程池可以动态的调整线程池中线程数。

FixedThreadPool代表定长线池,底层用的LinkedBlockingQueue,鸟事无界的阻塞线程。

线程池为什么是先添加队列而不是先创建最大线程?

当线程池中核心线程都在忙碌时,如果继续添加任务,那么先放队列,队列满了之后会开新线程。相当与一个公司十个人正常需求处理的过来,加大需求就需要加班,把未完成需求做了,但是,需求队列满了做不过来了就需要找新员工了。

HashMap中的put方法解析?

onlyIfAbsent(判断存在相同key情况下要不要把value去覆盖。)

evict(就是如果是false情况下标明是table创建模式下)

put方法创建的大概流程:

1.根据可以通过哈希算法和与运算算出数组下标

2.如果数组小标为空,则将key和value封装为Entry对象(1.7为Entry,1.8为Node)并放入当前位置

3.如果数组下标元素不为空分情况讨论:

a.如果是1.7,先判断是不是需要扩容,如果需要就扩容,如果不需要就生成Entry对象,并且使用头插法添加到当前位置链表中。

b.如果是1.8,则会先判断当前位置Node类型,看是红黑树Node还是链表Node:

i.如果是红黑树Node,则先将key和value封装为一个红黑树节点添加到红黑树中去,在这个过程中判断是否存在当前key,如果存在则更新value。

ii。此位置node为链表节点,则将key和value封装为一个链表Node并通过尾插法插入到链表最后位置去,因为是尾插法,所以需要遍历链表,在遍历链表过程中。

谈谈ConcurrentHashMap扩容机制?

jdk1.7版本之前,我们的ConcurrentHashmap结构为segment数组块,中间包含数组(小型hashmap中间指向链表)数组不扩容,内部的hashmap进行扩容。

多线程扩容

jdk1.8版本之后,结构为entry数组,扩容改为扩容双倍数组,扩容时间段是不可以进行put的,这段时间我们进行转移元素,将存储key和value的Entry数组内容进行多线程扩容,之后进行put

ThreadLocal底层原理?

如果你提交任务时,线程池队列已经满,这时间会发生什么?

如果使用无界队列,继续提交任务没关系。

如果有界队列,提交任务时,如果核心线程数没有到上线,增加线程,如果线程数量已经达到最大值,则使用拒绝策略进行拒绝。

Redis常见缓存问题解决方法?

除了上述三种常见的Redis缓存异常问题之外,还经常听到的有缓存预热和缓存降级两个名词,与其说是异常问题,不如说是两种的优化处理方法。

(一)缓存预热

缓存预热就是系统上线前后,将相关的缓存数据直接加载到缓存系统中去,而不依赖用户。这样就可以避免在用户请求的时候,先查询数据库,然后再将数据缓存的问题。用户直接查询事先被预热的缓存数据,这样可以避免那么系统上线初期,对于高并发的流量,都会访问到数据库中, 对数据库造成流量的压力。根据数据不同量级,可以有以下几种做法:

  • 数据量不大:项目启动的时候自动进行加载。

  • 数据量较大:后台定时刷新缓存。

  • 数据量极大:只针对热点数据进行预加载缓存操作。

(二)缓存降级

缓存降级是指当缓存失效或缓存服务出现问题时,为了防止缓存服务故障,导致数据库跟着一起发生雪崩问题,所以也不去访问数据库,但因为一些原因,仍然想要保证服务还是基本可用的,虽然肯定会是有损服务。因此,对于不重要的缓存数据,我们可以采取服务降级策略。一般做法有以下两种:

  • 直接访问内存部分的数据缓存。

  • 直接返回系统设置的默认值。

评论 1
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值