十五、线程
-
什么是线程?
-
线程(thread)是一个程序内部的一条执行路径
-
-
程序中如果只有一条执行路径,那么这个程序就是单线程的程序
-
-
多线程是什么?
-
多线程是指从软硬件上实现多条执行流程的技术
-
多线程的创建
-
方式一:继承Thread类
-
Thread类
-
Java是通过java.lang.Thread类来代表线程的
-
按照面向对象的思想,Thread类应该提供了实现多线程的方式
-
-
优点:编码简单
-
缺点:线程类已经继承Thread,无法继承其他类,不利于扩展
-
-
方式二:实现Runnalbe接口
-
Thread的构造器
-
构造器 说明 public Thread(String name) 可以为当前线程指定名称 public Thread(Runnable target) 封装Runnable对象成为线程对象 public Thread(Runnable target, String name) 封装Runnable对象成为线程对象,并指定线程名称
-
-
优点:线程任务类只是实现接口,可以继续继承类和实现接口,扩展性强
-
缺点:编程多一层对象包装,如果线程有执行结果是不可以直接返回的
-
-
方式三:JDK 5.0新增:实现Callable接口
-
FutureTask的API
-
方法名称 说明 public FutureTask<>(Callable call) 把Callable对象封装成FutureTask对象 public V get() throws Exception 获取线程执行call方法返回的结果
-
-
优点:
-
线程任务类只是实现接口,可以继续继承类和实现接口,扩展性强
-
可以在线程执行完毕后去获取线程执行的结果
-
-
缺点:编码复杂一点
-
Thread的常用方法
-
Thread常用方法:获取线程名称getName()、设置名称setName()、获取当前线程对象currentThread()
-
Thread获取和设置线程名称
-
方法名称 说明 String getName() 获取当前线程的名称,默认线程名称是Thread-索引 void setName(String name) 将此线程的名称更改为指定的名称,通过构造器也可以设置县城名称
-
-
Thread的构造器
-
方法名称 说明 public Thread(String name) 可以为当前线程指定名称 public Thread(Runnable target) 封装Runnable对象成为线程对象 public Thread(Runnable target , String name) 封装Runnable对象成为线程对象,并指定线程名称
-
-
Thread类的线程休眠方法
-
方法名称 说明 public static void sleep(long time) 让当前线程休眠指定的时间后在继续执行,单位为毫秒
-
线程安全
-
线程安全问题是什么、发生的原因
-
多个线程同时操作同一个共享资源的时候可能会出现业务安全问题,称为线程安全问题
-
-
线程安全问题案例模拟
-
thread-app.src.com.itheima.d3_thread_safe
-
线程同步
-
同步思想概述
-
为了解决线程安全问题
-
线程同步核心思想:加锁,把共享资源进行上锁,每次只能一个线程进入访问完毕以后解锁,然后其他线程才能进来
-
-
方式一:同步代码块
-
作用:把出现线程安全问题的核心代码给上锁
-
原理:每次只能一个线程进入,执行完毕后自动解锁,其他线程才可以进来执行
-
锁对象要求
-
理论上:锁对象只要对于当前同时执行的线程来说是同一个对象即可
-
规范上:**建议使用共享资源作为锁对象
-
对于实例方法建议使用this作为锁对象
-
对于静态方法建议使用字节码(类名.class)作为锁对象
-
-
-
方式二:同步方法
-
作用:把出现线程安全问题的核心方法给上锁
-
原理:每次只能一个线程进入,执行完毕以后自动解锁,其他线程才可以进来执行
-
同步方法底层原理
-
同步方法其实底层也是有隐式锁对象的,只是锁的范围是整个方法代码
-
如果方法是实例方法:同步方法默认用this作为的锁对象。但是代码要高度面向对象
-
如果方法是静态方法:同步方法默认用类名.class作为的锁对象
-
-
-
方式三:Lock锁
-
为了更清晰的表达如何加锁和释放锁,JDK5以后提供了一个新的锁对象Lock,更加灵活、方便
-
Lock实现提供比使用synchronized方法和语句可以获得更广泛的锁定操作
-
Lock是接口不能直接实例化,这里采用他的实现类ReentrantLock来构建Lock锁对象
-
方法名称 说明 public ReetrantLock() 获得Lock锁的实现类对象
-
-
Lock的API
-
方法名称 说明 void lock() 获得锁 void unlock() 释放锁
-
-
线程通信
-
所谓线程通信就是线程间相互发送数据,线程通信通常通过共享一个数据的方式实现
-
线程间会根据共享数据的情况决定自己该怎么做,以及通知其他线程怎么做
-
线程通信的前提:线程通信通常是在多个线程操作同一个共享资源的时候需要进行通信,且要保证线程安全
-
Object类的等待和唤醒方法:
-
方法名称 说明 void wait() 让当前线程等待并释放所占锁,直到另一个线程调用notify()方法或notifyAll()方法 void notify() 唤醒正在等待的单个线程 void notifyAll() 唤醒正在等待的多个线程 -
注意:上述方法应该使用当前同步锁对象进行调用
-
线程池
-
线程池概述
-
线程池就是一个可以复用线程的技术
-
不适用线程池的问题
-
如果用户每发起一个请求,后台就创建一个新线程来处理,下次新任务来了又要创建新线程,而创建新线程的开销是很大的,这样会严重影响系统的性能
-
-
-
线程池实现的API、参数说明
-
JDK5.0起提供了代表线程池的接口:ExecutorService
-
如何得到线程池对象
-
方式一:使用ExecutorService的实现类ThreadPoolExecutor自创建一个线程池对象
-
方式而:使用Executors(线程池的工具类)调用方法返回不同特点的线程池对象
-
-
ThreadPoolExecutor构造器的参数说明
-
参数一:指定线程池的线程数量(核心线程):corePoolSize ------->不能小于0
-
参数二:指定线程池可支持的最大线程数:maximumPoolSize------>最大数量>=核心线程数量
-
参数三:指定临时线程的最大存活时间:keepAliveTime------>不能小于0
-
参数四:指定存活时间的单位(秒、分、时、天):unit---------->时间单位
-
参数五:指定任务队列:workQueue------>不能为null
-
参数六:指定用哪个线程工厂创建线程:threadFactory----->不能为null
-
参数七:指定线程忙,任务满的时候,新任务来了怎么办:handler-------->不能为null
-
-
临时线程什么时候创建?
-
新任务提交时发现核心线程都在忙,任务队列也满了,并且还可以创建临时线程,此时才会创建临时线程
-
-
什么时候会开始拒绝任务?
-
核心线程和临时线程都在忙,任务队列也满了,新的任务过来的时候才会开始任务拒绝
-
-
-
线程池处理Runnable任务
-
ExecutorService的常用方法
方法名称 说明 void execute(Runnable command) 执行任务/命令,没有返回值,一般用来执行Runnable任务 Future<T> submit(Callable<T> task) 执行任务,返回未来任务对象获取线程结果,一般拿来执行Callable任务 void shutdown() 等任务执行完毕后关闭线程池 List<Runnable> shutdownNow() 立刻关闭,停止正在执行的任务,并返回队列中为执行的任务 -
新任务拒绝策略
-
策略 详解 ThreadPoolExecutor.AbortPolicy 丢弃任务并抛出RejectedExecutionException异常。是默认的策略 ThreadPoolExecutor.DiscardPolicy: 丢弃任务,但是不抛出异常这是不推荐的做法 ThreadPoolExecutor.DiscardOldestPolicy 抛弃队列中等待最久的任务然后把当前任务加入队列中去 ThreadPoolExecutor.CallerRunsPolicy 由主线程负责调用任务的run()方法从而绕过线程池直接执行
-
-
-
线程池处理Callable任务
-
thread-app.src.com.itheima.d8_threadpool.ThreadPoolDemo2
-
-
Executors工具类实现线程池
-
Executors得到线程池对象的常用方法
-
Executors:线程池的工具类通过调用方法返回不同类型的线程池对象
-
方法名称 说明 public static ExecutorService newCachedThreadPool() 线程数量随着任务增加而增加,如果线程任务执行完毕且空闲了一段时间则会被回收掉 public static ExecutorService newFixedThreadPool(int nThreads) 创建固定线程数量的线程池,如果某个线程因为执行异常而结束,那么线程池会补充一个新线程替代他 public static ExecutorService newSingleThreadExecutor() 创建只有一个线程的线程池对象,如果该线程出现异常而结束,那么线程池会补充一个新线程 public static ScheduledExecutorService newScheduledThreadPool(int corePoolSize) 创建一个线程池,可以实现在给定的延迟后运行任务,或者定期执行任务
-
-
注意:Executors的底层其实也是基于线程池的实现类ThreadPoolExecutor创建线程池对象的
-
Executors使用可能存在的陷阱
-
大型并发系统环境中使用Executors如果不注意可能出现系统风险
-
方法名称 存在问题 public static ExecutorService newFixedThreadPool(int nThreads) 允许请求的任务队列长度是Integer.MAX_VALUE,可能出现OOM错误(java.lang.OutOfMemoryError) public static ExecutorService newSingleThreadExecutor() 允许请求的任务队列长度是Integer.MAX_VALUE,可能出现OOM错误(java.lang.OutOfMemoryError) public static ExecutorService newCachedThreadPool() 创建的线程数量最大上限是Integer.MAX_VALUE,线程数可能会随着任务1:1增长,也可能出现OOM错误(java.lang.OutOfMemoryError) public static ScheduledExecutorService newScheduledThreadPool(int corePoolSize) 创建的线程数量最大上限是Integer.MAX_VALUE,线程数可能会随着任务1:1增长,也可能出现OOM错误(java.lang.OutOfMemoryError)
-
-
定时器
-
定时器是一种控制任务延时调用,或者周期调用的技术
-
作用:闹钟、定时邮件发送
-
定时器实现的方式
-
方式一:Timer
-
构造器 说明 public Timer() 创建Timer定时器对象 方法 说明 public void schedule(TimerTask task,long delay,long period) 开启一个定时器,按照计划处理TimerTask任务 -
特点和存在的问题:
-
Timer是单线程,处理多个任务按照顺序执行,存在延时与设置定时器的时间有出入
-
可能因为其中的某个任务的异常使Timer线程死掉,从而影响后续任务执行
-
-
-
方式二:ScheduledExecutorService
-
是jdk1.5中引入了并发包,目的是为了弥补Timer的缺陷,ScheduledExecutorService内部为线程池
-
Executors的方法 说明 public static ScheduledExecutorService newScheduledThreadPool(int corePoolSize) 得到线程池对象 -
ScheduledExecutorService的方法 说明 public ScheduledFuture<?> scheduleAtFixedTate(Runnable command,long initialDelay,long period,TimeUnit unit) 周期调度方法 -
优点:基于线程池,某个任务的执行情况不会影响其他定时任务的执行
-
-
补充知识:生命周期、并发并行
-
并发与并行:正在运行的程序(软件)就是一个独立的进程,线程是属于进程的,多个线程其实是并发与并行同时进行的
-
并发的理解:
-
CPU同时处理线程的数量有限
-
CPU会轮询为系统的每个线程服务,由于CPU切换的速度很快,给我们的感觉这些线程在同事执行,这就是并发
-
-
并行的理解:在同一时刻上,同时有多个线程在被CPU处理并执行
-
线程的状态:
-
线程的状态:也就是线程从生到死的过程,以及中间经历的各种状态及状态的转换
-
理解线程的状态有利于提升并发编程的理解能力
-
-
Java线程的状态
-
Java总共定义了6种状态
-
New 新建
-
Runnable 可运行
-
Teminated 被终止
-
Blocked 锁阻塞
-
Waiting 无限等待
-
Timed Waiting(计时等待)
-
线程状态 描述 NEW(新建) 线程刚被创建,但是并未启动 Runnable(可运行) 线程已经调用了start()等待CPU调度 Blocked(锁阻塞) 线程在执行的时候未竞争到锁对象,则该线程进入Blocked状态 Waiting(无线等待) 一个线程进入Waiting状态,另一个线程调用notify或者notfyall方法才能唤醒 Timed Waiting(计时等待) 同waiting状态,有几个方法有超时参数,调用他们将进入Timed Waiting状态。带有超时参数的常用方法有Thread.sleep、Object.wait Teminated(被终止) 因为run方法正常退出而死亡,或者因为没有捕获的异常终止了run方法而死亡
-
-
6种状态都定义在Thread类的内部枚举类中
-