目录
1.基本概念
2.创建线程方式
2.1直接建立线程
2.2实现Runnable接口
3.3实现Callable接口
3.4 了解Future接口
Future模式主要角色及其作用
3.5实例化FutureTask类
3.实现线程安全
3.1定义
3.2不安全原因
3.3解决方案
3.4volatile与synchronized区别
3.5Lock与sychronized区别
4.极端情况——线程死锁
4.1定义
4.2解决措施
5.线程池的掌握
5.1核心参数
5.2生命周期
5.3处理流程
5.4Handler拒绝策略
5.5常见线程池
5.6线程池的使用
1.基本概念
线程状态:新建(New)、就绪(Runnable)、运行(Running)、阻塞(Blocked)、
等待(Waiting)、超时等待(Timed Waiting)、终止(Terminated)
锁(同步监视器):分为悲观锁和乐观锁,常见的有偏向锁、轻量级锁、重量级锁
CAS(Compare And Swap)是一种乐观锁的实现方式,通过比较并
交换来实现并发控制
2.创建线程方式
Runnable接口、Callable接口、Future接口、FutureTask类
2.1直接建立线程
创建Thread子类,按需求重写run()方法 【run代表此线程执行时会做的事】
这里的属性若为 多线程共享属性,加static修饰
2.2实现Runnable接口
这里的 多线程共享属性 可以是非静态的,因为多线程共用此接口
3.3实现Callable接口
(1)实现接口Callable,重写call()回调方法,会返回一个值,值类型由Callable泛型决定
实例化上述类,利用FutureTask(Callable)构造器实例化类
3.4 了解Future接口
异步调用的多线程开发模式之一(异步:当我们需要调用一个函数方法时,并不急着要结果。让被调者立即返回,让它在后台慢慢处理这个请求;此时则可以先处理一些其他任务)
Future模式主要角色及其作用
Main->调用Client发送请求
Data->返回数据的接口
Client->返回Data对象,立即返回FutureData,并开启ClientThread线程装配RealData
FutureData->虚拟数据,伪造数据立即返回,最终装配上RealData
RealData->真实数据,构造缓慢
3.5实例化FutureTask类
FutureTask用于异步获取执行结果或取消执行任务
通过传入Runnable或者Callable的任务给FutureTask,
直接调用其run方法或者放入线程池执行,
最后在外部通过FutureTask的get方法异步获取执行结果(适合耗时操作)
3.实现线程安全
3.1定义
多线程环境下,对共享资源的访问不会导致数据出错。
因此和单线程执行相同的操作,结果相同
3.2不安全原因
1. 线程是抢占式的执行,线程间的调度充满了随机性
2. 多个线程对同一个变量进行修改操作
3. 对变量的操作不是原子性的
4. 内存可见性导致的线程安全问题
5. 指令重排序也会影响线程安全
3.3解决方案
使用同步机制(如synchronized、Lock)、
使用线程安全的数据结构(如ConcurrentHashMap)
synchronized:Java关键字,属于隐式锁,可以修饰方法或代码块
Lock:JAVA接口,显式锁
原理上都是通过对共享资源加锁来实现同步。
3.4volatile与synchronized区别
volatile轻量级的锁,保证变量的可见性和有序性,但仅仅保证单个变量的原子性
可见性:对一个volatile变量的读,总是能看到(任意线程)对这个volatile变量最后的写入,解决多核CPU缓存可见性问题
有序性:采用内存屏障来实现的,就是在编译器生成字节码时,会在指令序列中插入内存屏障来禁止特定类型的处理器重排序
原子性:对单个volatile变量的读写具有原子性,对“volatile变量++”这种复合操作则不具有原子性,
而synchronized既保证可见性又保证原子性
volatile适用于单个变量的读写操作,而synchronized适用于复合操作或临界区。
3.5Lock与sychronized区别
使用方式: 1、Lock接口是显式锁,即我们需要调用其内部定义的方法显式地加锁和解锁,更加灵活,Lock对象创建Condition对象来实现线程通信
2、synchronized关键字是隐式锁,无需显式地获取和释放锁,使用方便
功能特性:Lock弥补了synchronized的不足,它新增了特性:1、可中断地获取锁,2、 非阻塞地获取锁,3、可超时地获取锁
实现机制:AQS是队列同步器,是用来构建锁的基础框架,Lock实现类都是基于AQS实现的
synchronized的底层是采用Java对象头来存储锁信息的,对象头包含三部分,分别是Mark Word、Class Metadata Address、Array length。
4.极端情况——线程死锁
4.1定义
多个线程相互等待对方释放资源,导致无法继续执行
4.2解决措施
避免嵌套锁、
按固定的顺序获取锁、
设置超时时间、
使用Lock对象代替synchronized关键字。
5.线程池的掌握
5.1核心参数
核心线程数(corePoolSize):依据任务的处理时间和每秒产生的任务数量来确定
最大线程数(maximumPoolSize):参照核心线程数和系统每秒产生的最大任务数决定
线程空闲时间(keepAliveTime):用户自设置合理时间间隔
任务队列(workQueue):一定的顺序或优先级来执行任务
拒绝策略(handler):线程池已经关闭或达到饱和(最大线程和队列都已满)状态时,新提交的
任务将会被拒绝。
5.2生命周期
线程池的生命周期包含5个状态:
- RUNNING:线程池正在运行,接受新任务并执行。
- SHUTDOWN:任务队列不清空,线程池不再接受新任务,等待执行的任务继续执行完毕。
- STOP:清空任务队列,不等待执行的任务,尝试终止正在执行的任务。
- TIDYING:当线程池及任务队列为空时进入该状态,线程池会执行钩子函数(待实现)。
- TERMINATED:线程池终止状态,所有任务执行完毕,线程池关闭。
注意:
在线程池的生命周期中,它的状态是不可逆的
5.3处理流程
向线程池提交一个任务之后,线程池按照如下步骤处理这个任务
注意:
1、新建的线程处理完当前任务后,变为空闲线程,继续处理等待队列中的任务。
2、如果线程的空闲时间达到了keepAliveTime,则线程池会销毁一部分线程,将线程数量收缩至corePoolSize。
5.4Handler拒绝策略
Hanlder通常处理消息,但线程池中帮助任务异步执行,发送消息并执行UI更新等操作
拒绝策略分别对应着RejectedExecutionHandler接口的4个实现类,
1、让调用者自己执行任务
2、直接抛出异常
3、丢弃任务不做任何处理
4、删除队列中最老的任务并把当前任务加入队列
5.5常见线程池
FixedThreadPool、CachedThreadPool、ScheduledThreadPool
5.6线程池的使用
1:利用Executors工厂类的静态方法,创建线程池对象;
2:编写Runnable或Callable实现类的实例对象;
3:利用ExecutorService的submit方法或ScheduledExecutorService的schedule方法提交并执行线程任务
4:如果有执行结果,则处理异步执行结果(Future)
5:调用shutdown()方法,关闭线程池