4 java多线程和高并发(待更新)

java多线程

这部分可以多参考剑指offer

https://blog.csdn.net/evankaka/article/details/44153709#t3
https://www.cnblogs.com/gaopeng527/p/4234211.html
https://www.cnblogs.com/lixuan1998/p/6937986.html
https://www.cnblogs.com/felixzh/p/6036074.html

并发编程三要素

(1)原子性
原子性指的是一个或者多个操作,要么全部执行并且在执行的过程中不被其他操作打断,要么就全部都不执行。
(2)可见性
可见性指多个线程操作一个共享变量时,其中一个线程对变量进行修改后,其他线程可以立即看到修改的结果。
(3)有序性
有序性,即程序的执行顺序按照代码的先后顺序来执行

并发编程中线程的通信和同步

通信方式:共享内存和消息传递
同步:线程之间通过发送消息来显式进行 通信。

Java中实现多线程有几种方法

主要有四种:

继承Thread类、

实现Runnable接口、

通过ExecutorService、Callable实现有返回值的线程

基于线程池

创建线程方式的对比

(1)采用实现 Runnable、Callable 接口的方式创建多线程。
优势是: 线程类只是实现了 Runnable 接口或 Callable 接口,还可以继承其他类。在这种方式下,多个线程可以共享同一个 target 对象,所以非常适合多个相同线程来处理同一份资源的情况。

(2)使用继承 Thread 类的方式创建多线程
优势是: 编写简单,如果需要访问当前线程,则无需使用 Thread.currentThread()方法,直接使用 this 即可获得当前线程。
劣势是: 线程类已经继承了 Thread 类,所以不能再继承其他父类。
(3)通过线程池创建

Runnable 和 Callable 的区别

1、Callable 规定(重写)的方法是 call(),Runnable 规定(重写)的方法是 run()。
2、Callable 的任务执行后可返回值,而 Runnable 的任务是不能返回值的。
3、Call 方法可以抛出异常,run 方法不可以。
4、运行 Callable 任务可以拿到一个 Future 对象,表示异步计算的结果。通过 Future对象可以了解任务执行情况,可取消任务的执行,还可获取执行结果。

进程有哪几种状态?(一般说三种)

  • 创建状态(new) :进程正在被创建,尚未到就绪状态。
  • 就绪状态(ready) :进程已处于准备运行状态,即进程获得了除了处理器之外的一切所需资源,一旦得到处理器资源(处理器分配的时间片)即可运行。
  • 运行状态(running) :进程正在处理器上上运行(单核 CPU 下任意时刻只有一个进程处于运行状态)。
  • 阻塞状态(waiting) :又称为等待状态,进程正在等待某一事件而暂停运行如等待某资源为可用或等待 IO 操作完成。即使处理器空闲,该进程也不能运行。
  • 结束状态(terminated) :进程正在从系统中消失。可能是进程正常结束或其他原因中断退出运行。

订正:下图中 running 状态被 interrupt 向 ready 状态转换的箭头方向反了。

在这里插入图片描述

什么是上下文切换

概括来说就是:当前任务在执行完 CPU 时间片切换到另一个任务之前会先保存自己的状态,以便下次再切换回这个任务时,可以再加载这个任务的状态。任务从保存到再加载的过程就是一次上下文切换。

上下文切换通常是计算密集型的。也就是说,它需要相当可观的处理器时间,在每秒几十上百次的切换中,每次切换都需要纳秒量级的时间。所以,上下文切换对系统来说意味着消耗大量的 CPU 时间,事实上,可能是操作系统中时间消耗最大的操作。

Linux 相比与其他操作系统(包括其他类 Unix 系统)有很多的优点,其中有一项就是,其上下文切换和模式切换的时间消耗非常少。

线程的生命周期 或者五种基本状态和过程

在这里插入图片描述
在这里插入图片描述

(1)创建状态(New):
当线程对象对创建后,即进入了新建状态,如:Thread t= new MyThread();

(2)就绪状态(Runnable):
当调用线程对象的 start()方法(t.start();),线程即进入就绪状态。处于就绪状态的线程,只是说明此线程已经做好了准备,随时等待 CPU 调度执行,并不是说执行了t.start()此线程立即就会执行;

(3)运行状态(Running):
当 CPU 开始调度处于就绪状态的线程时,此时线程才得以真正执行,即进入到运行状态。注:就绪状态是进入到运行状态的唯一入口,也就是说,线程要想进入运行状态执行,首先必须处于就绪状态中;

(4)阻塞状态(Blocked):
处于运行状态中的线程由于某种原因,暂时放弃对 CPU的使用权,停止执行,此时进入阻塞状态,直到其进入到就绪状态,才 有机会再次被 CPU 调用以进入到运行状态。根据阻塞产生的原因不同,阻塞状态又可以分为三种:
1)等待阻塞:运行状态中的线程执行 wait()方法,使本线程进入到等待阻塞状态;
2)同步阻塞:线程在获取 synchronized 同步锁失败(因为锁被其它线程所占用),它会进入同步阻塞状态;
3)其他阻塞:通过调用线程的 sleep()或 join()或发出了 I/O 请求时,线程会进入到阻塞状态。当 sleep()状态超时、join()等待线程终止或者超时、或者 I/O 处理完毕时,线程重新转入就绪状态。

(5)死亡状态(Dead): 线程执行完了或者因异常退出了 run()方法,该线程结束生命周期。

如何停止一个正在运行的线程

  • 正常运行结束

  • 使用退出标志(isinterrupt()方法:这个方法由对象调用,测试线程是否已经被中断,仅仅返回标志的状态,不会对标志有任何影响。),使线程正常退出,也就是当run方法完成后线程终止。

  • 使用stop方法强行终止线程(这个方法不推荐使用,因为stop和suspend、resume一样,也可能发生不可预料的结果)。

  • 使用interrupt方法中断线程。

什么是死锁

死锁是指多个进程因竞争资源而造成的一种僵局(互相等待) , 若无外力作用,这些进程都将无法向前推进。

死锁产生的四个必要条件

互斥条件: 在一段时间内某资源仅为一个进程所占有。 此时若有其他进程请求该资源, 则请求进程只能等待。
不剥夺条件: 进程所获得的资源在未使用完毕之前, 不能被其他进程强行夺走, 即只能由获得该资源的进程自己来释放(只能是主动释放)。
请求和保持条件: 一个进程因请求资源而阻塞时,对已获得的资源保持不放。
循环等待条件: 若干进程之间形成一种头尾相接的循环等待资源关系。

避免死锁 四种主要处理方法:

鸵鸟策略:直接忽略死锁。因为解决死锁问题的代价很高,因此鸵鸟策略这种不采取任务措施的方案会获得更高的性能。当发生死锁时不会对用户造成多大影响,或发生死锁的概率很低,可以采用鸵鸟策略。

死锁避免:简单来说就是破坏死锁产生的四个必要条件。

  • 避免一个进程同时获取多个锁
  • 避免一个进程在锁内同时占用多个资源,尽量保证每个锁只占用一个资源

死锁检测和解除

如何检测死锁:检测有向图是否存在环;或者使用类似死锁避免的检测算法。

死锁解除的方法:

  • 利用抢占:挂起某些进程,并抢占它的资源。但应防止某些进程被长时间挂起而处于饥饿状态;
  • 利用回滚:让某些进程回退到足以解除死锁的地步,进程回退时自愿释放资源。要求系统保持进程的历史信息,设置还原点;
  • 利用杀死进程:强制杀死某些进程直到死锁解除为止,可以按照优先级进行。

线程通信

全局变量,消息队列

进程/线程 同步

临界区,互斥量,信号量,事件

sleep wait yield notify notifyAll join

一. Sleep 与 wait 区别

1.sleep 是线程类(Thread) 的方法, 导致此线程暂停执行指定时间, 给执行机会给其他线程, 但是监控状态依然保持, 到时后会自动恢复。 调用 sleep 不会释放对象锁。 sleep()使当前线程进入阻塞状态, 在指定时间内不会执行。

2.wait 是 Object 类的方法, 对此对象调用 wait 方法导致本线程放弃对象锁, 进入等待此对象的等待锁定池, 只有针对此对象发出 notify 方法(或 notifyAll) 后本线程才进入对象锁定池准备获得对象锁进入运行状态。

区别比较:
1、 这两个方法来自不同的类分别是 Thread 和 Object
2、 最主要是 sleep 方法没有释放锁, 而 wait 方法释放了锁, 使得其他线程可以使用同步控制块或者方法。
3、 wait, notify 和 notifyAll 只能在同步控制方法或者同步控制块里面使用, 而 sleep 可以在任何地方使用(使用范围)
4、 sleep 必须捕获异常, 而 wait, notify 和 notifyAll 不需要捕获异常

Thread 类中的start() 和 run() 方法有什么区别?

new 一个 Thread,线程进入了新建状态。调用 start()方法,会启动一个线程并使线程进入了就绪状态,当分配到时间片后就可以开始运行了。 start() 会执行线程的相应准备工作,然后自动执行 run() 方法的内容,这是真正的多线程工作。 但是,直接执行 run() 方法,会把 run() 方法当成一个 main 线程下的普通方法去执行,并不会在某个线程中执行它,所以这并不是多线程工作。

总结: 调用 start() 方法方可启动线程并使线程进入就绪状态,直接执行 run() 方法的话不会以多线程的方式执行。

线程池的核心组件和核心类

线程池管理器:用于创建并管理线程池

工作线程:线程池中执行具体任务的线程

任务接口:用于定义工作线程的调度和执行策略,线程只有实行了改接口,线程中的任务才能被线程池调度

任务队列:存放待处理的任务,新的任务会不断的被加入队列中,执行完成的任务将会被从队列中移除。

核心类:P63

ThreadPoolExecutor线程池的核心类构造函数。

corePoolSize:指定了线程池中的主线程数量;

maximumPoolSize:指定了线程池中的最大线程数量;

keepAliveTime:当线程池中空闲线程数量超过corePoolSize时,多余的线程会在多长时间内被销毁;

unit:keepAliveTime的单位;

workQueue:任务队列,被提交但是还没有执行的任务存放的地方

threadFactory:线程工厂,用于创建线程,一般用默认即可;

handler:拒绝策略;

线程池工作原理用线程池的好处

img

当提交一个新任务到之后, 线程池的处理流程如下:

在这里插入图片描述

  • 线程复用: 通过重复利用已创建的线程降低线程创建和销毁造成的消耗。
  • 提高响应速度。当任务到达时,任务可以不需要等到线程创建就能立即执行。
  • 提高线程的可管理型。 使用线程池可以进行统一分配、 调优和监控。

常用的线程池有哪些?

Executors.newFixedThreadPool(int):创建一个固定线程数量的线程池,可控制线程最大并发数,超出的线程需要在队列中等待。注意它内部corePoolSize和maximumPoolSize的值(就是第一和第二个参数 nThreads)是相等的,并且使用的是LinkedBlockingQueue(背后使用的是无界的工作队列):

Executors.newSingleThreadExecutor():创建一个单线程的线程池,它只有唯一的线程来执行任务,保证所有任务按照指定顺序执行。注意它内部corePoolSize和maximumPoolSize的值都为1,它使用的是LinkedBlockingQueue(无界限的工作队列):LinkedBlockingQueue是一个无界缓存等待队列。当前执行的线程数量达到corePoolSize的数量时,剩余的元素会在阻塞队列里等待。(所以在使用此阻塞队列时maximumPoolSizes就相当于无效了)

Executors.newCachedThreadPool():会试图缓存线程并重用,创建一个可缓存的线程池,如果线程长度超过处理需要,可灵活回收空闲线程,若无可回收线程,则创建新线程。注意它内部将corePoolSize值设为0,maximumPoolSize值设置为Integer.MAX_VALUE,并且使用的是SynchronizedQueue,keepAliveTime值为60,即当线程空闲时间超过60秒,就销毁线程:
SynchronousQueue没有容量,是无缓冲等待队列,是一个不存储元素的阻塞队列,会直接将任务交给消费者

Executors.newScheduledThreadPool(int):创建一个固定线程数量的线程池,相比于newFixedThreadPool(int)固定个数的线程池强大在 ①可以执行延时任务,②也可以执行带有返回值的任务,并且使用的是
DelayedWorkQueue:基于堆结构的等待队列,DelayedWorkQueue取根节点延迟时间,然后等待,直到延迟结束,从新取根节点达到延迟取元素。

重要一点就是任务队列会根据任务延时时间的不同进行排序,延时时间越短地就排在队列的前面,先被获取执行。

无界队列有界队列

与有界队列相比,除非系统资源耗尽,否则无界的任务队列不存在任务入队失败的情况。无界队列会保持快速增长,直到耗尽系统内存。

线程池和Executor框架

在这里插入图片描述

ThreadPoolExecutor

Executor框架最核心的类是ThreadPoolExecutor,它是线程池的实现类,主要由下列4个组件构成。

  • corePool:核心线程池的大小。
  • maximumPool:最大线程池的大小。
  • BlockingQueue:用来暂时保存任务的工作队列。
  • RejectedExecutionHandler:当ThreadPoolExecutor已经关闭或ThreadPoolExecutor已经饱和 时(达到了最大线程池大小且工作队列已满),execute()方法将要调用的Handler。
序号 名称 类型 含义
1 corePoolSize int 核心线程池大小
2 maximumPoolSize int 最大线程池大小
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值