线程状态
新建(New)
创建后尚未启动。
可运行(Runnable)
Java虚拟机中处于该状态的线程,对应操作系统状态中的Running和Ready,表示该线程正在执行或者等待操作系统分配执行时间
阻塞(Blocking)
当多个线程进行资源争抢,等待获取一个排他锁的线程会处于该状态,当线程获取到锁之后状态会变为RUNNABLE状态,其他等待锁的线程继续处于BLOCKED状态,一般被synchronized关键字修饰或者ReentrantLock包裹的代码快会触发不可到到达线程处于该状态。
无限期等待(Waiting)
处于该状态的线程不会被分配执行时间,需要等待被其他线程显式唤醒,调用Thread中以下方法会触发线程处于该状态:
- Object.wait() with no timeout
- Thread.join() with no timeout
- LockSupport.park() |
限期等待(Timed Waiting)
计时等待状态的线程,不会被操作系统分配执行时间,也不需要被其他线程显式唤醒,在计时结束后由操作系统自动唤醒,Thread中的以下方法会触发线程进入此状态:
- Thread.sleep()
- Object.wait() with timeout
- Thread.join() with timeout
- LockSupport.parkNanos()
- LockSupport.parkUntil()
死亡(Terminated)
可以是线程结束任务之后自己结束,或者产生了异常而结束。
Thread
构造方法:
- Thread() :创建一个默认设置的线程对象实例。
- Thread(Runnable target) :创建一个包含可执行对象的线程实例。
- Thread(Runnable target, String name) :创建一个包含可执行对象,指定名称的线程对象。
- Thread(String name):创建一个指定名称的线程对象。
- Thread(ThreadGroup group, Runnable target) :创建一个指定线程组,包含可执行对象的线程对象实例。
- Thread(ThreadGroup group, Runnable target, String name) :创建一个指定线程组,包含可执行对象,指定线程名称的线程对象实例。
- Thread(ThreadGroup group, Runnable target, String name, long stackSize) :创建一个指定线程组、包含可执行对象、指定名称以及堆栈大小的线程对象实例。
- Thread(ThreadGroup group, String name):创建一个指定线程组,线程名称的线程实例。
关于线程组(ThreadGroup类),一个线程组代表一组线程。此外,一个线程组还可以包括其他线程组。线程组形成一棵树,其中除了初始线程组之外的每个线程组都有一个父级。允许线程访问有关其自己的线程组的信息,但不能访问有关其线程组的父线程组或任何其他线程组的信息。
优先级
JAVA中创建的线程,每个线程都有一个优先级,具有较高优先级的线程优先于具有较低优先级的线程执行。当在某个线程中运行的代码创建一个新的Thread对象时,新线程的优先级最初设置为等于创建线程的优先级。
使用setPriority()设置优先级。优先级的范围1~10,值越大优先级越高。注意:设置的优先级并不能保证高优先级的线程先执行。Thread类中有三种优先级:
//最大优先级
public static final int MAX_PRIORITY = 10;
//默认优先级
public static final int NORM_PRIORITY = 5;
//最小优先级
public static final int MIN_PRIORITY = 1;
创建线程
Java中创建一个线程Thread。可以继承Thread类、实现Runnable或者Callable和Future创建线程。
继承Thread
public class MyThread extends Thread{
@Override
public void run() {
System.out.println(Thread.currentThread().getName());
}
public static void main(String[] args){
MyThread myThread=new MyThread();
myThread.start();
}
}
实现Runnable
public class MyThread implements Runnable {
@Override
public void run() {
System.out.println(Thread.currentThread().getName());
}
public static void main(String[] args) {
Thread thread = new Thread();
thread.start();
}
}
Callable和Future
public class MyThread implements Callable<String> {
public static void main(String[] args) {
MyThread callable = new MyThread();
FutureTask<String> futureTask = new FutureTask<>(callable);
new Thread(futureTask,"thread callable").start();
}
@Override
public String call() throws Exception {
return Thread.currentThread().getName();
}
}
Thread中的run()和start()的区别
start方法用来启动相应的线程;run方法只是thread的一个普通方法。
线程的run()方法是由java虚拟机直接调用的,如果我们没有启动线程(没有调用线程的start()方法)而是在应用代码中直接调用run()方法,那么这个线程的run()方法其实运行在当前线程(即run()方法的调用方所在的线程)之中。
sleep()和wait()的区别
sleep为线程的方法,而wait为Object的方法,他们的功能相似,最大本质的区别是:sleep不释放锁,wait释放锁。
用法上的不同:sleep(milliseconds)可以用时间指定来使他自动醒过来,如果时间不到你只能调用interreput()来终止线程;wait()可以用notify()/notifyAll()直接唤起。
Thread常用方法
ThreadLocal
ThreadLocal是通过线程隔离的方式防止任务在共享资源上产生冲突, 线程本地存储是一种自动化机制,可以为使用相同变量的每个不同线程都创建不同的存储。
ThreadLocal简单用法
1.通过重写ThreadLocal中的initialValue方法,为每个线程产生线程本地数据。
public class ThreadLocalDemo {
static Random random = new Random();
static ThreadLocal<Integer> stringThreadLocal = new ThreadLocal<Integer>() {
@Nullable
@Override
protected Integer initialValue() {
return random.nextInt();
}
};
public static void main(String[] args) {
new Thread(() -> {
//使用ThreadLocal get()方法返回的值为使用ThreadLocal中initialValue方法的返回值。
//同一个线程多次调用返回的值一样。
//不同线程调用会产生不同的值,每个线程都用相对应的线程本地数据
Integer value = stringThreadLocal.get();
System.out.print(value);
}).start();
}
}
2.通过ThreadLocal set方法为每个线程设置线程本地数据。
public class ThreadLocalDemo {
static ThreadLocal<Integer> stringThreadLocal = new ThreadLocal<Integer>();
public static void main(String[] args) {
stringThreadLocal.set(1);
new Thread(() -> {
System.out.print(stringThreadLocal.get());//输出值为null
stringThreadLocal.set(2);
System.out.print(stringThreadLocal.get());//输出值为2
}).start();
System.out.print(stringThreadLocal.get());//输出值为1
}
}
ThreadLocal原理
ThreadLocal主要是用到了Thread对象中的一个ThreadLocalMap类型的变量threadLocals来实现线程隔离。
具体源码:
public T get() {
Thread t = Thread.currentThread();
ThreadLocalMap map = getMap(t);
if (map != null) {
ThreadLocalMap.Entry e = map.getEntry(this);
if (e != null) {
@SuppressWarnings("unchecked")
T result = (T)e.value;
return result;
}
}
return setInitialValue();
}
ThreadLocalMap getMap(Thread t) {
return t.threadLocals;
}
- 首先获取当前线程对象t, 然后从线程t中获取到ThreadLocalMap的成员属性threadLocals
- 如果当前线程的threadLocals已经初始化(即不为null) 并且存在以当前ThreadLocal对象为Key的值, 则直接返回当前线程要获取的对象(本例中为Connection);
- 如果当前线程的threadLocals已经初始化(即不为null)但是不存在以当前ThreadLocal对象为Key的的对象, 那么重新创建一个Connection对象, 并且添加到当前线程的threadLocals Map中,并返回
- 如果当前线程的threadLocals属性还没有被初始化, 则重新创建一个ThreadLocalMap对象, 并且创建一个Connection对象并添加到ThreadLocalMap对象中并返回。
如果存在则直接返回,若不存在使用setInitialValue方法,并调用ThreadLocal中的initialValue方法创建value。
private T setInitialValue() {
T value = initialValue();
Thread t = Thread.currentThread();
ThreadLocalMap map = getMap(t);
if (map != null)
map.set(this, value);
else
createMap(t, value);
return value;
}
同时, ThreadLocal还提供了直接操作Thread对象中的threadLocals的方法。 这样我们也可以不实现initialValue, 也能对ThreadLocal设置value。
public void set(T value) {
Thread t = Thread.currentThread();
ThreadLocalMap map = getMap(t);
if (map != null)
map.set(this, value);
else
createMap(t, value);
}
线程池(Executor)
Executors创建线程池方法:
| 方法名 | 说明 |
|---|---|
| newSingleThreadExecutor() | 创建只有一个线程的线程池 |
| newFixedThreadPool(int nThreads) | 创建固定大小的线程池 |
| newCachedThreadPool() | 创建一个不限制线程数量的线程池,任何提交的任务都将立即执行,但是闲置线程会得到及时回收 |
| newScheduledThreadPool | 创建一个可定期或者延时执行任务的线程池 |
SingleThreadPool和FixedThreadPool
这两个工厂方法所创建的线程池,任务队列的长度都为Integer.MAX_VALUE,可能会堆积大量的任务,从而导致OOM。
CachedThreadPool和ScheduledThreadPool
这两个工厂方法所创建的线程池允许创建的线程数量为Integer.MAX_VALUE,可能会导致创建大量的线程,从而导致OOM。
newSingleThreadExecutor
该方法用于创建一个“单线程化线程池”,也就是只有一个线程的线程池,所创建的线程池用唯一的工作线程来执行任务,使用此方法创建的线程池能保证所有任务按照指定顺序(如FIFO)执行。
该线程池有以下特点:
-
单线程化的线程池中的任务是按照提交的顺序执行的。
-
池中的唯一线程的存活时间是无限的。
-
当池中的唯一线程正繁忙时,新提交的任务实例会进入内部的阻塞队列中,并且其阻塞队列是无界的。
适用的场景是:任务按照提交次序,一个任务一个任务地逐个执行的场景。
newFixedThreadPool
该方法用于创建一个“固定数量的线程池”,其唯一的参数用于设置池中线程的“固定数量”。
- 该线程池有以下特定:
- 如果线程池没有达到“固定数量”,每次提交一个任务线程池内就创建一个新线程,直到线程达到线程池固定的数量。
- 线程池的大小一旦达到“固定数量”就会保持不变,如果某个线程因为执行异常而结束,那么线程池会补充一个新线程。
- 在接收异步任务的执行目标实例时,如果池中的所有线程均在繁忙状态,新任务会进入阻塞队列中(无界的阻塞队列)。
适用的场景是:需要任务长时间的场景。注意:内部使用无界队列来存放排队任务,当大量任务超过线程池最大容量需要处理时,队列无限增大,使服务器资源迅速耗尽。
newCachedThreadPool
该方法用于创建一个“可缓存线程池”,如果线程池内的某些线程无事可干成为空闲线程,“可缓存线程池”可灵活回收这些空闲线程。
该线程池有以下特点:
- 在接收新的异步任务target执行目标实例时,如果池内所有线程繁忙,此线程池就会添加新线程来处理任务。
- 此线程池不会对线程池大小进行限制,线程池大小完全依赖于操作系统(或者说JVM)能够创建的最大线程大小。
- 如果部分线程空闲,也就是存量线程的数量超过了处理任务数量,就会回收空闲(60秒不执行任务)线程。
适用的场景是:需要快速处理突发性强、耗时较短的任务场景。注意线程池没有最大线程数量限制,如果大量的异步任务执行目标实例同时提交,可能会因创建线程过多而导致资源耗尽。
newScheduledThreadPool
该方法用于创建一个“可调度线程池”,即一个提供“延时”和“周期性”任务调度功能的ScheduledExecutorService类型的线程池。
适用的场景是:周期性地执行任务的场景。
标准线程池创建(ThreadPoolExecutor)
Executors中创建线程池的快捷方法,实际上是调用了ThreadPoolExecutor的构造方法、定时任务使用的是ScheduledThreadPoolExecutor。
ThreadPoolExecutor构造函数
public ThreadPoolExecutor(int corePoolSize,// 核心线程数,即使线程空闲,也不会回收
int maximumPoolSize,// 最大线程数
long keepAliveTime,// 线程最大空闲时长
TimeUnit unit,// 空闲时长单位
BlockingQueue<Runnable> workQueue,// 任务阻塞队列
ThreadFactory threadFactory,// 新线程的产生方式
RejectedExecutionHandler handler// 拒绝策略
)
参数详解:
- corePoolSize:核心线程数,也是线程池中常驻的线程数,线程池初始化时默认是没有线程的,当任务来临时才开始创建线程去执行任务
- maximumPoolSize:最大线程数,在核心线程数的基础上可能会额外增加一些非核心线程,需要注意的是只有当workQueue队列填满时才会创建多于corePoolSize的线程(线程池总线程数不超过maxPoolSize)
- keepAliveTime:非核心线程的空闲时间超过keepAliveTime就会被自动终止回收掉,注意当corePoolSize=maxPoolSize时,keepAliveTime参数也就不起作用了(因为不存在非核心线程);
- unit:keepAliveTime的时间单位
- workQueue:用于保存任务的队列,可以为无界、有界、同步移交三种队列类型之一,当池子里的工作线程数大于corePoolSize时,这时新进来的任务会被放到队列中。
- SynchronousQueue(同步移交队列):队列不作为任务的缓冲方式,可以简单理解为队列长度为零
- LinkedBlockingQueue(无界队列):队列长度不受限制,当请求越来越多时(任务处理速度跟不上任务提交速度造成请求堆积)可能导致内存占用过多或OOM
- ArrayBlockintQueue(有界队列):队列长度受限,当队列满了就需要创建多余的线程来执行任务
- threadFactory:创建线程的工厂类,默认使用Executors.defaultThreadFactory(),也可以使用guava库的ThreadFactoryBuilder来创建
- handler:线程池无法继续接收任务(队列已满且线程数达到maximunPoolSize)时的饱和策略,取值有AbortPolicy、CallerRunsPolicy、DiscardOldestPolicy、DiscardPolicy。
- AbortPolicy:中断抛出异常
- DiscardPolicy:默默丢弃任务,不进行任何通知
- DiscardOldestPolicy:丢弃掉在队列中存在时间最久的任务
- CallerRunsPolicy:让提交任务的线程去执行任务(对比前三种比较友好一丢丢)
线程池线程创建流程图:
线程池的关闭
- shutdownNow():立即关闭线程池(暴力),正在执行中的及队列中的任务会被中断,同时该方法会返回被中断的队列中的任务列表
- shutdown():平滑关闭线程池,正在执行中的及队列中的任务能执行完成,后续进来的任务会被执行拒绝策略
- isTerminated():当正在执行的任务及对列中的任务全部都执行(清空)完就会返回true
提交任务的三种方式
| 提交方式 | 是否关心返回结果 |
|---|---|
| Future submit(Callable task) | 是 |
| void execute(Runnable command) | 否 |
| Future<?> submit(Runnable task) | 否,虽然返回Future,但是其get()方法总是返回null |
Future和FutureTask
Future接口用来表示执行异步任务的结果存储器,当一个任务的执行时间过长就可以采用这种方式:把任务提交给子线程去处理,主线程不用同步等待,当向线程池提交了一个Callable或Runnable任务时就会返回Future,用Future可以获取任务执行的返回结果。Future的主要方法包括:
- get()方法:返回任务的执行结果,若任务还未执行完,则会一直阻塞直到完成为止,如果执行过程中发生异常,则抛出异常,但是主线程是感知不到并且不受影响的,除非调用get()方法进行获取结果则会抛出ExecutionException异常;
- get(long timeout, TimeUnit unit):在指定时间内返回任务的执行结果,超时未返回会抛出TimeoutException,这个时候需要显式的取消任务;
- cancel(boolean mayInterruptIfRunning):取消任务,boolean类型入参表示如果任务正在运行中是否强制中断;
- isDone():判断任务是否执行完毕,执行完毕不代表任务一定成功执行,比如任务执行失但也执行完毕、任务被中断了也执行完毕都会返回true,它仅仅表示一种状态说后面任务不会再执行了;
- isCancelled():判断任务是否被取消;
Future和FutureTask关系图
Future使用
public static void main(String[] args) {
ExecutorService executorService = Executors.newSingleThreadExecutor();
Future<Object> submit = executorService.submit(new Callable<Object>() {
@Override
public Object call() throws Exception {
return null;
}
});
Object o = submit.get();//使用get(),程序将停止往下执行,一直等待结果,直到有返回值
}
FutureTask使用
public static void main(String[] args) {
ExecutorService executorService = Executors.newSingleThreadExecutor();
FutureTask<Object> objectFutureTask = new FutureTask<Object>(new Callable<Object>() {
@Override
public Object call() throws Exception {
return null;
}
});
//使用execute执行
executorService.execute(objectFutureTask);
//ExecutorService的execute是没有返回值的,使用这种用法需要注意的是FutureTask的get方法会一直等待结果的返回,
// 如果get的调用顺序在execute之前的话,那么程序将会停止在get这里。
objectFutureTask.get();
//通用也可以使用submit执行
Future<?> submit = executorService.submit(objectFutureTask);
submit.get();
}