多线程

发布时间 2023-09-26 00:15:20作者: 牧丶归

1.什么是线程?什么是进程?

线程是指进程内部的一个独立执行单元,一个进程可以同时并发的运行多个线程,可以理解为一个进程便相当于一个单CPU操作系统,而线程便是这个系统中运行的多个任务。进程是指一个内存中运行的应用程序,就比如点电脑上运行的电脑管家等软件,每个进程都有一个独立的内存空间,一个应用程序可以同时运行多个进程,进程也是程序的一次执行过程,是系统运行程序的基本单位,系统运行一个程序即是一个进程从创建、运行到消亡的过程

2.多线程的创建方式

1、继承Thread类,重写run方法

2、实现Runnable接口,重写run方法

3、实现Callable接口

继承Thread类和实现Runnable接口,一般选择实现Runnable接口,因为实现Runnable接口的方式适合多个相同的程序代码的线程去共享同一个资源,而且还可以避免java中的单继承的局限性,还能增加程序的健壮性,实现解耦操作,代码可以被多个线程共享,代码和数据独立,而且线程池只能放入实现Runnable或callable类线程,不能直接放入继承Thread得嘞

实现Runable接口和实现Callable接口的区别就在于实现Runable接口重写的run方法没有返回值,但是实现Callable接口有返回值。

3.线程池

工作流程:

1.当有任务提交是时,首先要判断任务的数量有没有达到核心线程数量,如果没达到,那就创建一个工作线程来执行任务,如果达到了核心线程数,还有多的任务,那就会去判断工作队列是否已经满了,如果没满的话,则会将新提交的任务储存在工作队列里,如果满了的话,就需要在去看是否达到了线程池的最大数量,如果没达到,则会创建一个新的工作线程来执行任务。如果达到了线程池的最大数量,还有的话就会执行拒绝策略来处理这个任务

核心参数: 核心线程数、最大线程数、阻塞队列、线程保活时间、临时线程保活、时间单位、线程工厂、拒绝策略

拒绝策略分为以下几种:

1、AbortPolicy:丢弃任务并抛出RejectedExecutionException异常。 (默认)

2、DiscardPolicy:丢弃任务,但是不抛出异常。如果线程队列已满,则后续提交的任务都会被丢弃,且是静默丢弃。

3、DiscardOldestPolicy:丢弃队列最前面的任务,然后重新提交被拒绝的任务。

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

线程池创建的时候里面没有线程,只有在任务提交过来的时候才会创建线程去执行。如果到达了最大线程数,那么核心线程数就是最大线程数。

4.守护线程

Java中有两种线程,一种是用户线程,另一种是守护线程。用户线程是指用户自定义创建的线程,主线程停止,用户线程不会停止。守护线程当进程不存在或主线程停止,守护线程也会被停止

5.线程安全相关问题

线程安全问题都是由全局变量及静态变量引起的。若每个线程中对全局变量、静态变量只有读操作,而无写操作,一般来说,这个全局变量是线程安全的;若有多个线程同时执行写操作,一般都需要考虑线程同步,否则的话就可能影响线程安全。

6.如何解决

当我们使用多个线程访问同一资源的时候,且多个线程中对资源有写的操作,就容易出现线程安全问题。 要解决多线程并发访问一个资源的安全问题,可以使用锁

Synchronized:JVM层面、是关键字,出异常时会释放锁,不会出现死锁,不过他不会手动释放锁,只能等同步代码块或方法结束后释放锁

Lock:API层面、是接口,出异常时不会释放锁,会出现死锁,需要在finally中手动释放锁,可以调用api手动释放锁

Synchronized: 普通同步方法,锁是当前实例对象 this 静态同步方法,锁是当前类的class对象 同步代码块,锁是括号里面的对象【必须共享】,可以使用 monitorenter 和 monitorexit 指令实现的

锁分类:

1、悲观锁与乐观锁

悲观锁:Synchronized,Lock,数据库的行锁、表锁

乐观锁:cas:比较和交换;

在CAS(比较与交换)算法中涉及3个操作数:变量当前内存值V、变量的预期值E、新值U。只有该变量当前的内存值V与预期值E相同时,才会将新值U写入内存完成变量修改,否则什么都不做。下面是通过CAS修改变量数据的示例,CAS通过该变量的地址即可获取该变量当前的内存值V。当本轮CAS操作失败后,会重新读取该变量内存中最新的值并重新计算新值,直到其CAS操作修改变量成功为止

2、公平锁与非公平锁

公平锁:按线程顺序获得锁

非公平锁:线程随机获得锁

公平锁能保证:老的线程排队使用锁,新线程仍然排队使用锁。

非公平锁保证:老的线程排队使用锁;但是无法保证新线程抢占已经在排队的线程的锁

死锁: 死锁产生的原因:

1、系统资源不足;

2、进程运行推进的次序不合适;

3、资源分配不当。

4、如果系统资源充足,进程的资源请求都能够得到满足,死锁出现的可能性就很低,否则就会因争夺有限的资源而陷入死锁。其次,进程运行推进顺序与速度不同,也可能产生死锁。

死锁产生的原因及四个必要条件:

1、互斥条件:一个资源一次只能被一个进程访问。

2、请求与保持: 一个进程因请求资源而阻塞时,对已获得的资源保持不放。

3、不可剥夺:进程已获得的资源,在未使用完之前,不得强行剥夺。

4、循环等待:若干进程之间形成一种头尾相接的循环等待资源关系

7.线程状态

状态描述:

NEW(新建) 线程刚被创建,但是并未启动。

RUNNABLE(可运行) 线程可以在java虚拟机中运行的状态,可能正在运行自己代码,也可能没有,这取决于操作系统处理器。

BLOCKED(锁阻塞) 当一个线程试图获取一个对象锁,而该对象锁被其他的线程持有,则该线程进入Blocked状态;当该线程持有锁时,该线程将变成Runnable状态。

WAITING(无限等待) 一个线程在等待另一个线程执行一个(唤醒)动作时,该线程进入Waiting状态。进入这个状态后是不能自动唤醒的,必须等待另一个线程调用notify或者notifyAll方法才能够唤醒。 TIMED_WAITING(计时等待) 同waiting状态,有几个方法有超时参数,调用他们将进入Timed Waiting状态。这一状态将一直保持到超时期满或者接收到唤醒通知。带有超时参数的常用方法有Thread.sleep 、Object.wait。

TERMINATED(被终止) 因为run方法正常退出而死亡,或者因为没有捕获的异常终止了run方法而死亡。

8.线程通信

1、同步通信

 wait、notify、notifyAll jion方法 通过 volatile 关键字

2、异步通信

 消息中间件

3、进程间的通信

 http、feign、socket、mq

9.多线程并发

线程安全的特性 原子性:一个线程操作是不能被其他线程打断 有序性:线程在执行程序是有序的 可见性:一个线程修改数据后,对其他线程是可见的

volatile:保证可见性和有序性,可见性就是可以在一个线程修改了共享数据时对其他线程可见,其原理就是修改了变量副本值后及时同步到主内存,其他线程从主内存中获取 保证有序性的话就是他会禁止指令重排,指令重排就是把Java代码翻译成class文件后,最终在JVM执行时,是把class文件翻译一个个的指令进行执行的。而CPU为了程序的执行性能,会对指令进行重新排序

volatile是基于 内存屏障 来保证有序性和可见性的,保证有序性,因为内存屏障它本身就是是一类同步屏障指令,他提供了避免重排序的功能。保证可见性的话就是,内存屏障会把线程把工作内存中改变后的数据直接刷回主内存。其他线程就可以从主内存中获取最新数据

10.单例模式

多线程下的单例模式 使用synchronized修饰方法,可以实现多线程下的单例模式

但是每次调用该方法,都会给方法加锁,而只有第一次创建对象的时候需要加锁,其他时候都不需要,这样会导致程序的效率地下,那么可以使用双重检查锁+volatile,它可以解决了所有可能出现的问题

11.Spring集成线程池

@Async标记在方法上, 表示当前方法是异步执行的

在启动类上加@EnableAsync注解,开启多线程异步