Java基础篇-多线程八股文

一、Java中多线程实现方式

1.实现Runnable

2.继承Thread,覆写run方法。

3.实现Callable接口,重写Call方法,利用FutureTask包装Callable。并作为task传入Thread构造函数。

4.线程池

两者的不同点在于:Thread类中的run方法调用的其实也是子类Runnable中的run方法。如果通过继承Thread则必须覆写run方法。

如果一个类继承了Thread类,则不适合多线程共享资源。但是Runnable接口可以方便实现资源共享。

二、线程的五种状态

创建:new Thread

就绪:线程队列排队,等待cpu服务。

运行:获得cpu资源。调用run方法

阻塞:人为挂起,sleep、wait、suspend等,阻塞取消后重新进入就绪状态。

死亡:stop或run方法执行完成。

java程序启动时至少启动了两个线程,一个是main方法线程、一个是垃圾回收线程

线程的强制运行可以使用join方法。强制运行时其它方法进入就绪状态,等待该线程执行完毕。

线程可以使用yield方法将一个线程的操作暂时让给其它线程执行。

线程优先级可使用thread.setPriority()

三、同步和死锁

如果一个线程使用Runnable接口实现,则意味类中的属性被多个线程共享。那么就会出现资源同步问题。可以使用同步代码块:synchronized{}。

死锁

互斥:一次只有一个线程可以使用一个资源。

占有且等待:当一个线程等待分配得到其它资源时,还继续占有已分配得到的资源;或者说当前线程需要三个资源才能继续执行后面的操作,但是其中的一个资源正在被占有,因此该线程需要持续进行等待资源释放。

非抢占:不能强行抢占线程中已占有的资源。

循环等待:存在一个封闭的进程链,也就是A在等待B,B在等待A。

如何预防死锁?

1.添加锁顺序。

2.加锁时限(线程请求所加上权限,超时就放弃,同时释放自己占有的锁)。

3.死锁检测。

四、synchronized

synchronized在jdk1.6之前是重量级锁,因为它的实现直接调用ObjectMonitor的enter和exit。

但在jdk1.6之后,HotSpot团队对锁进行了优化,例如增加了适应性自旋、锁消除、锁粗化、轻量级锁、偏向锁

在synchronized修饰代码块时,JVM采用monitorEnter、monitorExit两个指令来实现同步。

synchronized修饰同步方法时,JVM采用ACC_SYNCHORNIZED标记来实现同步。

monitorenter、monitorexit、ACC_SYNCHORNIZED都是基于monitor实现的。

monitor是一种同步工具,也可以说是一种同步机制

五、锁状态

偏向锁:在无竞争的情况下,将整个同步都消除掉,CAS操作都不做

轻量级锁:在没有多线程竞争时,相对重量级锁,减少系统互斥量带来的性能消耗。如果存在锁竞争,除了互斥量本身的开销,还有额外的CAS操作开销。

自旋锁:减少不必要的CPU上下文切换。在轻量级锁升级为重量级锁的时候,就使用了自旋锁的方式

锁粗化:将多个连续的加锁、解锁操作连接在一起,将他们视为一个集体。扩展成一个范围更大的锁。

锁消除:虚拟机即时编译器在运行时,对一些代码上的要求同步,但是被检测到不可能存在共享数据竞争的锁进行消除。

六、ThreadLocal

线程本地变量,如果创建了一个ThreadLocal变量,那么访问这个变量的每个线程都会有这个变量的本地拷贝。

多个线程操作这个变量的时候,实际是操作自己本地内存里的变量。从而起到线程隔离作用,避免了线程安全问题。

实现原理:

由于每个线程都有一个属于自己的ThreadLocalMap。它里面维护着Entry数组,每个Entry代表一个完整的对象,key是ThreadLocal的本身,Value是ThreadLocal的泛型。每个线程在设置值时,就会往ThreadLocal的ThreadLocalMap里存,读也是以某个ThreadLocal作为引用,在自己的Map里找对应的key,从而实现了线程隔离。

ThreadLocal的内存泄露

由于ThreadLocalMap的key为ThreadLocal的弱引用,因此如果当它被GC时,但是Thread的生命周期并未结束,就会出现key消失,但value还在,从而导致内存泄漏的问题。

弱引用:只要垃圾回收机制一运行,不管JVM的内存空间是否充足,都会回收该对象占用的内存。

使用完ThreadLocal后,及时调用remove方法释放空间即可。

七、Synchronized和ReentrantLock

synchronized是java语言的关键字,基于JVM实现。ReentrantLock基于JDK的API实现(一般通过lock和unlock方法来完成)

ReentrantLock比synchronized增加了一些功能,例如等待可中断、公平锁、选择性通知等。

等待可中断:通过lock.lockInterruptibly()来实现这个机制

synchronized只能是非公平锁,而ReentrantLock可以指定公平锁或非公平锁。公平锁就是先等待先获取锁

synchronized与wait()和notify()/nofityAll()方法结合来实现等待、通知机制。

ReentrantLock类借助Condition接口与newCondition()方法实现

ReentrantLock需要通过lock和unlock的方式来加锁和释放锁,synchronized则不需要手动释放。

八、Start和Run

JVM执行start方法,会另起一个线程执行Thread的run方法。这时会起到多线程的效果。

如果直接调用Thread的run方法,其方法还是运行在主线程中,并没有起到多线程的效果。

九、线程池的创建

9.1 两种创建方式

通过 ThreadPoolExecutor 手动创建线程池

通过 Executors 执行器自动创建线程池

9.2 七种实现方式

1.Executors.newFixedThreadPool:创建一个固定大小的线程池,可控制并发的线程数,超出的线程会在队列中等待。

2.Executors.newCachedThreadPool:创建一个可缓存的线程池,若线程数超过处理所需,缓存一段时间后会回收,若线程数不够,则新建线程。

3.Executors.newSingleThreadExecutor:创建单个线程数的线程池,它可以保证先进先出的执行顺序。

4.Executors.newScheduledThreadPool:创建一个可以执行延迟任务的线程池。 5.Executors.newSingleThreadScheduledExecutor:创建一个单线程的可以执行延迟任务的线程池。

6.Executors.newWorkStealingPool:创建一个抢占式执行的线程池(任务执行顺序不确定)【JDK 1.8 添加】。

7.ThreadPoolExecutor:手动创建线程池的方式,它创建时最多可以设置 7 个参数。

猜你喜欢

转载自blog.csdn.net/qq_33351639/article/details/129121493