在上文中,线程共有6种状态,下面主要熟悉线程的创建,即由NEW到RUNNABLE的过程。
java创建线程的方式一般有四种,而项目中,一般是使用线程池,所以重点在线程池的使用。
1. 继承Thread
2. 实现Runnable接口
3. 使用Callable和Future
4. 线程池
public class MyThread extends Thread{
@Override
public void run() {
//重写run方法
for (int i = 0; i < 5; i++) {
System.out.println("i: "+i);
}
}
public static void main(String[] args) {
new MyThread().run();
new MyThread().start();
}
/**
* 这说明一下run()方法和start()方法的区别
*
* run()
* Runnable接口中的抽象方法,而Thread实现了Runnable接口,需要重写run()方法
* 而在Thread类重写的run()方法的源码中,只是调用了Runnable接口的run()方法
* 如果直接调用run方法,并不会启动新线程,程序中只有当前线程线程,
* 程序还是顺序执行,等待run方法体执行完毕后才可继续执行下面的代码,没有达到多线程的目的
* start()方法
* 启动新线程,处于就绪(可运行)状态,并没有运行,一旦得到cpu时间片,
* 就开始执行相应线程的run()方法,这里run()称为线程体,它包含了要执行的这个线程的内容,
* run()方法运行结束,此线程随即终止。
* start()无需等待run()执行完毕,即可继续执行下面的代码,进行了线程切换
*
*/
}
public class MyThread implements Runnable {
@Override
public void run() {
//重写run方法
for (int i = 0; i < 5; i++) {
System.out.println("i: "+i);
}
}
public static void main(String[] args) {
//创建Runnable的实例
MyThread myThread = new MyThread();
//该实例作为Thread的target,创建后这个thread对象才是真正的线程对象
Thread thread = new Thread(myThread);
thread.start();
}
}
不常用,有兴趣的可以查看:Java并发编程:Callable、Future和FutureTask。
区别:上面两种方法缺点是无法获取线程执行结果,而Callable和Future可以获取执行结果
- 降低资源消耗。通过重复利用已创建的线程降低线程创建和销毁造成的消耗。
- 提高响应速度。当任务到达时,任务可以不需要的等到线程创建就能立即执行。
- 提高线程的可管理性。线程是稀缺资源,如果无限制的创建,会消耗系统资源,还会降低系统的稳定性。
但是要做到合理的利用线程池,必须对其原理了如指掌。java通过Executors来创建线程池,它提供了5种线程池:
newFixedThreadPool:固定线程池大小,可控制线程最大并发数,超出的线程会在队列中等待
newSingleThreadExecutor:创建一个单线程化的线程池,它只会用唯一的工作线程来执行任务,保证所有任务按照指定顺序(FIFO, LIFO, 优先级)执行
newCachedThreadPool:可缓存线程池,如果线程池长度超过处理需要,可回收空闲线程,若无需回收,则新建线程
newScheduledThreadPool:创建一个定长线程池,支持定时(scheduleWithFixedDelay()函数的initdelay参数)及周期(delay 参数)任务执行
newWorkStealingPool:创建一个单线程化的支持定时的线程池,可以用一个线程周期性执行任务(比如周期7天,一次任务才用1小时,使用多线程就会浪费资源)
在创建时,java规范插件提示如下:
线程池不允许使用Executors创建,最好手动创建。
查看它的源码:
public ThreadPoolExecutor(int corePoolSize,
int maximumPoolSize,
long keepAliveTime,
TimeUnit unit,
BlockingQueue workQueue,
ThreadFactory threadFactory,
RejectedExecutionHandler handler)
- corePoolSize 线程池核心池的大小
- maximumPoolSize 线程池的最大线程数
- keepAliveTime 当线程数大于核心时,此为终止前多余的空闲线程等待新任务的最长时间
- unit keepAliveTime 的时间单位
- workQueue 用来储存等待执行任务的队列
- threadFactory 线程工厂
- handler 拒绝策略
在创建了线程池后,线程池中并没有任何线程(默认),等到有任务来才创建线程去执行任务,除非调用了prestartAllCoreThreads()或者prestartCoreThread()方法
当创建的线程数等于 corePoolSize 时,会加入阻塞队列。当队列满时,会创建线程执行任务直到线程池中的数量等于maximumPoolSize
ArrayBlockingQueue:是一个基于数组结构的有界阻塞队列,此队列按 FIFO(先进先出)原则对元素进行排序。
LinkedBlockingQueue:一个基于链表结构的无界阻塞队列,此队列按FIFO排序元素,吞吐量通常高于ArrayBlockingQueue。静态工厂方法Executors.newFixedThreadPool()使用了这个队列。
SynchronousQueue:默认。一个不存储元素的阻塞队列。每个线程的插入必须等另一个线程的移除,否则插入操作一直处于阻塞状态,吞吐量通常要高于LinkedBlockingQueue,静态工厂方法Executors.newCachedThreadPool使用了这个队列。
PriorityBlockingQueue:一个支持优先级排序的无界阻塞队列。
DelayQueue: 一个使用优先级队列实现的无界阻塞队列。
ThreadPoolExecutor.AbortPolicy: 丢弃任务并抛出RejectedExecutionException异常(默认)
ThreadPoolExecutor.DiscardPolicy:丢弃任务,但是不抛出异常
ThreadPoolExecutor.DiscardOldestPolicy:丢弃队列最前面的任务,然后重新尝试执行任务(重复此过程)
ThreadPoolExecutor.CallerRunsPolicy:由调用线程处理该任务
1. maven项目导入依赖。主要用于ThreadFactory命名线程名称,帮助日志打印,排查bug。
com.google.guava
guava
19.0
2. 连接池配置类:ThreadPoolConfig
import com.google.common.util.concurrent.ThreadFactoryBuilder;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;
import java.util.concurrent.*;
@Configuration
public class ThreadPoolConfig {
@Bean(value = "myThreadPool")
public ExecutorService buildMyThreadPool(){
// 例如,"rpc-pool-%d"会产生像线程名称 "rpc-pool-0","rpc-pool-1","rpc-pool-2"
ThreadFactory threadFactory = new ThreadFactoryBuilder().setNameFormat("my-thread-%d").build();
ExecutorService executorService = new ThreadPoolExecutor(
15
,30
,0
,TimeUnit.MILLISECONDS
,new ArrayBlockingQueue<>(1000)
,threadFactory
,new ThreadPoolExecutor.CallerRunsPolicy()
);
return executorService;
}
}
3. 注入并使用
@RunWith(SpringRunner.class)
@SpringBootTest(classes = Application.class)
public class Test {
@Resource(name = "myThreadPool")
private ExecutorService myThreadPool;
/**
* 简单执行任务
*/
@org.junit.Test
public void test(){
for (int i = 0; i < 10; i++) {
final int index = i;
myThreadPool.execute(new Runnable() {
@Override
public void run() {
System.out.println("index: "+index);
}
});
}
myThreadPool.shutdown();
}
}
篇幅原因,用法如上。配置具体详解请看下文: