什么是线程?什么是进程?
线程就是 进程中的一个最小执行单元。一个进程最少得有一个线程
进程就是 在内存中正在运行的程序
start和run的区别
start() 是启动线程的的方法,是真正实现了多线程
run()只是类的一个普通方法
在JAVA语言中,多线程的创建方式
多线程可以使用不同的方式来创建和管理,以下是四种常见的多线程创建方式:
-
继承Thread类: 创建一个继承自
java.lang.Thread类的子类,并重写run()方法来定义线程执行的任务。然后,创建子类的实例并调用start()方法启动线程。 -
实现Runnable接口:
创建一个实现java.lang.Runnable接口的类,实现其run()方法来定义线程执行的任务。然后,创建一个Thread对象,并将Runnable对象传递给它,最后调用start()方法启动线程。
- 实现Callable接口
它跟前两个线程的区别是,这个接口有返回值.
创建一个实现java.util.concurrent.Callable接口的类,实现其call()方法来定义线程执行的任务,并可以返回一个结果。使用ExecutorService来提交Callable任务并获取执行结果。
正常来说,我们一般是直接使用runable的,因为thread实际上是实现了runable的. 那什么时候该使用thread呢,什么时候该使用runable,因为java是单继承结构的,但是接口可以多继承,java里面有一个,单一职责原则,大概就是每一个服务和方法只实现一种功能. 三种方式的区别:thread是继承,runable和callable是实现.
- 使用线程池创建线程:
- 线程池是一种更高级的多线程管理方式,它可以重复使用线程来执行多个任务。使用
ExecutorService接口来创建和管理线程池,然后通过submit()方法提交任务。
线程池的七个核心参数
核心参数
1.核心线程数
2.最大线程数
3.阻塞队列 (一般最大线程数是多大,对列就是多大)
4.线程保活时间
5.临时线程保活时间单位
6.线程工厂
7.拒绝策略
AbortPolicy:丢弃任务并抛出RejectedExecutionException异常。 (默认策略)
DiscardPolicy:丢弃任务,但是不抛出异常。如果线程队列已满,则后续提交的任务都会被丢弃,且是静默丢弃。(静默策略)
DiscardOldestPolicy:丢弃队列最前面的任务,然后重新提交被拒绝的任务。(丢弃策略)
CallerRunsPolicy:由调用线程处理该任务
线程池的工作流程
当有任务提交的时候.
线程的五个状态
新建、就绪、运行、阻塞、死亡
join():插入线程
yield():礼让线程
wait():等待线程,需要等待notify()方法唤醒,但是需要注意的是,在你有多个线程的时候,这个唤醒线程的方法是随机唤醒一个线程,
notifyAll():是唤醒所有的线程
interrupt():中断线程
线程池的工作流程
线程池是一种用于管理和复用线程的机制,它可以提高多线程应用程序的性能和资源管理效率。以下是典型的线程池的工作流程:
-
初始化线程池:
- 创建一个线程池并初始化其参数,包括最小线程数、最大线程数、任务队列大小、线程空闲时间等。线程池的大小通常根据应用需求和系统资源来确定。
-
提交任务:
- 当需要执行任务时,将任务提交给线程池。任务可以是一个
Runnable或Callable对象,表示需要执行的工作单元。
- 当需要执行任务时,将任务提交给线程池。任务可以是一个
-
任务队列:
- 线程池维护一个任务队列,所有提交的任务都会排队在这个队列中等待执行。如果线程池中有可用的线程,它们会从队列中取出任务并执行。如果没有可用线程,任务会等待,直到有线程可用。
-
线程执行任务:
- 线程池中的线程会循环地从任务队列中取出任务并执行它们。一旦任务完成,线程将返回线程池中,准备执行下一个任务。
-
线程复用:
- 线程池会复用线程,而不是在每个任务之后销毁线程。这减少了线程创建和销毁的开销,提高了执行效率。
-
线程池管理:
- 线程池负责管理线程的数量和状态。它可以根据需要动态调整线程数量,以适应不同的工作负载。例如,可以根据队列中的任务数量来增加或减少线程的数量。
-
任务完成:
- 当任务执行完成后,可以获取任务的执行结果(如果任务是
Callable类型的)。然后可以对结果进行处理或返回给调用者。
- 当任务执行完成后,可以获取任务的执行结果(如果任务是
-
关闭线程池:
- 当不再需要线程池时,应该显式地关闭它。关闭线程池会停止接受新任务,并等待已提交的任务执行完成。然后线程池中的线程会被终止。关闭线程池是为了释放资源并避免内存泄漏。
线程池的主要优点在于可以有效地管理和复用线程,降低了线程创建和销毁的开销,提高了应用程序的性能和响应速度。它还可以控制并发线程的数量,避免资源耗尽问题。因此,在多线程应用程序中,使用线程池通常是一种良好的实践。
4、线程安全问题
Synchronized 与 Lock 的区别?
Synchronized
JVM层面、是关键字
出异常时会释放锁,不会出现死锁
不会手动释放锁,只能等同步代码块或方法结束后释放锁
Lock
API层面、是接口
出异常时不会释放锁,会出现死锁,需要在finally中手动释放锁
可以调用api手动释放锁
Synchronized
1.普通同步方法,锁是当前实例对象 this
2.静态同步方法,锁是当前类的class对象
3.同步代码块,锁是括号里面的对象【必须共享】
4.底层使用 monitorenter 和 monitorexit 指令实现的
锁分类
1、悲观锁与乐观锁
悲观锁
1.Synchronized
2.Lock
数据库的行锁、表锁
乐观锁
cas
2、公平锁与非公平锁
公平锁:按线程顺序获得锁
非公平锁:线程随机获得锁
3.死锁
死锁产生的原因
1、系统资源不足;
2、进程运行推进的次序不合适;
3、资源分配不当。
4、如果系统资源充足,进程的资源请求都能够得到满足,死锁出现的可能性就很低,否则就会因争夺有限的资源而陷入死锁。其次,进程运行推进顺序与速度不同,也可能产生死锁。
死锁产生的原因及四个必要条件
1、互斥条件:一个资源一次只能被一个进程访问。
2、请求与保持: 一个进程因请求资源而阻塞时,对已获得的资源保持不放。
3、不可剥夺:进程已获得的资源,在未使用完之前,不得强行剥夺。
4、循环等待:若干进程之间形成一种头尾相接的循环等待资源关系
线程通信
1、同步通信
wait、notify、notifyAll
jion方法
通过 volatile 关键字
2、异步通信
消息中间件
3、进程间的通信
http、feign
socket
mq
6、多线程并发
线程安全的特性
原子性
一个线程操作是不能被其他线程打断
有序性
线程在执行程序是有序的
可见性
一个线程修改数据后,对其他线程是可见的
volatile:
保证可见性,可以在一个线程修改了共享数据时对其他线程可见,其原理就是修改了变量副本值后及时同步到主内存,其他线程从主内存中获取
保证有序性,会禁止指令重排
指令重排:Java代码翻译成class文件后,最终在JVM执行时,是把class翻译一个个的指令进行执行的。而CPU为了程序的执行性能,会对指令进行重新排序
也就是说万一翻译后的指令是123,那么重排后的指令可能就是213。在多线程情况下,就会出现变量的可见性问题
是基于 内存屏障 来保证
有序性:
内存屏障(也称内存栅栏,内存栅障,屏障指令等,是一类同步屏障指令,是CPU或编译器在对内存随机访问的操作中的一个同步点,使得此点之前的所有读写操作都执行后才可以开始执行此点之后的操作),避免代码重排序。
内存屏障提供了避免重排序的功能
可见性:
内存屏障之前的所有写操作都要回写到主内存,内存屏障之后的所有读操作都能获得内存屏障之前的所有写操作的最新结果(实现了可见性)
内存屏障会把线程把工作内存中改变后的数据直接刷回主内存。其他线程就可以从主内存中获取最新数据
单例模式
定义一个应用中,该实例有且只有一个
双重校验锁
Spring集成线程池
@Async标记在方法上, 表示当前方法是异步执行的
@EnableAsync开启多线程异步