JAVA并发系列文章(一)
`
前言
新手笔记,写来防止我的偷懒(内容如有出错,还望各位大佬多多指出)
`
提示:以下是本篇文章正文内容,下面案例可供参考
一、JAVA线程如何实现/创建?
1.线程是什么?(资源调度最小单位)
对计算机来说,一个任务就是一个进程,进程是资源分配的最小单位。但是进程是由一个或者多个线程(线程也叫轻量级进程)组成的,而线程是进程中的实际运行单位,是资源调度的最小单位。是独立于进程之中的子任务。Java中线程(Thread)以对象形式存在
举例子:
1.打游戏时,打开客户端的时候会启动很多线程,比如好友聊天线程,队列进程等。
2.main主函数相当于一个主线程,多线程就相当于在main函数执行时,同时运行其他的进程,这种情况下有可能造成线程之间的资源竞争等
2.线程实现/创建方式
1.继承Thread类(是Runable接口的一个实例)
Public 一个继承Thread的类(内部写上运行方法)+new一个新对象+新对象.start()
start():是一个native方法,会启动一个新线程,并执行run()方法
ps:
native方法是一个java调用非java代码的接口,这个方法是由非java语言实现的,比如c/c++(既然不是java写的,我们就不用去看源码了,知道已经是已经实现了的就可以了,是操作系统实现的,java只能调用)
——
这就是为什么java是跨平台的语言,既然跨平台了,就要牺牲一些对底层的控制,所以就要其他语言的帮助,native就是这个作用
——
run()方法是Runnable接口中定义的,而start()是Thread类定义的,所有实现Runable的接口的类都要重写run方法,run方法是默认绑定操作系统的,是线程执行的入口
——
run()和start()的区别可以用一句话概括:单独调用run()方法,是同步执行;通过start()调用run(),是异步执行。
2.实现Runnable接口
java不可以继承多个类,是单继承的,但是通过接口可以实现多继承(一个java类智能继承一个父类,但可以有多个接口)
步骤:
定义类(是否有继承)implements Runnable
重写Runable接口中的run方法
new一个我们刚刚定义的类
实例化一个Thread,并传入自己的实例,并用start()启动我们刚刚定义的类
3.ExecutorService+Callable+Future有返回值线程
有返回值的任务必须实现Callable接口,无返回值的任务必须Runnable接口。
执行了Callable任务后,可以获取一个Future的对象,在该对象上调用get就可以获取Callable任务返回的Object,再加上ExecutorService(线程池接口)就可以实现有返回结果的多线程
4.基于线程池的方式
利用缓存的策略,不需要我们再去创建和销毁,这样可以节省资源
二、有哪几种线程池(可以手动/自动创建)?(4种)
线程池是什么?
java中经常需要用多线程来处理一些业务,不建议单纯使用继承Thread或者实现Runnable接口来创建线程,因为那样创建会引发创建/销毁线程耗费资源,线程上下文切换等问题,同时创建过多线程也可能导致资源耗尽。
因此引入了线程池,方便线程任务的管理。JDK1.5开始的java.util.concurrent包中,设计的几个核心类以及接口包括:Executor+Executors+ExecutorService+ThreadPoolExecutor+Callable+Runnable等
线程池作用
1.加快响应请求(时间优先)(为了缩短接口响应时间,往往不设置队列去缓存并发的请求,而是尽可能创造线程来执行任务,利用corePoolSize和maxPoolSize)
2.处理大任务速度快(吞吐量优先)(往往设置队列来缓存并发任务,避免corePoolSize和maxPoolSize设置参数太大,造成线程上下文频繁切换,降低了任务处理速度)
ThreadPoolExecutor(手动创建)
可以灵活设置线程池的各个参数,体现在ThreadPoolExecutor类构造器上各个实参的不同
public ThreadPoolExecutor(int corePoolSize,
int maximumPoolSize,
long keepAliveTime,
TimeUnit unit,
BlockingQueue<Runnable> workQueue,
ThreadFactory threadFactory,
RejectedExecutionHandler handler)
corePoolSize
核心线程数,线程池初始化时默认没有线程的,有任务了才开始创建线程去执行任务
maximumPoolSize
最大线程数,在核心线程数上可能会增加一些非核心线程,只有当workQueue队列满了才会创建多余corePoolSize线程(线程总数≤maxPoolSize)
keppAliveTime
非核心线程空闲时间超过keepAliveTime就会被自动终止回收
unit
keepAliveTime的时间单位
workQueue
用来保存任务的队列(池子里的任务数大于corePoolSize时,就会把新来的任务放进这里)
threadFactory
线程工厂,主要用来创建线程,默认用Executors.defaultThreadFactory(),也可以用guava库的ThreadFactoryBuilder来创建
handler(最好自己定义拒绝策略的方案,这4个方案是参考)
线程池拒绝处理任务时的策略(AbortPolicy,DiscardPolicy,DiscardOldestPolicy,CallerRunsPolicy)
AbortPolicy:丢弃任务并抛出异常
DiscardPolicy:丢弃任务但不抛出异常
DiscardOldPoilcy:丢弃队列最前边的任务,重新尝试执行任务(不断重复)
CallRunsPolicy:只要线程池不被关闭,就会在调用线程中,运行被丢弃的任务
Executors工具类中的四种线程池(自动创建)
1.newCachedThreadPool
只会重用空闲并且可用的线程,对于执行很多短期异步任务程序而言,这些线程池通常可提高程序性能。
线程可用(调用execute重用以前的线程)
线程不可用(创建一个新线程)
还会终止并从缓存里移除超过60秒没被使用的线程
2.newFixedThreadPool
采用队列(队列大小未被设置)缓存线程的模式,重点在可以按照自己需求定义核心线程池数量和最大线程数
3.newScheduledThreadPool
创建一个线程池,可以安排在给定延迟后运行命令或者定期执行
4.newSingleThreadExecutor
是一个特殊的newFixedThreadPool,只是固定了某些参数值的ThreadPoolExecutor,这个线程池可以在线程死后(或者发生异常)重新启动一个线程来替代原来的线程继续执行下去。
三、线程生命周期
什么是线程生命周期?
指的是线程进入运行状态后要经过的5个状态。
一般的操作系统是采用抢占式方式来让线程获得CPU资源,然后线程状态在这几个状态下不断切换
经过的5个状态
1.新建状态(NEW)
使用new关键字创建一个线程后,就要由JVM来分配内存,并初始化其成员变量
2.就绪状态(RUNNABLE)
线程调用了strat()方法后,JVM会为其创建方法调用栈和程序计数器,等待调度运行
3.运行状态(RUNNING)
就绪的线程获得了CPU,开始执行run()方法
4.阻塞状态(BLOCKED)
线程因为某种原因放弃了CPU使用权,暂时停止运行,直到进入可运行状态。有三种阻塞情况
5.死亡(DEAD)
1.正常结束:线程正常结束
2.异常结束:线程抛出一个Excetpion或者Error
3.调用stop:直接调用stop()方法来结束线程(stop方法会强制停止一个正在运行的线程,无论此时线程是何种状态,在停止线程时需要自行指定线程退出逻辑,否则线程立即退出,不做任何清理操作,因此会造成数据不一致问题)(建议换成interrupt方法)
四、如何终止线程?
1.正常运行结束
2.使用退出标志退出线程
即在线程内部定义一个bool变量来判断是否结束当前的线程
3.Interrupt来结束线程
interrupt就是用来中断线程的。
interrupt可以用来设置线程打断标志,但是不会修改线程的运行状态。
public void interrupt()
isInterrupted()(会返回布尔值)来判断线程是否已经中断。
public boolean isInterrupted()
interrupted()是用来获取进程打断标志,同时会清除打断标志。
public static boolean interrupted()
4.stop方法来终止线程(线程不安全)
总结
`
以上就是Java多线程并发的线程池一部分内容,后续笔记会继续更新