文章目录
前言
我们在创建线程时有四种方法
1、继承Thread类创建线程
2、实现Runnable接口创建线程
3、使用Callable和Future创建线程
4、使用线程池创建(使用java.util.concurrent.Executor接口)
一、创建线程
1.继承Thread类
简单实现:
-
定义Thread类的子类,并重写该类的run方法,该run方法的方法体就是线程要完成的任务。run()方法称为执行体。
-
创建Thread子类的实例,即创建了线程对象。
-
调用线程对象的start()方法来启动该线程。
代码示例如下:
package thread;
public class ThreadModelOne extends Thread {
@Override
public void run() {
System.out.println("我是继承Thread创建的子线程");
}
public static void main(String[] args) {
//创建两个子线程
for (int i = 0; i < 2; i++) {
new ThreadModelOne().start();
}
craterByJava8();
}
// 使用java8的Lambda表达式创建
public static void craterByJava8() {
new Thread(){
@Override
public void run() {
System.out.println("我是实现Runnable接口创建ByJava8");
}
}.start();
}
}
2.实现Runnable接口
简单实现:
- 定义runnable接口的实现类,并重写该接口的run()方法,该run()方法的方法体同样是该线程的线程执行体
- 创建 Runnable实现类的实例,并依此实例作为Thread的target来创建Thread对象,该Thread对象才是真正的线程对象
- 调用线程对象的start()方法来启动该线程
代码示例如下:
package thread;
public class ThreadModelTwo implements Runnable {
@Override
public void run() {
System.out.println("我是实现Runnable接口创建的子线程");
}
public static void main(String[] args) {
Runnable runnable = new ThreadModelTwo();
//同样是生成两个子线程
for (int i = 0; i < 2; i++) {
new Thread((runnable)).start();
}
craterByJava8();
}
// 使用java8的Lambda表达式创建
public static void craterByJava8() {
new Thread(() -> {
System.out.println("我是实现Runnable接口创建ByJava8");
}).start();
}
}
3.使用Callable和Future
和Runnable接口不一样,Callable接口提供了一个call()方法作为线程执行体,call()方法比run()方法功能要强大,其表现在:
call方法可以有返回值
call()方法可以声明抛出异常
简单实现:
- 创建Callable接口的实现类,并实现call()方法,该call()方法将作为线程执行体,并且有返回值
- 创建Callable实现类的实例,使用FutureTask类来包装Callable对象,该FutureTask对象封装了该Callable对象的call()方法的返回值。
- 使用FutureTask对象作为Thread对象的target创建并启动新线程(因为FutureTask实现了Runnable接口)。
- 调用FutureTask对象的get()方法来获得子线程执行结束后的返回值
代码示例如下:
package thread;
import java.util.concurrent.Callable;
import java.util.concurrent.ExecutionException;
import java.util.concurrent.FutureTask;
public class ThreadModelThree implements Callable<String> {
@Override
public String call() throws Exception {
System.out.println("我是实现Callable接口创建的子线程");
Thread.sleep(3000);
return Thread.currentThread().getName();
}
public static void main(String[] args) throws ExecutionException, InterruptedException {
ThreadModelThree callable = new ThreadModelThree();
FutureTask<String> futureTask = new FutureTask <>(callable);
Thread thread = new Thread(futureTask,"Hello,Tom");
Thread thread2 = new Thread(futureTask,"Hello,Jerry");
thread.start();
thread2.start();
System.out.println(futureTask.get());
}
}
二、使用线程池
1. 线程的生命周期
java中经常需要用到多线程来处理一些业务,创建过多的线程也可能引发资源耗尽的风险,不建议单纯使用继承Thread或者实现Runnable接口的方式来创建线程,那样势必有创建及销毁线程耗费资源、线程上下文切换问题。
线程有五种基本状态,分别是:
1、NEW(初始化)状态
实例化一个Thread类对象出来(未执行start()方法前),Thread的状态为NEW。
2、RUNNABLE(可运行)状态
调用线程的start()方法,此时线程进入RUNNABLE状态,该状态的线程位于可运行线程池中,等待被操作系统调度,获取CPU的使用权。
当获得CPU的时间片时,线程进入运行状态,执行程序代码。
3、BLOCKED(阻塞)状态
当线程等待获取monitor锁(synchronized)时,线程就会进入BLOCKED状态。
-注意:
等待获取monitor锁,线程的状态是BLOCKED。
等待获取Lock锁(LockSupport.park()),线程的状态是WAITING
4.1、TIMED_WAITING(超时等待)状态
当执行 Thread.sleep(time)、Thread.join(time)、Object.wait(time)、LockSupport.parkNanos(time)、LockSupport.partUntil(time)等操作时,线程会从RUNNABLE状态进入TIMED_WAITING状态。
当执行Object.notify()/notifyAll()、Thread.join()程序执行完、LockSupport.unpark()、线程被中断等操作时,线程会从WAITING状态进入RUNNABLE状态
4.2 、WAITING(等待)状态
当执行Object.wait()、Thread.join()、LockSupport.park()等操作时,线程会从RUNNABLE状态进入WAITING状态。
当执行Object.notify()/notifyAll()、Thread.join()程序执行完、LockSupport.unpark()、线程被中断等操作时,线程会从WAITING状态进入RUNNABLE状态
5、TERMINATED(终止)状态
当线程执行完毕、Thread.stop()、内存溢出时,线程就会进入TERMINATED状态。
线程一旦死亡,就无法复活。
在一个死亡的线程上调用start()方法,会抛出java.lang.IllegalThreadStateException异常。
2.线程池使用
这个时候引入线程池比较合理,方便线程任务的管理。
java中涉及到线程池的相关类均在jdk1.5开始的java.util.concurrent包中,涉及到的几个核心类及接口包括:Executor、Executors、ExecutorService、ThreadPoolExecutor、FutureTask、Callable、Runnable等
线程池可以自动创建也可以手动创建:
1、自动创建体现在Executors工具类中,常见的可以创建newFixedThreadPool、newCachedThreadPool、newSingleThreadExecutor、newScheduledThreadPool
public static ExecutorService newFixedThreadPool(int nThreads) {
return new ThreadPoolExecutor(nThreads, nThreads,
0L, TimeUnit.MILLISECONDS,
new LinkedBlockingQueue<Runnable>());
}
public static ExecutorService newCachedThreadPool() {
return new ThreadPoolExecutor(0, Integer.MAX_VALUE,
60L, TimeUnit.SECONDS,
new SynchronousQueue<Runnable>());
}
public static ScheduledExecutorService newScheduledThreadPool(int corePoolSize) {
return new ScheduledThreadPoolExecutor(corePoolSize);
}
public static ExecutorService newSingleThreadExecutor() {
return new FinalizableDelegatedExecutorService
(new ThreadPoolExecutor(1, 1,
0L, TimeUnit.MILLISECONDS,
new LinkedBlockingQueue<Runnable>()));
}
-
Executors.newFixedThreadPool 创建一个定长线程池,可控制线程最大并发数,超出的线程会在队列中等待
-
Executors.newSingleeThreadPool()创建一个单线程化的线程池,它只会用唯一的工作线程来执行任务,保证所有任务按照指定顺序(FIFO, LIFO, 优先级)执行
-
Executors.newScheduledThreadPool ()创建一个定长线程池,支持定时及周期性任务执行。
-
Executors.newCacheThreadPool()创建一个可缓存线程池,如果线程池长度超过处理需要,可灵活回收
2、手动创建主要用ThreadPoolExecutor类,体现在可以灵活设置线程池的各个参数,体现在代码中即ThreadPoolExecutor类构造器上各个实参的不同:
public ThreadPoolExecutor(int corePoolSize,
int maximumPoolSize,
long keepAliveTime,
TimeUnit unit,
BlockingQueue<Runnable> workQueue,
ThreadFactory threadFactory) {
this(corePoolSize, maximumPoolSize, keepAliveTime, unit, workQueue,
threadFactory, defaultHandler);
public ThreadPoolExecutor(int corePoolSize,
int maximumPoolSize,
long keepAliveTime,
TimeUnit unit,
BlockingQueue<Runnable> workQueue,
RejectedExecutionHandler handler) {
this(corePoolSize, maximumPoolSize, keepAliveTime, unit, workQueue,
Executors.defaultThreadFactory(), handler);
}
ThreadPoolExecutor中重要的几个参数详解
corePoolSize:核心线程数,也是线程池中常驻的线程数,线程池初始化时默认是没有线程的,当任务来临时才开始创建线程去执行任务。
maximumPoolSize:最大线程数,在核心线程数的基础上可能会额外增加一些非核心线程,需要注意的是只有当workQueue队列填满时才会创建多于corePoolSize的线程(线程池总线程数不超过maxPoolSize)
keepAliveTime:非核心线程的空闲时间超过keepAliveTime就会被自动终止回收掉,注意当corePoolSize=maxPoolSize时,keepAliveTime参数也就不起作用了(因为不存在非核心线程);
unit:keepAliveTime的时间单位
workQueue:用于保存任务的队列,可以为*、有界、同步移交三种队列类型之一,当池子里的工作线程数大于corePoolSize时,这时新进来的任务会被放到队列中
threadFactory:创建线程的工厂类,默认使用Executors.defaultThreadFactory(),也可以使用guava库的ThreadFactoryBuilder来创建。
handler:线程池无法继续接收任务(队列已满且线程数达到maximunPoolSize)时的饱和策略,取值有AbortPolicy、CallerRunsPolicy、DiscardOldestPolicy、DiscardPolicy。
线程池中的线程创建流程(拒绝策略):
总结
实现Runnable和实现Callable接口的方式基本相同,不过是后者执行call()方法有返回值,后者线程执行体run()方法无返回值,并且如果使用FutureTask类的话,只执行一次Callable任务。
这种方式与继承Thread类的方法之间的差别如下:
1、线程只是实现Runnable或实现Callable接口,还可以继承其他类。
2、这种方式下,多个线程可以共享一个target对象,非常适合多线程处理同一份资源的情形。
3.继承Thread类只需要this就能获取当前线程。不需要使用Thread.currentThread()方法
4、继承Thread类的线程类不能再继承其他父类(Java单继承决定)。
5、前三种的线程如果创建关闭频繁会消耗系统资源影响性能,而使用线程池可以不用线程的时候放回线程池,用的时候再从线程池取,项目开发中主要使用线程池的方式创建多个线程。
6.实现接口的创建线程的方式必须实现方法(run() call())。