目录
线程与进程
区别
协程
线程的工作原理 内存模型
线程安全:
多线程的优点:
同步 异步 阻塞 非阻塞
如何创建线程
启动线程时,Start与run的区别是什么
ThreadLocal的实现
Spring使用ThreadLocal解决线程安全问题:
java中的线程有几种状态? 新建状态 就绪状态 运行状态 阻塞状态 终止状态
看哪些线程处于阻塞状态 jstack ps -ef ps -aux
java中的线程池实现 4种 FixedThreadPool底层使用的是什么任务队列?
ThreadPoolExecutor参数含义: 7 个
线程池的状态 5 种
向线程池提交任务(2种)
execute()内部实现
线程池容量的动态调整
Java线程池使用有界队列实现时,饱和策略有哪些?
解决的问题
jvm有一个主存main memory,而每个线程有自己的(工作内存)working memory,一个线程对一个变量所有操作,都要在工作内存里建立一个副本,操作完之后再写入主存;
(1) 从主存复制变量到工作内存 (read and load)
(2) 执行代码,操作变量 (use and assign)
(3) 工作内存数据刷新到主存 (store and write)
1、提高应用程序响应:当一个操作耗时很长时,整个系统都会等待这个操作,使用多线程技术,将耗时长的操作置于一个新的线程,可以避免这种情况。
2、使多CPU系统有效,充分利用:操作系统会保证当线程数不大于CPU数目时,不同的线程运行于不同的CPU上。
3、改善程序结构:复杂的进程可以分为多个线程,成为几个独立的运行部分,程序利于理解和修改
进程调用,IO,方法:
同步:功能调用后,没有得到结果前,调用不返回
异步:通过状态、回调函数处理调用,无需立刻返回
线程:
阻塞:结果返回之前,当前线程会被挂起,让出cpu时间,仅在得到结果之后才继续执行
非阻塞:即使不能立刻得到结果,也不会挂起线程
多线程同步方式
Java使用Thread类代表线程,所有的线程对象都Thread类或其子类的实例
3种方式来创建线程:
1)继承Thread类创建线程
2)实现Runnable接口创建线程 适合多继承
3)使用Callable和Future创建线程
java5提供了Future接口来代表Callable接口里的call()方法的返回值,提供FutureTask实现类,实现类实现了Future接口,并实现了Runnable接口
既能当做一个Runnable直接被Thread执行,也能作为Future用来得到Callable的计算结果
1)start方法
start()用来启动一个线程,当调用start方法后,系统才会开启一个新的线程来执行用户定义的子任务,在这个过程中,会为相应的线程分配需要的资源。
2)run方法
run()方法是不需要用户来调用的,当start方法启动线程后,线程获得CPU执行时间,便进入run方法体去执行具体的任务。继承Thread类必须重写run方法,在run方法中定义具体要执行的任务。
应用:数据库连接的独立建立与关闭
每个线程中都有一个connect变量
同一线程贯通MVC三层,将一些非线程安全的变量以ThreadLocal存放,在同一次请求响应的调用线程中,所有关联的对象引用到的都是同一个变量。
线程从创建、运行到结束总是处于下面五个状态之一:新建状态、就绪状态、运行状态、阻塞状态及死亡状态。
所谓阻塞状态是:正在运行的线程没有运行结束,暂时让出CPU
1.新建状态(New):
当用new操作符创建一个线程时, 例如new Thread(r),线程还没有开始运行,此时线程处在新建状态
2.就绪状态(Runnable)
线程对象调用start()方法即启动了线程,start()方法创建线程运行的系统资源,并调度线程运行run()方法。当start()方法返回后,线程就处于就绪状态
于就绪状态的线程必须同其他线程竞争CPU时间,只有获得CPU时间才可以运行线程。多个处于就绪状态的线程由线程调度程序(thread scheduler)调度
3.运行状态(Running)
当线程获得CPU时间后,进入运行状态,真正开始执行run()方法.
4. 阻塞状态(Blocked) **********
所谓阻塞状态是:正在运行的线程被挂起,暂时让出CPU,其他处于就绪状态的线程就可以获得CPU时间,进入运行状态
线程运行过程中,可能由于各种原因进入阻塞状态:
1>sleep方法进入睡眠状态;
2>I/O上阻塞的操作,即该操作在输入输出操作完成之前不会返回
3>试图得到一个锁,而该锁正被其他线程持有;
4>等待触发条件
TIMED_WAITING,对应"阻塞"状态,代表此线程正处于有限期的主动等待中,要么有人唤醒它,要么等待够了一定时间之后,才会再次进入就绪状态
5. 终止状态(Dead)
有两个原因会导致线程死亡:
1) run方法正常退出而自然死亡,
2) 一个未捕获的异常终止了run方法,线程猝死。
为了确定线程在当前是否存活着(运行、阻塞),使用isAlive方法:如果是可运行或被阻塞,返回true; new状态或不可运行, 或死亡,返回false
jstack用于打印出给定的java进程ID或core file或远程调试服务的Java堆栈信息
什么是阻塞队列
阻塞添加
当阻塞队列元素已满时,队列会阻塞加入元素的线程,直队列元素不满时才重新唤醒线程执行加入操作。
阻塞删除
队列元素为空时,删除队列元素的线程将被阻塞,直到队列不为空再执行删除操作
线程池的接口:Executors接口;实现类ThreadPoolExecutor类
ThreadPoolExecutor通过构造方法的参数,构造不同配置的线程池,除了ScheduledThreadPool
1.FixedThreadPool( 3 ) 线程数
说明:指定线程数的线程池,使用LinkedBlockingQuene作为阻塞队列
特点:当线程池没有可执行任务时,也不会释放线程。
2.CachedThreadPool( ) 无参数
说明:可以缓存线程的线程池,默认缓存60s,线程池的线程数可达到Integer.MAX_VALUE,SynchronousQueue作为阻塞队列(不存储元素);
特点:在没有任务执行时,线程的空闲时间超过keepAliveTime 60s,会自动释放线程资源;
当提交新任务时,如果没有空闲线程,则创建新线程执行任务;否则复用未超过60s的空闲线程
使用时注意控制并发的任务数,防止因创建大量的线程导致而降低性能
3.SingleThreadExecutor( )
说明:初始化只有一个线程的线程池,内部使用LinkedBlockingQueue作为阻塞队列。
特点:保证所提交任务的顺序执行,如果该线程异常结束,会重新创建一个新的线程继续执行任务,
4.ScheduledThreadPool() }, 1, 3, TimeUnit.SECONDS); 定义在ExecutorService接口中
特定:延迟(定时)执行、周期执行;延迟1秒后,每3秒执行一次
<1>corePoolSize:线程池中核心线程的数量,预创建线程; 作用:频繁创建线程和销毁线程需要时间,
<2>maximumPoolSize:线程池允许创建的最大线程数;
<3>keepAliveTime:工作线程空闲之后继续存活的时间,默认一直存活
<4>unit:参数keepAliveTime的时间单位;如:TimeUnit.SECONDS
<5>workQueue:阻塞队列;存储等待执行的任务,有四种阻塞队列类型,
<6>threadFactory:用于创建线程的线程工厂; 作用:统一在创建线程时的参数,如是否守护线程。
<7>handler:饱和策略;阻塞队列满,没有空闲线程,线程数目已达到最大数量,队列处于饱和状态,
1.Abort策略:默认策略,新任务提交时直接抛出未检查的异常RejectedExecutionException,该异常可由调用者捕获。
2.CallerRuns策略:在调用者的线程中运行新的任务,既不抛弃任务也不抛出异常。
3.Discard策略: 新任务被抛弃。
4.DiscardOldest策略:旧任务被抛弃。(不适合工作队列为优先队列场景,否则优先级高的会被抛弃)
1、RUNNING:会接收新任务、处理阻塞队列中的任务;
2、SHUTDOWN: 不会接收新任务,会处理阻塞队列中的任务;
3、STOP :不接收,不处理任务,会中断正在运行的任务;
4、TIDYING :对线程进行整理优化;
5、TERMINATED: 停止工作;
有两种方式:
1.executor.execute (new Runnable()
重复以下代码添加任务到线程池中
ExecutorService executor=Executors.newFixedThreadPool(2);
executor.execute(new Runnable() { @Override public void run() {具体任务 }
});
2.executor.submit(new Runnable()
Future对象来判断当前的线程是否执行完毕,new Runnable() 无法返回数据信息
Future future = executor.submit(new Runnable() { public void run() { System.out.println("Asynchronous task"); } }); //如果任务结束执行则返回 null System.out.println("future.get()=" + future.get());
3.executor.submit(new Callable()
Future对象,传递的参数为Callable对象,new Callable()可以返回数据信息
Future future = executor.submit(new Callable(){ public Object call() throws Exception { return "Callable Result"; } }); System.out.println("future.get() = " + future.get()); //上述样例代码会输出如下结果: //future.get() = Callable Result
1.首次通过workCountof()获知当前线程池中的线程数,如果小于corePoolSize, 就通过addWorker()创建线程并执行该任务;否则,将该任务放入阻塞队列;
2. 如果能成功将任务放入阻塞队列中, 如果当前线程池是非RUNNING状态,则将该任务从阻塞队列中移除,然后执行reject()处理该任务;
如果当前线程池处于RUNNING状态,则需要再次检查线程池(因为可能在上次检查后,有线程资源被释放),是否有空闲的线程;如果有则执行该任务;
3、如果不能将任务放入阻塞队列中,说明阻塞队列已满;那么将通过addWoker()尝试创建一个新的线程去执行这个任务;
如果addWoker()执行失败,说明线程池中线程数达到maxPoolSize,则执行reject()处理任务;
sumbit()内部实现
1.将提交的Callable任务会被封装成了FutureTask对象
2.FutureTask类实现了Runnable接口,通过Executor.execute()提交FutureTask到线程池,最终执行FutureTask的run方法
比较:两个方法都可以向线程池提交任务
其它线程池类像ThreadPoolExecutor和ScheduledThreadPoolExecutor都有这些方法。
线程池的关闭(2种) shutdown()和shutdownNow()
shutdown():不会立即终止线程池,要等所有任务缓存队列中的任务都执行完后才终止,不会接受新的任务
shutdownNow():立即终止线程池,并尝试打断正在执行的任务,并清空任务缓存队列,返回尚未执行的任务
ThreadPoolExecutor提供了动态调整线程池容量大小:setCorePoolSize() 和 setMaximumPoolSize()
线程池会将提交的任务先置于工作队列
无界队列不存在饱和的问题,但是其问题是当请求持续高负载的话,任务会无脑的加入工作队列,那么很可能导致内存等资源溢出或者耗尽
有界队列不会带来高负载导致的内存耗尽的问题,但是有引发工作队列已满情况下,新提交的任务如何管理的难题,这就是线程池工作队列饱和策略要
当工作队列满了,不同策略的处理方式为:
1.Abort策略:默认策略,新任务提交时直接抛出未检查的异常RejectedExecutionException,该异常可由调用者捕获。
2.CallerRuns策略:在调用者的线程中运行新的任务,既不抛弃任务也不抛出异常。
3.Discard策略: 新任务被抛弃。
4.DiscardOldest策略:旧任务被抛弃。(不适合工作队列为优先队列场景,否则优先级高的会被抛弃)