1 线程状态
Java线程的状态在java.lang.Thread类中的State内部枚举类中定义,共有六种状态,分别是NEW(初始化)、 RUNNABLE(可运行)、 BLOCKED(阻塞)、 WAITING(等待)、 TIMED_WAITING(超时等待)、 TERMINATED(终止)。六种状态的转换如下:
1.1 NEW(初始化)
新建一个线程,此时线程尚未启动,也就是没有没有调用Thread.start()方法
1.2 RUNNABLE(可运行)
Java中runnable状态包含ready(就绪)和running(运行中)两种状态,new、block、waiting、time_waiting状态到runnable状态时,线程首先进入可运行线程池中,等待被调度,此时线程处于ready状态。当线程获取到CPU时间片,线程进去running状态,如果时间片用完或调用Thread.yield()方法,线程会让出CPU时间片,此时,线程处于ready状态。如果在running状态,程序执行完成或者发生异常,程序最终会到terminated(终止)状态。
1.3 TERMINATED(终止)
线程已经结束的状态
1.4 BLOCKED(阻塞)
当线程需要一个锁才能执行接下来的操作,而锁又被其他线程占用时,该线程进入阻塞状态。
1.5 WAITING(等待)
当出现以下两种情况
1、线程a执行了Object.wait(),而其他线程并没有Object.notify()或Object.notifyAll()时
2、线程a执行了thread.join(),线程thread没有执行完时
线程a会处于等待状态。需要注意的是此时线程是没有超时时间的。
1.6 TIME_WAITING(超时等待)
与waiting不同的是:time_waiting有超时时间的概念。如果线程等待了一段时间(超时时间)之后会重新获取锁。
2 四种实现方式
2.1 继承Thread类
通过继承Thread类,并重写Thread类的run方法即可创建一个新的线程类
public class ThreadDemo1 extends Thread {
@Override
public void run() {
System.out.println("当前线程是:"+Thread.currentThread().getName());
}
}
启动线程类是通过调用start()方法进行的
public static void main(String[] args) {
Thread thread1 = new ThreadDemo1();
thread1.start();
}
2.2 实现Runnable接口
事实上Thread类也是通过实现Runnable接口实现的。如果一个类已经继承了一个类,那么此时它就无法在继承Thread类了,如果此时我们还想此类是一个线程类,那么最好的方法应该是实现Runnable接口了。代码如下
public class ThreadDemo2 implements Runnable {
@Override
public void run() {
System.out.println("当前线程是:"+Thread.currentThread().getName());
}
}
读过源码的朋友应该都知道Thread类和Runnble接口以及Runnable接口的实现类是典型的代理模式,Thread类实现了Runnable接口并代理了Runnable接口的其他实现类。所以实现Runnable接口的线程启动方式与2.1稍有不同,需要new一个Thread类在启动,代码如下
public static void main(String[] args) {
ThreadDemo2 demo2 = new ThreadDemo2();
Thread thread2 = new Thread(demo2);
thread2.start();
}
2.3 实现Callable接口
不管是继承Thread类还是实现Runnable接口,线程都没法返回结果,如果我们想要获得新线程的结果怎么办?通过Callable和FuterTask的组合可以很容易实现这一点。使用方法可以分为以下四步:1)创建Callable实现类,并实现Callable接口;2)new一个Callable的实现,并使用FuterTask包装这个实现;3)将FuterTask的实现作为参数创建Thread,调用Thread的start方法启动线程;4)使用FuterTast的get()方法获取子线程的返回结果。代码如下
//1、创建Callable实现类
public class CallableTask<Object> implements Callable<Object> {
@Override
public Object call() {
System.out.println(Thread.currentThread().getName());
return (Object) "这是callable返回值";
}
}
public class ThreadDemo3 {
public static void main(String[] args) throws ExecutionException, InterruptedException {
//2、new一个Callable的实现,并使用FuterTask包装这个实现
Callable<Object> callable = new CallableTask<>();
FutureTask<Object> futureTask = new FutureTask<>(callable);
//3、将FuterTask的实现作为参数创建Thread,调用Thread的start方法启动线程
Thread thread = new Thread(futureTask);
thread.start();
System.out.println(Thread.currentThread().getName());
//4、使用FuterTast的get()方法获取子线程的返回结果
System.out.println(futureTask.get());
}
}
2.4 使用线程池
由于线程池资源可控、响应快并且便于管理,所以我们在实际工作中多线程用到最多的还是线程池。线程池的使用也非常简单,只需要使用Executors类的相关方法,然后将线程类submit到线程池中,最后使用Future去接收执行的结果就行。实例代码如下
public class ThreadDemo3 {
public static void main(String[] args) throws ExecutionException, InterruptedException {
ExecutorService threadPool = Executors.newFixedThreadPool(10);
Future<String> future = threadPool.submit(new Callable<String>() {
@Override
public String call() throws Exception {
System.out.println(Thread.currentThread().getName());
return "我是线程池里返回的字符串";
}
});
Thread.sleep(100);
System.out.println(Thread.currentThread().getName());
System.out.println(future.get());
}
}
Executors提供了一些列工厂方法用于创建线程池,返回的线程池都实现了ExecutorService这个接口。最常用的是一下四个方法:
- newFixedThreadPool()创建固定数目线程的线程池
- newCachedThreadPool()创建一个可缓存的线程池,调用execute 将重用以前构造的线程(如果线程可用)。如果现有线程没有可用的,则创建一个新线程并添加到池中。终止并从缓存中移除那些已有 60 秒钟未被使用的线程。
- newSingleThreadExecutor()创建一个单线程化Executor
- newScheduledThreadPool() 创建一个支持定时及周期性的任务执行的线程池,多数情况下可用来替代Timer类。
3 线程池原理
3.1 线程池参数
2.4提到的四种创建线程池的方法最终都是调用new ThreadPoolExecutor()去创建线程池,ThreadPoolExecutor构造函数共有7个参数,他们分别是:
-
corePoolSize:核心线程数大小。当提交一个任务到线程池时,线程池会创建一个线程去执行此任务(有空闲线程也创建),当线程池中的线程数目达到corePoolSize时就不再创建线程,而是把此线程放入到缓存队列中。线程池创建时,默认情况下,corePoolSize为0,如果调用了prestartAllCoreThreads()或prestartCoreThread()这两个方法,会预先创建一些线程放入到线程池。
-
maximumPoolSize:最大线程数。线程池允许创建的最多的线程数量,超过此数量线程池就不能在创建线程,如果此时缓存队列也满了,就会触发拒绝策略。
-
keepAliveTime:线程没有任务执行时的存活时间。这个参数在线程数大于corePoolSize才有意义。
-
unit:keepAliveTime的时间单位
-
workQueue:阻塞队列。用来存储等待执行的任务,常用的有LinkedBlockingQueue、SynchronousQueue,此外还有ArrayBlockingQueue、PriorityBlockingQueue
-
threadFactory:线程工厂。用来创建线程
-
handler:拒绝策略。主要有AbortPolicy(直接丢弃并且抛出RejectedExecutionException异常)、DiscardPolicy(丢弃任务但不抛异常)、DiscardOldestPolicy(丢弃最前面的任务,并执行当前任务)、CallerRunsPolicy(调用者所在线程处理任务)
3.2 重要的成员变量
//低29位标识线程池中线程数量,高3位标识线程池运行状态
//线程池运行状态分别为:RUNNING(111)、SHUTDOWN(000)、STOP(001)、TIDYING(010)、TERMINATED(011)
private final AtomicInteger ctl = new AtomicInteger(ctlOf(RUNNING, 0));
//线程池最大数量,2^29 - 1
private static final int CAPACITY = (1 << COUNT_BITS) - 1;
//任务缓存队列,用来存放等待执行的任务
private final BlockingQueue<Runnable> workQueue;
//可重入锁,线程池状态改变时使用
private final ReentrantLock mainLock = new ReentrantLock();
//存放工作线程
private final HashSet<Worker> workers = new HashSet<Worker>();
3.3 execute(Runnable command)源码分析
在看execute()源码之前,我们先看一下submit()的源码,源码比较简单,直接附上
public <T> Future<T> submit(Callable<T> task) {
if (task == null) throw new NullPointerException();
RunnableFuture<T> ftask = newTaskFor(task);
execute(ftask);
return ftask;
}
从这段源码可以看出,submit最终也是调execute()方法执行的。
public void execute(Runnable command) {
if (command == null)
throw new NullPointerException();
//线程池中线程数量小于核心线程数,将任务加入workers集合,如果加入成功直接返回
int c = ctl.get();
if (workerCountOf(c) < corePoolSize) {
if (addWorker(command, true))
return;
c = ctl.get();
}
//如果线程池处于RUNNING状态,且任务能加入阻塞队列
if (isRunning(c) && workQueue.offer(command)) {
//重新获取线程池的状态,如果线程池处于非RUNNING状态,那么将任务从阻塞队列中移除,并拒绝任务
int recheck = ctl.get();
if (! isRunning(recheck) && remove(command))
reject(command);
//如果线程池没有任务,创建新线程去执行任务
else if (workerCountOf(recheck) == 0)
addWorker(null, false);
}
//如果上面都没有成功,尝试开启新线程去执行任务,如果开启失败,拒绝任务
else if (!addWorker(command, false))
reject(command);
}