本篇文章主要介绍java中线程和线程池的使用
你想拥有我,必须先了解我
一、线程
1. 线程分类
继承 java.lang.Thread
public class MyThread extends Thread{
@Override
public void run() {
System.out.println("任务执行");
}
}
实现 java.lang.Runnable
public class MyRunnable implements Runnable {
@Override
public void run() {
System.out.println("方法执行");
}
}
Thread与Runnable区别
- Runnable是一个接口,有且仅有一个run方法。而Thread是一个类,他不仅实现了Runnable而且还有自己的方法
- 实现Runnable的类无法直接开启新的线程运行,必须要利用new Thread(runnable).start()开启新的线程
守护线程与非守护线程
java线程中分为守护线程
和用户线程(非守护线程)
。当程序中所有的用户线程都结束了,那么程序也就退出了,换言之只要有一个用户线程还活着,程序也就不会退出。
我们一般默认创建的线程就是用户线程,如果想要改变成守护线程可以调用threadA.setDaemon(true)
,注意此方法必须在start()
方法之前调用,还有就是守护线程中创建的线程也是守护线程。
下面 举个栗子
Thread t1 = new Thread(()->{
try{
Thread.sleep(60000);
}catch (Exception ex){
ex.printStackTrace();
}
});
System.out.println("thread 1 start执行前结果---->"+t1.getState());
//此方法如果放开程序会很快结束,如果注释,程序会大概等待60秒才结束
//t1.setDaemon(true);
t1.start();
Thread.sleep(200);
System.out.println("thread 1 start执行后结果---->"+t1.getState());
上面的栗子会大概等待运行60秒结束,如果放开t1.setDaemon(true)
注释,程序会很快结束。
注意上面的栗子如果用Junit
跑也会很快退出,因为Junit Runner执行主线程完成后,会主动退出程序
2. 线程的生命周期
java.lang.Thread.State中定义了6种线程状态,分别为NEW
,RUNNABLE
,BLOCKED
,WAITING
,TIMED_WAITING
,TERMINATED
。
下面这张图对jvm线程状态和生命周期做了很好的展示。
接下来我们分别分析一下这些线程状态
-
NEW
当程序使用关键字的new出来的时候,这个线程就处于NEW状态,此时他与普通的java对象没有区别
Thread thread = new Thread(()->{while (true){}});
System.out.println(thread.getState());
----> 运行结果: NEW
-
Runnable
其实我们都知道,在实际线程生命周期中还存在就绪
和运行
的状态,但是jvm 并没有定义这两个状态,而是将这两个状态都认为是Rnunable状态,因为运行
状态实在太短暂了,即时我们当下用api拿到了线程状态为运行
那也并不代表此时就是运行
状态。
Thread thread = new Thread(()->{while (true){}});
thread.start();
System.out.println(thread.getState());
----> 运行结果: RUNNABLE
-
BLOCKED
当线程正在等待获取监视锁时,会进行阻塞
状态
Object lock = new Object();
Runnable run = ()->{
synchronized (lock){
while (true){}
}
};
Thread thread1 = new Thread(run);
Thread thread2 = new Thread(run);
thread1.start();
thread2.start();
System.out.println("运行结果--->thread1:"+thread1.getState());
System.out.println("运行结果--->thread2:"+thread2.getState());
运行结果--->thread1:RUNNABLE
运行结果--->thread2:BLOCKED
相关线程栈信息
-
WAITING
等待线程的线程状态,在调用以下方法时会处于此状态- Object.wait with no timeout
- Thread.join with no timeout
- LockSupport.park
Object.wait with no timeout
执行ObjectA.wait
方法时必须获取到monitor锁,因此wait方法需要放到synchronized
同步方法块中,当然调用notify()
or notifyAll
也必须要在同步方法块中。
注意
如果ObjectA是线程对象时,那么只要ObjectA线程执行完成后,会自动
调用notifyAll方法
Object obj = new Object();
Thread thread = new Thread(()->{
try {
synchronized (obj) {
obj.wait();
}
}catch (InterruptedException ex){}
});
thread.start();
while (true){
Thread.sleep(1000);
System.out.println(thread.getState());
}
执行结果---->WAITING
执行结果---->WAITING
执行结果---->WAITING
调用Object.wait()所处线程状态
Thread.join with no timeout
thread.join把指定的线程加入到当前线程,可以将两个交替执行的线程合并为顺序执行的线程。join内部原理是调用了Object.wait方法,使得两个线程间有了通讯方式,从而达到顺序执行的目的。
Thread t1 = new Thread(()->{
try {
Thread.sleep(5000);
System.out.println("this is thread 1");
}catch (InterruptedException ex){}
});
Thread t2 = new Thread(()->{
try {
t1.join();
System.out.println("this is thread 2");
}catch (InterruptedException ex){}
});
////t1,t2执行顺序可以任意
t1.start();
t2.start();
while (true){
Thread.sleep(1000);
System.out.println("thread 1 执行结果---->"+t1.getState());
System.out.println("thread 2 执行结果---->"+t2.getState());
}
thread 1 执行结果---->TIMED_WAITING
thread 2 执行结果---->WAITING
this is thread 1
this is thread 2
thread 1 执行结果---->TERMINATED
thread 2 执行结果---->TERMINATED
thread.join()的线程状态
LockSupport.park
LockSupport.park()
休眠当前线程进入阻塞状态,直到得到许可证(也就是调用LockSupport.unpark(thread1))后继续执行。注意
unpark
可以优先于park
执行,但是许可证不会进行累加,也就是说在执行park前无论执行多少次unpark,获得的park许可证只有一次。在线程启动之前调用 park/unpark方法没有任何效果
Thread t1 = new Thread(()->{
LockSupport.park();
});
t1.start();
while (true){
Thread.sleep(1000);
System.out.println("thread 1 执行结果---->"+t1.getState());
}
thread 1 执行结果---->WAITING
执行LockSupport.park()的线程状态
-
TIMED_WAITING
指定等待时间
的等待线程状态,执行以下方法会进入定时等待状态- Thread.sleep
- Object.wait with timeout
- Thread.join with timeout
- LockSupport.parkNanos
- LockSupport.parkUntil
由于在WAITING
中已经举过类似例子,他们用法都是一样的,只是多了一个 超时时间
。在这里就不一 一举例了,只演示Thread.sleep
Thread t1 = new Thread(()->{
try{
Thread.sleep(1000000);
}catch (Exception ex){}
});
t1.start();
while (true){
Thread.sleep(1000);
System.out.println("thread 1 执行结果---->"+t1.getState());
}
thread 1 执行结果---->TIMED_WAITING
sleep 指定时间内的线程状态
-
TERMINATED
线程终止状态。线程执行完成,或者异常退出。
举个栗子:
Thread t1 = new Thread(()->{
try{
int i = 1 / 0;
}catch (Exception ex){
ex.printStackTrace();
}
});
System.out.println("thread 1 start执行前结果---->"+t1.getState());
t1.start();
while (true){
System.out.println("thread 1 start执行后结果---->"+t1.getState());
Thread.sleep(1000);
}
执行结果
thread 1 start执行前结果---->NEW
thread 1 start执行后结果---->RUNNABLE
java.lang.ArithmeticException: / by zero
at lykos.demo.ThreadDemo.lambda$main$0(ThreadDemo.java:124)
at java.lang.Thread.run(Thread.java:748)
thread 1 start执行后结果---->TERMINATED
从结果可以看出线程异常退出后,会进入TERMINATED
状态
3. 线程间通讯
线程间的通讯可以用以下几种方式,由于篇幅问题就不在这里做具体的例子,和对比。
- 共享变量
- 共享锁与独享锁(排它锁)
- Object对象的方法:
wait
、notify
、join
、park
、unpark
二、线程池
作用
- 减少创建线程和切换线程所带来的系统开销
- 控制线程的数量,防止创建过多线程,给系统带来不必要的压力。
要点
- 如何创建线程池
- 如何执行任务
- 如何保证核心的线程不会退出
接下来我们一 一为上面的疑问作出解释
创建线程池
我们来看官网对ThreadPoolExecutor
构造函数的定义
public ThreadPoolExecutor(int corePoolSize, //核心线程数
int maximumPoolSize, //最大线程数
long keepAliveTime, //线程存活时间
TimeUnit unit, //线程存活时间单位
BlockingQueue<Runnable> workQueue, // 任务队列
ThreadFactory threadFactory, // 创建线程的工场类
RejectedExecutionHandler handler)// 任务拒绝策略
理解了上面的解释,接下来我们做一个例子
ThreadPoolExecutor threadPoolExecutor = new ThreadPoolExecutor(
2,//核心线程数为2
4,//最大线程数为4
60,//空闲线程存活时间
TimeUnit.SECONDS,//空闲线程存活时间单位
new LinkedBlockingQueue<>(10),//存放任务队列
Executors.defaultThreadFactory(),//创建线程工场
new ThreadPoolExecutor.AbortPolicy());//当任务队列饱和,也达到最大线程数后拒绝任务策略
//如果设置为true 允许核心线程也可以回收,回收时间就是设置的 空闲线程存活时间 60
//threadPoolExecutor.allowCoreThreadTimeOut(true);
threadPoolExecutor.execute(()->{
System.out.println("this execute task");
});
Future future = threadPoolExecutor.submit(()->{
System.out.println("this submit method task");
return 1;
});
如何执行任务
ThreadPoolExecutor有个核心内部类Worker
真正执行线程,创建线程定义的核心线程数,最大线程数其实就是这个Worker类的数量。
上面的例子值得注意的是当我们创建好一个线程池后,有两种方法提交任务execute
和submit
他们内部本身没有太大区别,都是通过Worker对象
来执行任务,唯一的区别就是submit
支持返回值,且返回值是Future
对象。下面介绍一下提交任务内部的大致逻辑。
1.判断工作线程数是否达到核心线程数,如果没有则创建新的工作线程
2. 判断是否队列中任务满
2.1 未满
判断是否工作线程数达到最大线程数
2.1.1 达到
不做处理
2.1.2 未达到
创建并开启新的工作线程
2.2 已满
判断是否工作线程数达到最大线程数
2.2.1 未达到
创建并开启新的工作线程
2.2.2 达到
拒绝任务
如何保证核心的线程不会退出
先看一段源码,出自ThreadPoolExecutor.getTask()
方法
boolean timed = allowCoreThreadTimeOut || wc > corePoolSize;
if ((wc > maximumPoolSize || (timed && timedOut))
&& (wc > 1 || workQueue.isEmpty())) {
if (compareAndDecrementWorkerCount(c))
return null;
continue;
}
try {
Runnable r = timed ?
workQueue.poll(keepAliveTime, TimeUnit.NANOSECONDS) :
workQueue.take();
if (r != null)
return r;
timedOut = true;
} catch (InterruptedException retry) {
timedOut = false;
}
上面的源码大概意思就是如果timed
为true即设置了allowCoreThreadTimeOut为true 或者 当前线程数大于核心线程数,那么执行队列的poll
方法并设置了超时时间,也就是我们常说的空闲线程在多久后会自动释放的原因,那如果不是上面两种情况就会调用队列的take
方法一直阻塞等待获取任务,这也就解释了如何保持核心线程数不被回收的原因。
网友评论