Java 并发编程 线程和线程池
进程和线程
一个进程是一个独立的运行环境,它可以被看作为一个程序或者一个应用。线程是在进程中执行的一个任务。线程是进程的子集,一个进程可以有多个线程,每条线程并行执行不同的任务。
通用线程的生命周期
五大状态
:初始状态、可运行状态、运行状态、休眠状态、终止状态
- 初始状态: 指的是线程已经被创建,但是还没有分配CPU执行。这里所谓的线程被创建,仅仅是编程语言层面的被创建,而在操作系统层面,真正的线程还没有创建。
- 可运行状态: 指的是线程可以分配CPU执行。在这种状态下,真正的操作系统已经被成功创建了,所以可以分配CPU执行。
- 运行状态: 当有空闲的CPU时,操作系统会将其分配给一个处于可运行状态的线程,被分配到CPU的线程的状态就转换成了运行状态。
- 休眠状态: 运行状态的线程如果调用了一个阻塞的API或者等待某个事件,那么线程的状态就会转换成休眠状态,同时释放CPU的使用权,当等待的事件出现了,线程就会从休眠状态转换为可运行状态。
- 终止状态: 线程执行完或者出现异常就会进入终止状态,此时线程的生命周期结束
Java 中线程的生命周期
Java语言中线程共有六种状态,分别是:
- NEW (初始化状态)
- RUNNABLE(可运行 / 运行状态)
- BLOCKED (阻塞状态)
- WAITING (无限时等待)
- TIMED_WAITING(有限时等待)
- TERMINATED (终止状态)
RUNNABLE >> BLOCKED
RUNNABLE >> WAITING
RUNNABLE >> TIMED_WAITING
Thread 和 Runnable
如何创建一个线程
继承Thread
public class TestThread extends Thread{
@Override
public void run() {
String threadName = Thread.currentThread().getName();
System.out.println("Hello " + threadName);
}
}
// main
TestThread testThread = new TestThread();
testThread.start();
实现Runnable接口
public class TestRunnable implements Runnable{
@Override
public void run() {
String threadName = Thread.currentThread().getName();
System.out.println("Hello " + threadName);
}
}
// main
TestRunnable testRunnable = new TestRunnable();
Thread testThread = new Thread(testRunnable);
testThread.start();
Runnable runnable = () -> {
String threadName = Thread.currentThread().getName();
System.out.println("Hello " + threadName);
};
runnable.run();
Thread thread = new Thread(runnable);
thread.start();
System.out.println("Done");
使用ExecutorService、Callable、Future实现有返回结果的多线程
ExecutorService executorService = Executors.newSingleThreadExecutor();
executorService.submit(() -> {
String threadName = Thread.currentThread().getName();
System.out.println("Hello " + threadName);
});
线程常用方法
start()
使该线程开始执行
setDaemon(boolean on)
将该线程标记为守护线程或用户线程
join(long millis)
等待该线程终止的时间最长为 millis 毫秒
interrupt()
中断线程
yield()
暂停当前正在执行的线程对象,并执行其他线程
sleep()
在指定的毫秒数内让当前正在执行的线程休眠(暂停执行),此操作受到系统计时器和调度程序精度和准确性的影响。
sleep()
指定毫秒数内让当前正在执行的线程休眠。
join()
join的作用是等待线程对象销毁。
public class TestThreadJoin extends Thread{
public TestThreadJoin(String name) {
super(name);
}
public void run() {
for (int i = 0; i < 5; i++) {
System.out.println(getName() + " " + i);
}
}
public static void main(String[] args) throws InterruptedException {
// 启动子进程
new TestThreadJoin("new thread").start();
for (int i = 0; i < 10; i++) {
if (i == 5) {
TestThreadJoin th = new TestThreadJoin("joined thread");
th.start();
th.join();
}
System.out.println(Thread.currentThread().getName() + " " + i);
}
}
}
线程的优先级
守护线程
Java 中线程分为两种,一种是用户线程(User Thread),另一种是Daemon Thread (守护线程)
守护线程的作用是为其他线程的运行提供服务,比如说GC线程。如果用户线程全部结束,那么守护线程也就退出了。
线程池
首先看下 《阿里巴巴 Java 手册》关于线程的规范:
在面向对象的编程中,创建和销毁对象是很费时间的,
由此可见线程的使用应该慎重,尽量使用线程池来解决问题
线程池的目的:
- 线程是稀缺资源,不能频繁的创建。。
- 解耦作用;线程的创建和执行完全分开,方便维护
- 应当将其放入一个池子中,可以给其他任务进行复用,重用线程池中的线程,减少因对象的创建,销毁所带来的性能开销
- 能有效控制线程的最大并发数,提高系统资源利用率,同时避免过多的资源竞争,避免堵塞。
池化资源
提高服务程序的效率的一个手段就是尽可能减少创建和销毁对象的次数,特别是一些很好资源的对象创建和销毁。如何利用一个已有对象来服务就是需要解决的关键问题,这便是池化资源
技术产生的原因
线程池的原理:
线程池的核心思想就是将宝贵的资源放到一个池子中;每次使用都从池子里获取,用完之后又放回池子供其他人使用。
线程池的创建
ExecutorService newCachedThreadPool()
:无限线程池。ExecutorService newFixedThreadPool(int nThreads)
创建固定大小的线程池。ExecutorService newSingleThreadExecutor()
创建单个线程的线程池。
创建线程池源码
public static ExecutorService newCachedThreadPool() {
return new ThreadPoolExecutor(0, Integer.MAX_VALUE,
60L, TimeUnit.SECONDS,
new SynchronousQueue<Runnable>());
}
创建线程的API
public ThreadPoolExecutor(int corePoolSize,
int maximumPoolSize,
long keepAliveTime,
TimeUnit unit,
BlockingQueue<Runnable> workQueue,
ThreadFactory threadFactory,
RejectedExecutionHandler handler) {
if (corePoolSize < 0 ||
maximumPoolSize <= 0 ||
maximumPoolSize < corePoolSize ||
keepAliveTime < 0)
throw new IllegalArgumentException();
if (workQueue == null || threadFactory == null || handler == null)
throw new NullPointerException();
this.acc = System.getSecurityManager() == null ?
null :
AccessController.getContext();
this.corePoolSize = corePoolSize;
this.maximumPoolSize = maximumPoolSize;
this.workQueue = workQueue;
this.keepAliveTime = unit.toNanos(keepAliveTime);
this.threadFactory = threadFactory;
this.handler = handler;
}
核心的几个参数
corePoolSize
为线程池的基本大小maximumPoolSize
为线程池最大线程大小keepAliveTime
和unit
线程空闲后的存活时间workQueue
用于存放任务的阻塞队列handler
当队列和最大线程池都满了之后的饱和策略。
几个重要的类:
Executor
: 所有线程池的接口,只有一个方法。
public interface Executor {
void execute(Runnable command);
}
ExecutorService
: 增加Executor的行为,是Executor实现类的最直接接口。
Executors
: 提供了一系列工厂方法用于创先线程池,返回的线程池都实现了ExecutorService 接口。
ThreadPoolExecutor
:线程池的具体实现类,一般用的各种线程池都是基于这个类实现的。
构造方法如下:
线程池中定义的几个状态
private static final int RUNNING = -1 << COUNT_BITS;
private static final int SHUTDOWN = 0 << COUNT_BITS;
private static final int STOP = 1 << COUNT_BITS;
private static final int TIDYING = 2 << COUNT_BITS;
private static final int TERMINATED = 3 << COUNT_BITS;
RUNNING
运行状态SHUTDOWN
指的是调用了shutdown()
方法,不再接受新的任务,但是队列里的任务得执行完毕STOP
指调用shutdownnow()
方法,不再接受新的任务,同时抛弃阻塞队列里的所有任务并中断所有正在执行的任务。TIDYING
所有的任务都执行完毕。TERMINATED
终止状态,当执行terminated()
后会更新为这个状态。
线程池的工作过程
-
线程池刚创建时,里面没有一个线程。任务队列是作为参数传进来的。不过,就算队列里面有任务,线程池也不会马上执行它们。
-
当调用 execute() 方法添加一个任务时,线程池会做如下判断:
- 如果正在运行的线程数量小于 corePoolSize,那么马上创建线程运行这个任务;
- 如果正在运行的线程数量大于或等于 corePoolSize,那么将这个任务放入队列;
- 如果这时候队列满了,而且正在运行的线程数量小于 maximumPoolSize,那么还是要创建非核心线程立刻运行这个任务;
- 如果队列满了,而且正在运行的线程数量大于或等于 maximumPoolSize,那么线程池会抛出异常RejectExecutionException。
-
当一个线程完成任务时,它会从队列中取下一个任务来执行。
-
当一个线程无事可做,超过一定的时间(keepAliveTime)时,线程池会判断,如果当前运行的线程数大于 corePoolSize,那么这个线程就被停掉。所以线程池的所有任务完成后,它最终会收缩到 corePoolSize 的大小。
线程池的使用
execute
ExecutorService.execute(Runnable runable);
submit
FutureTask task = ExecutorService.submit(Runnable runnable);
FutureTask task = ExecutorService.submit(Runnable runnable,T Result);
FutureTask task = ExecutorService.submit(Callable callable);
可以看出submit开启的是有返回结果的任务,会返回一个FutureTask对象,这样就能通过get()方法得到结果。submit最终调用的也是execute(Runnable runable)
,submit只是将Callable对象或Runnable封装成一个FutureTask对象,因为FutureTask是个Runnable,所以可以在execute中执行。
Spring Boot 中线程池的使用
@Configuration
public class TreadPoolConfig {
/**
* 消费队列线程
* @return
*/
@Bean(value = "consumerQueueThreadPool")
public ExecutorService buildConsumerQueueThreadPool(){
ThreadFactory namedThreadFactory = new ThreadFactoryBuilder()
.setNameFormat("consumer-queue-thread-%d").build();
ExecutorService pool = new ThreadPoolExecutor(5, 5, 0L, TimeUnit.MILLISECONDS,
new ArrayBlockingQueue<Runnable>(5),namedThreadFactory,new ThreadPoolExecutor.AbortPolicy());
return pool ;
}
}
使用时:
@Resource(name = "consumerQueueThreadPool")
private ExecutorService consumerQueueThreadPool;
@Override
public void execute() {
//消费队列
for (int i = 0; i < 5; i++) {
consumerQueueThreadPool.execute(new ConsumerQueueThread());
}
}