线程:
创建线程方法1:继承Thread,重写run()方法,start()开启线程
创建线程方法2:实现Runable接口,重写run()方法,将Runable对象传参到Thread对象,satart()开启线程
创建线程方法3:直接把Runnable实现类用匿名内部类实现,Runable接口中只有一个抽象方法,可以用lambda表达式
创建线程方法4:实现Callable接口,重写call方法,创建Future的实现类对象FutureTask对象传参到Thread对象,start()启动线程
创建线程方法5:使用线程池Executor或ThreadPoolExecutor(推荐),线程池内容在文末。
方法选择:
如果需要继承其他类:Runnable
不需要继承其他类:Thread
需要获取线程结束的返回值:Callable
一些概念:
进程:运行的应用程序,一个进程有1一个或多个线程
线程:应用程序中可以做的事,cpu调度的最小单位
并发:同一时刻,多个线程在一个CPU上交替执行
并行:同一时刻,多个线程在多个CPU上同时执行
优先级:确定线程的执行顺序,使用的是抢占式调度模型:优先让优先级高的线程使用CPU,如果线程的优先级相同,那么会随机选择一个
常用方法Api:
getName():返回线程的名字
setName(String name) 设置线程的名字
currentThread():获取线程对象
sleep(long millis):线程睡眠,参数是毫秒
getPriority():获取线程优先级
setPriority(int newPriority):设置线程优先级
setDaemon(boolean on):参数为true时,设置线程为守护线程
线程安全问题:
原因:线程属于进程,用一个应用程序进程是共享数据的,多个线程操作了共享数据就会产生线程安全问题
解决办法1:将操作共享数据的代码锁起来,同步代码块,只让一个线程执行
synchronize同步代码块: 修饰符 synchronized 返回值类型 方法名(方法参数) { 方法体;}
synchronize同步代码块就是加锁的操作,方法参数就是锁,锁对象要唯一才能保证线程安全,但是没有直接的表示,使用Lock直观表示加锁操作
解决办法2:使用Lock接口的实现类ReentrantLock创建锁对象直接加锁
加锁和解锁:lock(),unlock():{ lock.lock();方法体;lock.unlock();}
加锁和解锁伴随try…finally操作,unlock()放到finnaly中
死锁:
加了二个或多个锁产生的问题,两个或者多个线程互相持有对方所需要的资源,导致这些线程一直处于等待状态
加锁解决类多个线程操作共享数据的操作,但是当一个线程操作过后数据发生了变化,
此时另一个线程需要该数据但另一个线程被另一个锁锁住了无法产生数据就产生了死锁
线程池:
使用多个线程的时候,管理线程操作繁琐并且难免频繁创建和销毁线程,所以使用线程池管理线程
有四个组成部分:线程池管理器;工作线程;任务接口;任务队列
线程池管理器(ThreadPool):用于创建并管理线程池,包括 创建线程池,销毁线程池,添加新任务;
工作线程(PoolWorker):线程池中线程,在没有任务时处于等待状态,可以循环的执行任务;
任务接口(Task):每个任务必须实现的接口,以供工作线程调度任务的执行,它主要规定了任务的入口,任务执行完后的收尾工作,任务的执行状态等;
任务队列(taskQueue):用于存放没有处理的任务。提供一种缓冲机制。
线程、线程池、进程分为五个阶段:
创建:NEW;就绪:RUNABLE;阻塞:BLOCKED;运行:RUN;终止:TERMINATED;
线程池六种状态:
NEW:一个尚未启动的线程的状态。也称之为初始状态、开始状态。线程刚被创建,但是并未启动。还没调用start方法。
RUNNABLE:当我们调用线程对象的start方法,那么此时线程对象进入了RUNNABLE状态。那么此时才是真正的在JVM进程中创建了一个线程,线程一经启动并不是立即得到执行,线程的运行与否要听令与CPU的调度,那么我们把这个中间状态称之为可执行状态(RUNNABLE)也就是说它具备执行的资格,但是并没有真正的执行起来而是在等待CPU的度。
BLOCKED:当一个线程试图获取一个对象锁,而该对象锁被其他的线程持有,则该线程进入Blocked状态;当该线程持有锁时,该线程将变成Runnable状态。
WAITING:一个正在等待的线程的状态。也称之为等待状态。造成线程等待的原因有两种,分别是调用Object.wait()、join()方法。处于等待状态的线程,正在等待其他线程去执行一个特定的操作。
TIMED_WAITING:一个在限定时间内等待的线程的状态。也称之为限时等待状态。造成线程限时等待状态的原因有三种,分别是:Thread.sleep(long),Object.wait(long)、join(long)。
TERMINATED:一个完全运行完成的线程的状态。也称之为终止状态、结束状态
常用线程池:
newCachedThreadPool():默认线程池。使用的是SynchronousQueue 同步阻塞队列。
newSingleThreadExecutor:创建一个单线程的线程池。这个线程池只有一个线程在工作,也就是相当于单线程串行执行所有任务。如果这个唯一的线程因为异常结束,那么会有一个新的线程来替代它。此线程池保证所有任务的执行顺序按照任务的提交顺序执行。
newFixedThreadPool:创建固定大小的线程池。每次提交一个任务就创建一个线程,直到线程达到线程池的最大大小。线程池的大小一旦达到最大值就会保持不变,如果某个线程因为执行异常而结束,那么线程池会补充一个新线程。
newCachedThreadPool:创建一个可缓存的线程池。如果线程池的大小超过了处理任务所需要的线程,那么就会回收部分空闲(60秒不执行任务)的线程,当任务数增加时,此线程池又可以智能的添加新线程来处理任务。此线程池不会对线程池大小做限制,线程池大小完全依赖于操作系统(或者说JVM)能够创建的最大线程大小。
newScheduledThreadPool:创建一个大小无限的线程池。
线程池处理器:
创建线程池:new ThreadPoolExecutor(7个参数)→操作线程池:pool.submit(() -> { //操作代码});→关闭线程池:pool.shutdown()
submit()和execute()方法区别:接收的参数不一样,execute只能接收Runnable类型、submit可以接收Runnable和Callable两种类型;submit有返回值,而execute没有返回值;submit方便Exception处理
ThreadPoolExecutor对象有七个参数如下:
核心线程数:corePoolSize、最大线程数:maximumPoolSize 、空闲线程存活时间:keepAliveTime 、存活时间单位:TimeUnit unit 、阻塞队列:blockQueue、线程工厂:threadFactory、拒绝策略:handler
corePoolSize :线程池中核心线程数的最大值,不能小于0。
maximumPoolSize :线程池中能拥有最多线程数,不能小于等于0。最大线程数=核心线程数 + 临时线程数
keepAliveTime :表示空闲线程的存活时间,不能小于0。
TimeUnit unit :表示keepAliveTime的时间单位,毫秒,秒,分,时等。
blockQueue:用于缓存任务的阻塞队列
threadFactory :指定创建线程的工厂。(可以不指定)
handler :表示当 blockQuene已满,且池中的线程数达到 maximumPoolSize 时,线程池拒绝添加新任务时采取的策略。(可以不指定)
时间单位:TimeUnit.DAYS:天、TimeUnit.HOURS:小时、TimeUnit.MINUTES:分钟
、TimeUnit.SECONDS:秒、TimeUnit.MILLISECONDS:毫秒、
TimeUnit.MICROSECONDS:微秒、TimeUnit.NANOSECONDS:纳秒
阻塞队列有五种:
ArrayBlockingQueue 数组型阻塞队列:数组结构,初始化时传入大小,有界,FIFO,使用一个重入锁,默认使用非公平锁,入队和出队共用一个锁,互斥。
LinkedBlockingQueue 链表型阻塞队列:链表结构,默认初始化大小为Integer.MAX_VALUE,有界,FIFO,使用两个重入锁分别控制元素的入队和出队,用Condition进行线程间的唤醒和等待。
SynchronousQueue 同步阻塞队列:容量为0,有界,添加任务必须等待取出任务,这个队列相当于通道,不存储元素。
PriorityBlockingQueue 优先阻塞队列:无界,默认采用元素自然顺序升序排列。
DelayQueue 延时阻塞队列:无界,元素有过期时间,过期的元素才能被取出。
拒绝策略有四种:
AbortPolicy:丢弃任务并抛出RejectedExecutionException异常。默认策略。
DiscardPolicy:丢弃任务,但是不抛出异常。可能导致无法发现系统的异常状态。
DiscardOldestPolicy:丢弃队列最前面的任务,然后重新提交被拒绝的任务。
CallerRunsPolicy:由向线程池提交任务的线程来执行该任务。
阿里关于Exector的开发规范:
线程资源必须通过线程池提供,不允许在应用中自行显式创建线程。
说明:使用线程池的好处是减少在创建和销毁线程上所花的时间以及系统资源的开销,解决资源不足的问题。如果不使用线程池,有可能造成系统创建大量同类线程而导致消耗完内存或者“过度切换”的问题。
线程池不允许使用 Executors 去创建,而是通过 ThreadPoolExecutor 的方式,这样
的处理方式让写的同学更加明确线程池的运行规则,规避资源耗尽的风险。
说明:Executors 返回的线程池对象的弊端如下:
1)FixedThreadPool 和 SingleThreadPool:
允许的请求队列长度为 Integer.MAX_VALUE,可能会堆积大量的请求,从而导致 OOM。
2)CachedThreadPool 和 ScheduledThreadPool:
允许的创建线程数量为 Integer.MAX_VALUE,可能会创建大量的线程,从而导致 OOM。
ArrayBlockingQueue 数组型阻塞队列,和 LinkedBlockingQueue 链表型阻塞队列是无界队列
无界队列会让把线程池撑爆
撑爆的原因有两个
1、队列本身很大
2、无数的任务都可以提交到线程池内部来