本文主要介绍Java线程的三种创建方式和线程池的四种实现方式。
1.进程和线程
-
A:进程概念
进程:进程指正在运行的程序。确切的来说,当一个程序进入内存运行,即变成一个进程,进程是处于运行过程中的程序,并且具有一定独立功能。
-
B:线程的概念
线程:线程是进程中的一个执行单元(执行路径),负责当前进程中程序的执行,
一个进程中至少有一个线程。一个进程中是可以有多个线程的,这个应用程序也可以称之为多线程程序。
简而言之:一个程序运行后至少有一个进程,一个进程中可以包含多个线程
多线程:即就是一个程序中有多个线程在同时执行。
一个核心的CPU在多个线程之间进行着随即切换动作,由于切换时间很短(毫秒甚至是纳秒级别),所以感觉不到
单线程/多线程程序
单线程程序:即若有多个任务只能依次执行。当上一个任务执行结束后,下一个任务开始执行。如去 网吧上网,网吧只能让一个人上网,当这个人下机后,下一个人才能上网。
多线程程序:即若有多个任务可以同时执行。如,去网吧上网,网吧能够让多个人同时上网。
-
C:线程的运行模式
分时调度
所有线程轮流使用 CPU 的使用权,平均分配每个线程占用 CPU 的时间。
抢占式调度
优先让优先级高的线程使用 CPU,如果线程的优先级相同,那么会随机选择一个(线程随机性),Java使用的为抢占式调度。
大部分操作系统都支持多进程并发运行,现在的操作系统几乎都支持同时运行多个程序。
实际上,CPU(中央处理器)使用抢占式调度模式在多个线程间进行着高速的切换。对于CPU的一个核而言,某个时刻,只能执行一个线程,而 CPU的在多个线程间切换速度相对我们的感觉要快,看上去就是在同一时刻运行。
其实,多线程程序并不能提高程序的运行速度,但能够提高程序运行效率,让CPU的使用率更高。
2.线程的状态
3.创建线程的三种方式
继承Thread类创建线程类
Thread的实现步骤:
1). 定义Thread的子类,重写run()方法,run()方法代表了线程要完成的任务,run()方法称为线程执行体。
2). 创建Thread子类的实例,子类对象就是线程。
3). 调用线程对象的start()方法来启动线程。
/*
* 创建和启动一个线程
* 创建Thread子类对象
* 子类对象调用方法start()
* 让线程程序执行,JVM调用线程中的run
*/
public class ThreadDemo {
public static void main(String[] args) {
SubThread st = new SubThread();
SubThread st1 = new SubThread();
st.start();
st1.start();
for(int i = 0; i < 50;i++){
System.out.println(Thread.currentThread().getName()+"..."+i);
}
}
}
/*
* 定义子类,继承Thread
* 重写方法run
*/
public class SubThread extends Thread{
public void run(){
for(int i = 0; i < 50;i++){
System.out.println("run..."+i);
}
}
}
输出结果:
main...0
main...1
main...2
main...3
main...4
main...5
main...6
main...7
main...8
main...9
main...10
main...11
main...12
main...13
main...14
main...15
main...16
main...17
main...18
main...19
main...20
main...21
main...22
main...23
main...24
main...25
main...26
main...27
main...28
main...29
main...30
main...31
main...32
main...33
main...34
main...35
main...36
main...37
main...38
main...39
main...40
main...41
main...42
main...43
main...44
main...45
main...46
main...47
main...48
main...49
run...0
run...0
run...1
run...2
run...3
run...4
run...5
run...6
run...7
run...8
run...9
run...10
run...11
run...12
run...13
run...14
run...15
run...16
run...17
run...18
run...19
run...20
run...21
run...22
run...23
run...24
run...25
run...26
run...27
run...28
run...29
run...30
run...31
run...32
run...33
run...34
run...35
run...36
run...37
run...38
run...1
run...2
run...3
run...4
run...5
run...6
run...7
run...8
run...9
run...10
run...11
run...12
run...13
run...14
run...15
run...16
run...17
run...18
run...19
run...20
run...21
run...22
run...23
run...24
run...25
run...26
run...27
run...28
run...29
run...30
run...31
run...32
run...33
run...34
run...35
run...36
run...37
run...38
run...39
run...40
run...41
run...42
run...43
run...44
run...45
run...46
run...47
run...48
run...49
run...39
run...40
run...41
run...42
run...43
run...44
run...45
run...46
run...47
run...48
run...49
Process finished with exit code 0
实现Runnable接口
1).定义runnable接口的实现类,并重写该接口的run()方法,该run()方法的方法体同样是该线程的线程执行体。
2).创建 Runnable实现类的实例,并以此实例作为Thread的target来创建Thread对象,该Thread对象才是真正的线程对象。
3).调用线程对象的start()方法来启动该线程。
/*
* 使用匿名内部类,实现多线程程序
* 前提: 继承或者接口实现
* new 父类或者接口(){
* 重写抽象方法
* }
*/
public class ThreadDemo {
public static void main(String[] args) {
//继承方式 XXX extends Thread{ public void run(){}}
new Thread(){
public void run(){
System.out.println("!!!");
}
}.start();
//实现接口方式 XXX implements Runnable{ public void run(){}}
Runnable r = new Runnable(){
public void run(){
System.out.println("###");
}
};
new Thread(r).start();
new Thread(new Runnable(){
public void run(){
System.out.println("@@@");
}
}).start();
}
}
输出结果:
!!!
###
@@@
通过Callable和Future创建线程
Callable的实现步骤:
(1)创建Callable接口的实现类,并实现call()方法,该call()方法将作为线程执行体,并且有返回值。
(2)创建Callable实现类的实例,使用FutureTask类来包装Callable对象,该FutureTask对象封装了该Callable对象的call()方
法的返回值。(FutureTask是一个包装器,它通过接受Callable来创建,它同时实现了Future和Runnable接口。)
(3)使用FutureTask对象作为Thread对象的target创建并启动新线程。
(4)调用FutureTask对象的get()方法来获得子线程执行结束后的返回值
public class CallableDemo implements Callable<Integer> {
public static void main(String args[]) {
FutureTask<Integer> futureTask = new FutureTask<Integer>(new CallableDemo());
new Thread(futureTask).start();
try {
System.out.println("子线程返回值:" + futureTask.get());
} catch (InterruptedException e) {
e.printStackTrace();
} catch (ExecutionException e) {
e.printStackTrace();
}
if (futureTask.isDone()) {
System.out.println("线程结束");
}
}
@Override
public Integer call() throws Exception {
System.out.println("线程开始");
int ss = 0;
for (int i = 0; i < 20; i++) {
ss += i;
}
return ss;
}
}
输出结果:
线程开始
子线程返回值:190
线程结束
4.线程相关问题总结
A、采用实现Runnable、Callable接口的方式创建多线程的优缺点
优势:
线程类只是实现了Runnable接口或Callable接口,还可以继承其他类。
在这种方式下,多个线程可以共享同一个target对象,所以非常适合多个相同线程来处理同一份资源的情况,从而可以将CPU、代码和数据分开,形成清晰的模型,较好地体现了面向对象的思想。
劣势:
编程稍微复杂,如果要访问当前线程,则必须使用Thread.currentThread()方法。
B、使用继承Thread类的方式创建多线程的优缺点
优势:
编写简单,如果需要访问当前线程,则无需使用Thread.currentThread()方法,直接使用this即可获得当前线程。
劣势:
线程类已经继承了Thread类,所以不能再继承其他父类。
C、Runnable和Callable的区别
(1) Callable规定(重写)的方法是call(),Runnable规定(重写)的方法是run()。
(2) Callable的任务执行后可返回值,而Runnable的任务是不能返回值的。
(3) Callable的call方法可以抛出异常,Runnable的run方法不可以。
(4) 运行Callable任务可以拿到一个Future对象,表示异步计算的结果。它提供了检查计算是否完成的方法,以等待计算的
完成,并检索计算的结果。通过Future对象可以了解任务执行情况,可取消任务的执行,还可获取执行结果future.get()。
5.线程池的使用
使用线程池必要性
1. 重用存在的线程,减少对象创建、消亡的开销,性能佳
记创建线程消耗时间 T1,执行任务消耗时间 T2,销毁线程消耗时间 T3
如果 T1+T3>T2,那么是不是说开启一个线程来执行这个任务太不划算了!
正好,线程池缓存线程,可用已有的闲置线程来执行新任务,避免了 T1+T3 带来的系统开销
2. 可有效控制最大并发线程数,提高系统资源的使用率,同时避免过多资源竞争,避免堵塞
线程可以共享系统资源,如果同时执行的线程过多,就有可能导致系统资源不足而产生阻塞的情况
运用线程池能有效的控制线程最大并发数,避免以上的问题
3.提供定时执行、定期执行、单线程、并发数控制等功能
线程池的原理
1).在java中,如果每个请求到达就创建一个新线程,开销是相当大的。
2).在实际使用中,创建和销毁线程花费的时间和消耗的系统资源都相当大,甚至可能要比在处理实际的用户请求的时间和资源要多的多。
3).除了创建和销毁线程的开销之外,活动的线程也需要消耗系统资源。
如果在一个jvm里创建太多的线程,可能会使系统由于过度消耗内存或“切换过度”而导致系统资源不足。
为了防止资源不足,需要采取一些办法来限制任何给定时刻处理的请求数目,尽可能减少创建和销毁线程的次数,特别是一些资源耗费比较大的线程的创建和销毁,尽量利用已有对象来进行服务。
线程池主要用来解决线程生命周期开销问题和资源不足问题。通过对多个任务重复使用线程,线程创建的开销就被分摊到了多个任务上了,而且由于在请求到达时线程已经存在,所以消除了线程创建所带来的延迟。这样,就可以立即为请求服务,使用应用程序响应更快。另外,通过适当的调整线程中的线程数目可以防止出现资源不足的情况。
6.常见的几种线程池实现
Java里面线程池的顶级接口是Executor,但是严格意义上讲Executor并不是一个线程池,而只是一个执行线程的工具。真正的
线程池接口是ExecutorService。
ThreadPoolExecutor是线程池中最核心的一个类,后面提到的四种线程池都是基于ThreadPoolExecutor实现的。
ThreadPoolExecutor提供了四个构造方法,其中最重要的一个构造函数
public class ThreadPoolExecutor extends AbstractExecutorService {
public ThreadPoolExecutor(int corePoolSize,
int maximumPoolSize,
long keepAliveTime,TimeUnit unit,
BlockingQueue<Runnable> workQueue,
ThreadFactory threadFactory,
RejectedExecutionHandler handler);
}
函数的参数含义如下:
corePoolSize: 线程池维护线程的最少数量
maximumPoolSize:线程池维护线程的最大数量
keepAliveTime: 线程池维护线程所允许的空闲时间
unit: 线程池维护线程所允许的空闲时间的单位
workQueue: 线程池所使用的缓冲队列
handler: 线程池对拒绝任务的处理策略
threadFactory - 执行程序创建新线程时使用的工厂
核心线程:
线程池新建线程的时候,如果当前线程总数小于 corePoolSize,则新建的是核心线程,如果超过 corePoolSize,则新建的是非核心线程。
核心线程默认情况下会一直存活在线程池中,即使这个核心线程啥也不干(闲置状态)。
如果指定 ThreadPoolExecutor 的 allowCoreThreadTimeOut 这个属性为 true,那么核心线程如果不干活(闲置状态)的话,超过一定时间(时长下面参数决定),就会被销毁掉。
线程池执行的过程:
线程池刚创建时,里面没有一个线程。任务队列是作为参数传进来的。不过,就算队列里面有任务,线程池也不会马上执行它们。
当调用 execute() 方法添加一个任务时,线程池会做如下判断:
a. 如果正在运行的线程数量小于 corePoolSize,那么马上创建线程运行这个任务;
b. 如果正在运行的线程数量大于或等于 corePoolSize,那么将这个任务放入队列。
c. 如果这时候队列满了,而且正在运行的线程数量小于 maximumPoolSize,那么还是要创建线程运行这个任务;
d. 如果队列满了,而且正在运行的线程数量大于或等于 maximumPoolSize,那么线程池会抛出异常,告诉调用者“我不能再接受任务了”。
当一个线程完成任务时,它会从队列中取下一个任务来执行。
当一个线程无事可做,超过一定的时间(keepAliveTime)时,线程池会判断,如果当前运行的线程数大于corePoolSize,那么这个线程就被停掉。所以线程池的所有任务完成后,它最终会收缩到 corePoolSize 的大小。
Java通过Executors提供四种线程池:
CachedThreadPool:(推荐使用)可缓存的线程池,该线程池中没有核心线程,非核心线程的数量为Integer.max_value,就是无限大,当有需要时创建线程来执行任务,没有需要时回收线程,适用于耗时少,任务量大的情况。
SecudleThreadPool:周期性执行任务的线程池,按照某种特定的计划执行线程中的任务,有核心线程,但也有非核心线程,非核心线程的大小也为无限大。适用于执行周期性的任务。
SingleThreadPool:只有一条线程来执行任务,适用于有顺序的任务的应用场景。
FixedThreadPool:定长的线程池,有核心线程,核心线程的即为最大的线程数量,没有非核心线程。
- newCachedThreadPool
这种类型的线程池特点是:
工作线程的创建数量几乎没有限制(其实也有限制的,数目为Interger. MAX_VALUE), 这样可灵活的往线程池中添加线程。
如果长时间没有往线程池中提交任务,即如果工作线程空闲了指定的时间(默认为1分钟),则该工作线程将自动终止。终止后,如果你又提交了新的任务,则线程池重新创建一个工作线程。
在使用CachedThreadPool时,一定要注意控制任务的数量,否则,由于大量线程同时运行,很有会造成系统瘫痪.
import java.util.concurrent.ExecutorService;
import java.util.concurrent.Executors;
public class ThreadPoolExecutorTest {
public static void main(String[] args) {
ExecutorService cachedThreadPool = Executors.newCachedThreadPool();
for (int i = 0; i < 10; i++) {
final int index = i;
try {
Thread.sleep(index * 1000);
} catch (InterruptedException e) {
e.printStackTrace();
}
cachedThreadPool.execute(new Runnable() {
public void run() {
System.out.println(index);
}
});
}
}
}
- newFixedThreadPool
创建一个指定工作线程数量的线程池。每当提交一个任务就创建一个工作线程,如果工作线程数量达到线程池初始的最大数,则将提交的任务存入到池队列中。
FixedThreadPool是一个典型且优秀的线程池,它具有线程池提高程序效率和节省创建线程时所耗的开销的优点。但是,在线程池空闲时,即线程池中没有可运行任务时,它不会释放工作线程,还会占用一定的系统资源。
import java.util.concurrent.ExecutorService;
import java.util.concurrent.Executors;
public class ThreadPoolExecutorTest {
public static void main(String[] args) {
ExecutorService fixedThreadPool = Executors.newFixedThreadPool(3);
for (int i = 0; i < 10; i++) {
final int index = i;
fixedThreadPool.execute(new Runnable() {
public void run() {
try {
System.out.println(index);
Thread.sleep(2000);
} catch (InterruptedException e) {
e.printStackTrace();
}
}
});
}
}
}
- newSingleThreadExecutor
创建一个单线程化的Executor,即只创建唯一的工作者线程来执行任务,它只会用唯一的工作线程来执行任务,保证所有任务按照指定顺序(FIFO, LIFO, 优先级)执行。如果这个线程异常结束,会有另一个取代它,保证顺序执行。单工作线程最大的特点是可保证顺序地执行各个任务,并且在任意给定的时间不会有多个线程是活动的。
结果依次输出,相当于顺序执行各个任务。
import java.util.concurrent.ExecutorService;
import java.util.concurrent.Executors;
public class ThreadPoolExecutorTest {
public static void main(String[] args) {
ExecutorService singleThreadExecutor = Executors.newSingleThreadExecutor();
for (int i = 0; i < 10; i++) {
final int index = i;
singleThreadExecutor.execute(new Runnable() {
public void run() {
try {
System.out.println(index);
Thread.sleep(2000);
} catch (InterruptedException e) {
e.printStackTrace();
}
}
});
}
}
}
- newScheduleThreadPool
创建一个定长的线程池,而且支持定时的以及周期性的任务执行,支持定时及周期性任务执行。
例如下述代码 表示延迟1秒后每3秒执行一次。
import java.util.concurrent.Executors;
import java.util.concurrent.ScheduledExecutorService;
import java.util.concurrent.TimeUnit;
public class ThreadPoolExecutorTest {
public static void main(String[] args) {
ScheduledExecutorService scheduledThreadPool = Executors.newScheduledThreadPool(5);
scheduledThreadPool.scheduleAtFixedRate(new Runnable() {
public void run() {
System.out.println("delay 1 seconds, and excute every 3 seconds");
}
}, 1, 3, TimeUnit.SECONDS);
}
}
比较重要的几个类:
ExecutorService: 真正的线程池接口。
ScheduledExecutorService: 能和Timer/TimerTask类似,解决那些需要任务重复执行的问题。
ThreadPoolExecutor: ExecutorService的默认实现。
ScheduledThreadPoolExecutor: 继承ThreadPoolExecutor的ScheduledExecutorService接口实现,周期性任务调度的类实现。
要配置一个线程池是比较复杂的,尤其是对于线程池的原理不是很清楚的情况下,很有可能配置的线程池不是较优的,因此
在Executors类里面提供了一些静态工厂,生成一些常用的线程池。
部分片段参考文章: