文章目录
创建线程的四种方式
- 继承Thread类
- 实现Runnable接口
- 实现Callable接口(有返回值)
- 线程池(常用)
一、继承Thread类
- 继承Thread类
- 重写run()方法,将所要完成的任务代码写进run()方法中
- 调用该对象的start()方法,即可开启线程;
public class Thread01 extends Thread {
@Override
public void run() {
System.out.println(Thread.currentThread().getName()+"--》执行run方法");
}
public static void main(String[] args) {
System.out.println("主线程开始");
Thread01 thread01 = new Thread01();
thread01.setName("继承thread");
thread01.start();
System.out.println("主线程结束");
}
}
// 主线程开始
// 主线程结束
// 继承thread--》执行run方法
二、实现Runnable接口
- 实现Runnable接口
- 重写run()方法,将所要完成的任务代码写进run()方法中
- 启动线程,需要首先实例化一个 Thread,并传入自己的线程实例
- 实例化的thread调用start方法
public class Thread02 implements Runnable {
@Override
public void run() {
System.out.println(Thread.currentThread().getName()+"--》执行run方法");
}
public static void main(String[] args) {
System.out.println("主线程开始");
Thread thread = new Thread(new Thread02());
thread.setName("实现runnable");
thread.start();
System.out.println("主线程结束");
}
}
// 主线程开始
// 主线程结束
// 实现runnable--》执行run方法
三、实现Callable接口(有返回值)
- 创建一个类并实现Callable接口(有返回值)
- 重写call()方法,将所要完成的任务的代码写进call()方法中,有返回值,并且可以抛出异常
- 需要创建Future接口的实现类的对象,即FutureTask类的对象,调用该对象的get()方法可获取call()方法的返回值
- 使用Thread类的有参构造器创建对象,将FutureTask类的对象当做参数传进去,然后调用start()方法开启并运行该线程。
public class Thread03 implements Callable<String> {
@Override
public String call() throws Exception {
System.out.println(Thread.currentThread().getName()+"--》执行run方法");
return Thread.currentThread().getName();
}
public static void main(String[] args) throws ExecutionException, InterruptedException {
System.out.println("主线程开始");
FutureTask<String> task = new FutureTask<>(new Thread03());
Thread thread = new Thread(task);
thread.setName("实现runnable");
thread.start();
System.out.println("主线程结束");
// get方法会阻塞线程
String str = task.get();
System.out.println("主线程输出-->"+str);
}
}
// 主线程开始
// 主线程结束
// 实现runnable--》执行run方法
// 主线程输出-->实现runnable
四、线程池
4.1:newFixedThreadPool(定长线程池)
newFixedThreadPool:创建一个定长线程池,可控制线程最大并发数,超出的线程会在队列中等待。底层使用的是ThreadPoolExecutor(线程执行池),任务使用LinkedBlockingQueue,核心线程池大小和最大线程树大小均为固定值
// 有固定数量的线程池
public static void test01(){
// new一个有固定数量的线程池,最多只有五个线程在同时执行,其他的会在等待
ExecutorService service = Executors.newFixedThreadPool(5);
System.out.println(service);
for (int i = 0; i < 7; i++) {
service.execute(new Runnable() {
@Override
public void run() {
try {
TimeUnit.SECONDS.sleep(1);
System.out.println(Thread.currentThread().getName());
} catch (InterruptedException e) {
e.printStackTrace();
}
}
});
}
// jdk1.8
service.execute(()->{
try {
TimeUnit.SECONDS.sleep(1);
System.out.println(Thread.currentThread().getName());
} catch (InterruptedException e) {
e.printStackTrace();
}
});
// [Running, pool size = 5, active threads = 5, queued tasks = 2, completed tasks = 0]
// 正在运行 链接池大小为5 活跃的线程为5 等待线程为2 已完成的线程为0
System.out.println(service);
// 停止线程池,以加入线程池的可正常执行
// 将线程状态改为Shutting down
// service.shutdown();
// 线程池处于STOP状态,此时线程池不能接受新的任务,并且会去尝试终止正在执行的任务
// service.shutdownNow();
// [Shutting down, pool size = 5, active threads = 5, queued tasks = 2, completed tasks = 0]
// Shutting down正在停止运行中
System.out.println(service);
System.out.println(service.isTerminated());
System.out.println(service.isShutdown());
try {
TimeUnit.SECONDS.sleep(3);
} catch (InterruptedException e) {
e.printStackTrace();
}
// [Terminated, pool size = 0, active threads = 0, queued tasks = 0, completed tasks = 7]
// 线程池彻底终止
System.out.println(service);
System.out.println(service.isTerminated());
System.out.println(service.isShutdown());
}
4.2:newCachedThreadPool(可缓存线程池)
newCachedThreadPool:创建一个可缓存线程池,如果线程池长度超过处理需要,可灵活回收空闲线程,若无可回收,则新建线程。底层使用的是ThreadPoolExecutor(线程执行池),任务使用SynchronousQueue,核心线程池大小默认为0,最大线程树大小默认为int最大值
// 可缓存线程池
public static void test02(){
// 可缓存线程池,如果线程池长度超过处理需要,可灵活回收空闲线程,若无可回收,则新建线程
ExecutorService service = Executors.newCachedThreadPool();
System.out.println(service);
for (int i = 0; i < 2; i++) {
service.execute(() -> {
try {
TimeUnit.MILLISECONDS.sleep(500);
} catch (InterruptedException e) {
e.printStackTrace();
}
System.out.println(Thread.currentThread().getName());
});
}
System.out.println(service);
// 默认回收时间为60秒
// try {
// TimeUnit.SECONDS.sleep(30);
// } catch (InterruptedException e) {
// e.printStackTrace();
// }
System.out.println(service);
service.shutdown();
// 测试主线程结束后,还能看到子线程的输出
// 用于对比newWorkStealingPool,精灵线程
System.out.println("~~~~~");
}
4.3:newSingleThreadExecutor(单线程化的线程池)
newSingleThreadExecutor:创建一个单线程化的线程池,它只会用唯一的工作线程来执行任务,保证所有任务按照指定顺序(FIFO, LIFO, 优先级)执行。底层使用的是ThreadPoolExecutor(线程执行池),任务使用LinkedBlockingQueue,核心线程池大小和最大线程树大小默认为1
// 单线程化的线程池
public static void test03(){
// 只有单线程,可以保证顺序执行
ExecutorService service = Executors.newSingleThreadExecutor();
for (int i = 0; i < 5; i++) {
service.execute(()->{
System.out.println(Thread.currentThread().getName());
});
}
service.shutdown();
}
4.4:newScheduledThreadPool(定时及周期性任务)
newScheduledThreadPool:创建一个定长线程池,支持定时及周期性任务执行。底层使用的是ScheduledThreadPoolExecutor(定时线程执行池),任务使用DelayedWorkQueue,核心线程池大小为固定值,最大线程树大小默认为int最大值
// 定长线程池,支持定时及周期性任务执行
public static void test04(){
ScheduledExecutorService service = Executors.newScheduledThreadPool(4);
for (int i = 0; i < 10; i++) {
// 要执行的任务、首次任务执行延迟时间、隔多久执行一次、时间单位
service.scheduleAtFixedRate(()->{
System.out.println(Thread.currentThread().getName());
},1,2,TimeUnit.SECONDS);
}
try {
TimeUnit.SECONDS.sleep(20);
} catch (InterruptedException e) {
e.printStackTrace();
}
service.shutdown();
}
4.5:newWorkStealingPool(工作窃取线程池)
newWorkStealingPool:工作窃取线程池,假设ABC三个线程,若C线程执行结束,线程C会从AB中窃取未完成的任务进行工作。使用的是ForkJoinPool,属于精灵线程(守护线程、后台线程)
public class Test04_WorkStealingPool {
public static void main(String[] args) {
// 可运行的线程树
// 16 可以同时运行16个线程
System.out.println(Runtime.getRuntime().availableProcessors());
ExecutorService service = Executors.newWorkStealingPool();
service.execute(new R(1));
service.execute(new R(2));
service.execute(new R(2));
service.execute(new R(2));
service.execute(new R(2));
service.execute(new R(2));
service.execute(new R(2));
service.execute(new R(2));
service.execute(new R(2));
service.execute(new R(2));
service.execute(new R(2));
service.execute(new R(2));
service.execute(new R(2));
service.execute(new R(2));
service.execute(new R(2));
service.execute(new R(2));
service.execute(new R(2));
// 其他线程为非精灵线程,主线程
// 由于产生的是精灵线程(守护线程、后台线程),主线程不阻塞的话,看不到输出
// 阻塞主线程
// try {
// System.in.read();
// } catch (IOException e) {
// e.printStackTrace();
// }
try {
TimeUnit.SECONDS.sleep(5);
} catch (InterruptedException e) {
e.printStackTrace();
}
service.shutdown();
}
// 定义一个类实现Runnable
static class R implements Runnable{
int time;
public R(int time) {
this.time = time;
}
@Override
public void run() {
try {
TimeUnit.SECONDS.sleep(time);
} catch (InterruptedException e) {
e.printStackTrace();
}
System.out.println(Thread.currentThread().getName());
}
}
}
4.6:ForkJoinPool
ForkJoinPool: Fork:分,Join:合,产生的线程由ForkJoinPool管理,采用了分而治之算法,按照自定义的规则,将大任务分为小任务,进行分而治之(递归),适合的是计算密集型的任务(大规模数据运算)
public class Test05_ForkJoinPool {
// 求和数组
static int[] nums = new int[1000000];
// 每一个小任务计算的值不能超过此值
static final int MAX_NUM = 50000;
static Random r = new Random();
static {
for (int i = 0; i < nums.length; i++) {
nums[i] = r.nextInt(100);
}
// stream 求和
// 49478895
// jdk新特性
System.out.println(Arrays.stream(nums).sum());
}
/**
* ForkJoinPool执行的类为ForkJoinTask<?> task,继承RecursiveAction
* 没有返回值
*/
static class AddTask extends RecursiveAction {
int start;
int end;
public AddTask(int start, int end) {
this.start = start;
this.end = end;
}
@Override
protected void compute() {
// 需要求和的数值小于等于最大值时才进行求和
if(end-start <= MAX_NUM) {
long sum = 0L;
for(int i=start; i<end; i++) sum += nums[i];
System.out.println("from:" + start + " to:" + end + " = " + sum);
} else {
//否则则进入递归
int middle = start + (end-start)/2;
AddTask subTask1 = new AddTask(start, middle);
AddTask subTask2 = new AddTask(middle, end);
subTask1.fork();
subTask2.fork();
}
}
}
/**
* 无返回值的
*/
public static void test01(){
ForkJoinPool pool = new ForkJoinPool();
// public void execute(ForkJoinTask<?> task) {
// pool.execute(new AddTask(0,nums.length));
AddTask addTask = new AddTask(0, nums.length);
pool.execute(addTask);
// ForkJoinPool属于精灵线程所以需要阻塞,要不看不到结果
try {
System.in.read();
} catch (IOException e) {
e.printStackTrace();
}
}
/**
* ForkJoinPool执行的类为ForkJoinTask<?> task,继承RecursiveTask<Long>
* 有返回值,返回值可以定义
*/
static class AddTask01 extends RecursiveTask<Long> {
int start;
int end;
public AddTask01(int start, int end) {
this.start = start;
this.end = end;
}
@Override
protected Long compute() {
// 需要求和的数值小于等于最大值时才进行求和
if(end-start <= MAX_NUM) {
long sum = 0L;
for(int i=start; i<end; i++) sum += nums[i];
return sum;
} else {
//否则则进入递归
int middle = start + (end-start)/2;
AddTask01 subTask1 = new AddTask01(start, middle);
AddTask01 subTask2 = new AddTask01(middle, end);
subTask1.fork();
subTask2.fork();
// join会阻塞等待放回值
return subTask1.join() + subTask2.join();
}
}
}
/**
* 有返回值的
*/
public static void test02(){
ForkJoinPool pool = new ForkJoinPool();
// public void execute(ForkJoinTask<?> task) {
// pool.execute(new AddTask(0,nums.length));
AddTask01 addTask = new AddTask01(0, nums.length);
pool.execute(addTask);
// ForkJoinPool属于精灵线程所以需要阻塞,要不看不到结果
// join()有阻塞效果
System.out.println(addTask.join());
}
public static void main(String[] args) {
// 无返回值
// test01();
// 有返回值
test02();
}
}
4.7:ThreadPoolExecutor(自定义线程池,建议采用)
全参包含了7个参数(核心线程数、最大线程数、除核心线程外多余空闲线程在终止前等待任务最大时间、时间单位、阻塞队列、线程工厂、拒绝策略)
拒绝策略包含了四种:
1. new ThreadPoolExecutor.AbortPolicy()(默认) 当达到线程界限和队列容量时,再新加线程时直接报错
2. new ThreadPoolExecutor.CallerRunsPolicy() "调用者运行"一种调用机制,该策略既不会抛弃线程也不会报错,而是将某些任务回退到调用者,从而降低新任务流量
3. new DiscardOldestPolicy() 抛弃任务中等待最久的任务,然后将当前任务尝试加入到当前队列尝试再次提交当前任务
4. new ThreadPoolExecutor.DiscardPolicy() 直接丢弃任务,不抛弃线程也不报错,如果允许丢弃任务,则可以选择这种方案
阻塞队列
1. ArrayBlockingQueue :由数组结构组成的有界阻塞队列。
2. LinkedBlockingQueue :由链表结构组成的有界阻塞队列。
3. PriorityBlockingQueue :支持优先级排序的无界阻塞队列。
4. DelayQueue:使用优先级队列实现的无界阻塞队列。
5. SynchronousQueue:不存储元素的阻塞队列。
6. LinkedTransferQueue:由链表结构组成的无界阻塞队列。
7. LinkedBlockingDeque:由链表结构组成的双向阻塞队列。
public class Test06_ThreadPoolExecutorTest {
public static void main(String[] args) {
// 自定义线程池
ThreadPoolExecutor pool = new ThreadPoolExecutor(
// 核心线程数
1,
// 线程池中允许最大线程数
// cpu密集型(类似for循环之类,不操作数据库):服务器核心线程数+1(加一是为了减少频繁切换上下文)
// io密集型(频繁操作数据库):服务器核心线程数/(1-阻塞系数) 阻塞系数一般为0.8~0.9
2,
// 除核心线程数之后多余的空闲线程在终止之前等待新任务的最长时间
1L,
// 时间单位
TimeUnit.SECONDS,
// 阻塞队列
new LinkedBlockingDeque<>(2),
// 默认的线程工厂
Executors.defaultThreadFactory(),
// 当达到了线程界限和队列容量时,新来任务被阻塞时要使用的处理程序
new ThreadPoolExecutor.DiscardPolicy());
// new ThreadPoolExecutor.AbortPolicy()(默认)
/**
* java.util.concurrent.RejectedExecutionException
*/
// 当达到线程界限和队列容量时,再新加线程时直接报错
// new ThreadPoolExecutor.CallerRunsPolicy()
// "调用者运行"一种调用机制,该策略既不会抛弃线程也不会报错,而是将某些任务回退到调用者,从而降低新任务流量
// new DiscardOldestPolicy()
// 抛弃任务中等待最久的任务,然后将当前任务尝试加入到当前队列尝试再次提交当前任务
// new ThreadPoolExecutor.DiscardPolicy()
// 直接丢弃任务,不抛弃线程也不报错,如果允许丢弃任务,则可以选择这种方案
for (int i = 0; i < 6; i++) {
pool.execute(()->{
System.out.println(Thread.currentThread().getName() + "~~~~");
try {
TimeUnit.SECONDS.sleep(2);
} catch (InterruptedException e) {
e.printStackTrace();
}
});
}
pool.shutdown();
}
}
线程状态
新建状态(NEW)--> 就绪状态(RUNNABLE)--> 运行状态(RUNNING)--> 阻塞状态(BLOCKED)--> 线程死亡(DEAD)
- 新建状态(NEW)
当程序使用 new 关键字创建了一个线程之后,该线程就处于新建状态,此时仅由 JVM 为其分配内存,并初始化其成员变量的值 - 就绪状态(RUNNABLE)
当线程对象调用了 start()方法之后,该线程处于就绪状态。Java 虚拟机会为其创建方法调用栈和程序计数器,等待调度运行。 - 运行状态(RUNNING)
如果处于就绪状态的线程获得了 CPU,开始执行 run()方法的线程执行体,则该线程处于运行状
态。 - 阻塞状态(BLOCKED)
阻塞状态是指线程因为某种原因放弃了 cpu 使用权,也即让出了 cpu timeslice,暂时停止运行。直到线程进入可运行(runnable)状态,才有机会再次获得 cpu timeslice 转到运行(running)状态。- 等待阻塞(o.wait->等待对列):运行(running)的线程执行 o.wait()方法,JVM 会把该线程放入等待队列(waitting queue)
中。 - 同步阻塞(lock->锁池):运行(running)的线程在获取对象的同步锁时,若该同步锁被别的线程占用,则 JVM 会把该线程放入锁池(lock pool)中。
- 其他阻塞(sleep/join):运行(running)的线程执行 Thread.sleep(long ms)或 t.join()方法,或者发出了 I/O 请求时,JVM 会把该线程置为阻塞状态。当 sleep()状态超时、join()等待线程终止或者超时、或者 I/O处理完毕时,线程重新转入可运行(runnable)状态。
- 等待阻塞(o.wait->等待对列):运行(running)的线程执行 o.wait()方法,JVM 会把该线程放入等待队列(waitting queue)
- 线程死亡(DEAD)
线程会以下面三种方式结束,结束后就是死亡状态。- 正常结束:run()或 call()方法执行完成,线程正常结束
- 异常结束:线程抛出一个未捕获的 Exception 或 Error。
- 调用 stop:直接调用该线程的 stop()方法来结束该线程—该方法通常容易导致死锁,不推荐使用。
核心线程是否会回收
根据上方源码备注,即使这个核心线程啥也不干(闲置状态),也会保留核心线程的,除非配置了allowCoreThreadTimeOut这个属性。
根据源码显示,allowCoreThreadTimeOut默认为false,没有配置的情况下核心线程是不会被回收的。
// 自定义线程池
ThreadPoolExecutor pool = new ThreadPoolExecutor(
// 核心线程数
1,
// 线程池中允许最大线程数
// cpu密集型(类似for循环之类,不操作数据库):服务器核心线程数+1(加一是为了减少频繁切换上下文)
// io密集型(频繁操作数据库):服务器核心线程数/(1-阻塞系数) 阻塞系数一般为0.8~0.9
2,
// 除核心线程数之后多余的空闲线程在终止之前等待新任务的最长时间
1L,
// 时间单位
TimeUnit.SECONDS,
// 阻塞队列
new LinkedBlockingDeque<>(2),
// 默认的线程工厂
Executors.defaultThreadFactory(),
// 当达到了线程界限和队列容量时,新来任务被阻塞时要使用的处理程序
new ThreadPoolExecutor.DiscardPolicy());
// 是否允许核心线程超时
pool.allowCoreThreadTimeOut(true);
当把allowCoreThreadTimeOut设置为true时,则核心线程在空闲超时后也会被回收。