多线程中常见问题

1、为什么不建议使用Executors来创建线程池?

(1)FixedThreadPool
当我们使用Executors创建FixedThreadPool时,对应的构造方法为:
在这里插入图片描述
发现创建的队列为LinkedBlockingQueue,是一个无界阻塞队列,如果使用该线程池执行任务,如果任务过多就会不断的添加到队列中,任务越多占用的内存就越多,最终可能耗尽内存,导致 OOM。

(2)SingleThreadExecutor
当我们使用Executors创建SingleThreadExecutor时,对应的构造方法为:
在这里插入图片描述
也是LinkedBlockingQueue,所以同样可能会耗尽内存。

除开有可能造成的OOM外,使用Executors来创建线程池也不能自定义线程的名字,不利于排查问题,所以建议是直接使用ThreadPoolExecutor来定义线程池,这样可以灵活控制

2、线程池有几种状态?每种状态分别表示什么?

(1)RUNNING
Accept new tasks and process queued tasks
表示线程池正常运行,既能接受新任务,也会正常处理队列中的任务

(2)SHUTDOWN
Don’t accept new tasks, but process queued tasks
当调用线程池的shutdown()方法时,线程池就进入SHUTDOWN状态,表示线程池处于正在关闭状态,此状态下线程池不会接受新任务,但是会继续把队列中的任务处理完

(3)STOP
Don’t accept new tasks, don’t process queued tasks, and interrupt in-progress tasks
当调用线程池的shutdownnow()方法时,线程池就进入STOP状态,表示线程池处于正在停止状态,此状态下线程池既不会接受新任务了,也不会处理队列中的任务,并且正在运行的线程也会被中断

(4)TIDYING
All tasks have terminated, workerCount is zero, the thread transitioning to state TIDYING will run the terminated() hook method
线程池中没有线程在运行后,线程池的状态就会自动变为TIDYING,并且会调用terminated(),该方法是空方法,留给程序员进行扩展。

(5)TERMINATED
terminated() has completed
terminated()方法执行完之后,线程池状态就会变为TERMINATED

3、Sychronized和ReentrantLock有哪些不同点?

在这里插入图片描述

4、ThreadLocal有哪些应用场景?它的顶层是如何实现的?

(1)ThreadLocal是Java中所提供的线程本地存储机制,可以利用该机制将数据缓存在某个线程内部,该线程可以在任意时刻、任意方法中获取缓存的数据

(2)ThreadLocal底层是通过ThreadLocalMap来实现的,每个Thread对象(注意不是ThreadLocal对象)中都存在一个ThreadLocalMap,Map的key为ThreadLocal对象,Map的 value为需要缓存的值。
在这里插入图片描述

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

public class MyThreadPool2 {
    private ThreadLocal<String> local = new ThreadLocal<>();

    public void doTask(){
        local.set("test ThreadLocal");
        // do someThings
        local.remove();
    }
}

什么事内存泄露?
‌‌内存泄漏是指程序中已动态分配的堆内存由于某种原因未被释放或无法释放,导致系统内存的浪费,最终可能导致程序运行速度减慢甚至系统崩溃。‌ 内存泄漏是一个常见的问题,尤其在长时间运行的程序中更为突出,因为它会导致系统资源的逐渐耗尽。

(4)ThreadLocal经典的应用场景就是连接管理(一个线程持有一个连接,该连接对象可以在不同的方法之间进行传递,线程之间不共享同一个连接)

5、Sychronized的锁升级过程是怎样的?

(1)偏向锁:在锁对象的对象头中记录一下当前获取到该锁的线程ID,该线程下次如果又来获取
该锁就可以直接获取到了,也就是支持锁重入

(2)轻量级锁:由偏向锁升级而来,当一个线程获取到锁后,此时这把锁是偏向锁,此时如果有
第二个线程来竞争锁,偏向锁就会升级为轻量级锁,之所以叫轻量级锁,是为了和重量级锁区分开来,轻量级锁底层是通过自旋来实现的,并不会阻塞线程

(3)如果自旋次数过多仍然没有获取到锁,则会升级为重量级锁,重量级锁会导致线程阻塞

(4)自旋锁:自旋锁就是线程在获取锁的过程中,不会去阻塞线程,也就无所谓唤醒线程,阻塞
和唤醒这两个步骤都是需要操作系统去进行的
,比较消耗时间,自旋锁是线程通过CAS获取预期的一个标记,如果没有获取到,则继续循环获取,如果获取到了则表示获取到了锁,这个过程线程一直在运行中,相对而言没有使用太多的操作系统资源,比较轻量。

6、Tomcat中为什么要使用自定义类加载器?

一个Tomcat中可以部署多个应用,而每个应用中都存在很多类,并且各个应用中的类是独立的,全类名是可以相同的。
比如一个订单系统中可能存在com.Test.User类,一个库存系统中可能也存在com.Test.User类,一个Tomcat,不管内部部署了多少应用,Tomcat启动之后就是一个Java进程,也就是一个JVM,所以如果Tomcat中只存在一个类加载器,比如默认的AppClassLoader,那么就只能加载一个com.Test.User类,这是有问题的,而在Tomcat中,会为部署的每个应用都生成一个类加载器实例,名字叫做WebAppClassLoader,这样Tomcat中每个应用就可以使用自己的类加载器去加载自己的类,从而达到应用之间的类隔离,不出现冲突。另外Tomcat还利用自定义加载器实现了热加载功能。

7、对线程安全的理解

线程安全指的是:我们写的某段代码,在多个线程同时执行这段代码时,不会产生混乱,依然能够得到正常的结果。
比如i++,i初始化值为0,那么两个线程来同时执行这行代码,如果代码是线程安全的,那么最终的结果应该就是一个线程的结果为1,一个线程的结果为2,如果出现了两个线程的结果都为1,则表示这段代码是线程不安全的。
所以线程安全,主要指的是一段代码在多个线程同时执行的情况下,能否得到正确的结果

8、对守护线程的理解

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

9、并发、并行、串行之间的区别

串行:一个任务执行完,才能执行下一个任务
并行(Parallelism):两个任务同时执行
并发(Concurrency):两个任务整体看上去是同时执行,在底层,两个任务被拆成了很多份,然后一个一个执行,站在更高的角度看来两个任务是同时在执行的

10、Java死锁如何避免

造成死锁的几个原因
(1)一个资源每次只能被一个线程使用
(2)一个线程在阻塞等待某个资源时,不释放已占有资源
(3)一个线程已经获得的资源,在未使用完之前,不能被强行剥夺
(4)若干线程形成头尾相接的循环等待资源关系
这是造成死锁必须要达到的4个条件,如果要避免死锁,只需要不满足其中某一个条件即可。而其中前3个条件是作为锁要符合的条件,所以要避免死锁就需要打破第4个条件,不出现循环等待锁的关系。

注意:在开发过程中
(1)要注意加锁顺序,保证每个线程按同样的顺序进行加锁
(2)要注意加锁时限,可以针对所设置一个超时时间
(3)要注意死锁检查,这是一种预防机制,确保在第一时间发现死锁并进行解决

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值