场景
Java中创建线程的方式有三种
1、通过继承Thread类来创建线程
定义一个线程类使其继承Thread类,并重写其中的run方法,run方法内部就是线程要完成的任务,
因此run方法也被称为执行体,使用start方法来启动线程。
2、通过实现Runanle接口来创建线程
首先定义Runnable接口,并重写Runnable接口的run方法,run方法的方法体同样是该线程的线程执行体。
3、通过Callable 和 Future来创建线程
Runnable接口执行的是独立的任务,Runnable接口不会产生任何返回值,
如果希望在任务完成之后能够返回一个值的话,可以实现Callable接口。
注:
博客:
霸道流氓气质的博客_CSDN博客-C#,架构之路,SpringBoot领域博主
实现
Java创建线程的三种方式
1、通过继承Thread类来创建线程
public class TJavaThread extends Thread{
static int count;
@Override
public synchronized void run() {
for(int i =0;i<10000;i++){
count++;
}
}
public static void main(String[] args) {
TJavaThread tJavaThread = new TJavaThread();
tJavaThread.start();
try {
//使用线程的join方法,用来等待线程的执行结束,如果不加join方法,它就不会等待tJavaThread的执行完毕。
tJavaThread.join();
} catch (InterruptedException e) {
e.printStackTrace();
}
System.out.println(count);
}
}
2、通过实现Runanle接口来创建线程
public class TJavaThreadRunable implements Runnable{
static int count;
@Override
public synchronized void run() {
for(int i=0;i<10000;i++){
count++;
}
}
public static void main(String[] args) throws InterruptedException {
Thread thread = new Thread(new TJavaThreadRunable());
thread.start();
thread.join();
System.out.println(count);
}
}
3、通过Callable 和 Future来创建线程
public class TJavaThreadCallable implements Callable {
static int count;
public TJavaThreadCallable(int count){
this.count = count;
}
@Override
public Object call(){
return count;
}
public static void main(String[] args) throws ExecutionException, InterruptedException {
FutureTask<Integer> task = new FutureTask((Callable<Integer>)()->{
for (int i =0;i<1000;i++){
count++;
}
return count;
});
Thread thread = new Thread(task);
thread.start();
Integer total = task.get();
System.out.println(total);
}
}
Java使用线程池来创建线程
Executor虽然不是传统线程创建的方式之一,但是它却成为了创建线程的替代者,使用线程池的好处
1、利用线程池能够复用线程、控制最大并发数
2、实现任务线程队列缓存策略和拒绝机制
3、实现某些与时间相关的功能,如定时执行、周期执行等。
4、隔离线程环境。比如两个服务在同一台服务器上,分别开启两个线程池,避免各服务线程相互影响。
ExecutorService是Executor的默认实现,也是Executor的扩展接口,
ThreadPoolExecutor类提供了线程池的扩展实现。Executors类为这些Executor提供了方便的工厂方法。
ExecutorService创建线程的几种方式:
1、CacheedThreadPool
创建一个可缓存的线程池,调用execute 将重用以前构造的线程(如果线程可用)。
如果现有线程没有可用的,则创建一个新线程并添加到池中。
终止并从缓存中移除那些已有 60 秒钟未被使用的线程。CacheThreadPool会为每一个任务都创建一个线程
private static void CacheedThreadPoolTest() {
ExecutorService service = Executors.newCachedThreadPool();
for (int i = 0; i < 5; i++) {
//submit()有返回值,而execute()没有
//submit()可以进行Exception处理
service.execute(() -> {
int count = 0;
for (int j = 0; j < 10000; j++) {
count++;
}
System.out.println(count);
});
}
service.shutdown();
}
2、FixedThreadPool
使你可以使用有限的线程集来启动多线程,
可以一次性的预先执行高昂的线程分配,因此也就可以限制线程的数量。
这样可以节省时间,因为你不必为每个任务都固定的付出创建线程的开销。
private static void FixedThreadPoolTest() {
ExecutorService service = Executors.newFixedThreadPool(5);
for (int i = 0; i < 5; i++) {
int k = i;
service.execute(() -> {
int count = 0;
for (int j = 0; j < 10000; j++) {
count++;
}
System.out.println(count);
System.out.println(Thread.currentThread().getId() + "--" + k);
});
}
//ExecutorService 对象是使用静态的Executors创建的,这个方法可以确定Executor类型。对shutdown的调用可以防止新任务提交给ExecutorService,
//这个线程在Executor中所有任务完成后退出、
service.shutdown();
}
3、SingleThreadExecutor
就是线程数量为1的FixedThreadPool,如果向SingleThreadPool一次性提交了多个任务,
那么这些任务将会排队。每个任务都会在下一个任务开始前结束,所有的任务都将使用相同的线程。
SingleThreadPool会序列化所有提交给他的任务,并会维护它自己的悬挂队列。
从输出结果来看,任务都是挨着进行的。为任务分配五个线程,但是这五个线程不像上面有换进换出的效果,
它每次都会先执行完自己的那个线程,然后余下的线程继续走完这条线程的执行路径。
可以使用SingleThreadExecutor来确保任意时刻都只有唯一一个任务在运行。
private static void SingleThreadExecutorTest() {
ExecutorService service = Executors.newSingleThreadExecutor();
for (int i = 0; i < 5; i++) {
int k = i;
service.execute(() -> {
int count = 0;
for (int j = 0; j < 10000; j++) {
count++;
}
System.out.println(count);
System.out.println(Thread.currentThread().getId() + "--" + k);
});
}
//ExecutorService 对象是使用静态的Executors创建的,这个方法可以确定Executor类型。对shutdown的调用可以防止新任务提交给ExecutorService,
//这个线程在Executor中所有任务完成后退出、
service.shutdown();
}
4、ScheduledThreadPool
常用于需要延迟执行或周期循环执行任务的场景
schedule()方法可以用来延迟任务的执行
运行下面任务,则先输出时间,延迟2秒后才执行
private static void ScheduledThreadPoolTestSchedule() {
System.out.println("当前时间:" + System.currentTimeMillis());
ScheduledExecutorService service = Executors.newScheduledThreadPool(5);
service.schedule(
() ->
System.out.println("开始执行:" + System.currentTimeMillis()), 2, TimeUnit.SECONDS);
service.shutdown();
}
scheduleAtFixedRate()方法 固定频率执行方法
private static void ScheduledThreadPoolTestFixedRate(){
System.out.println("当前时间:" + new SimpleDateFormat("yyyy-MM-dd HH:mm:ss").format(System.currentTimeMillis()));
ScheduledThreadPoolExecutor executor = new ScheduledThreadPoolExecutor(5);
executor.scheduleAtFixedRate(new Runnable() {
@Override
public void run() {
System.out.println("开始执行:" + new SimpleDateFormat("yyyy-MM-dd HH:mm:ss").format(System.currentTimeMillis()));
try {
Thread.sleep(1000);
} catch (InterruptedException e) {
throw new RuntimeException(e);
}
}
},2,1,TimeUnit.SECONDS);
}
scheduleWithFixedDelay 固定的间隔时间执行任务
private static void ScheduledThreadPoolTestFixedDelay(){
System.out.println("当前时间:" + new SimpleDateFormat("yyyy-MM-dd HH:mm:ss").format(System.currentTimeMillis()));
ScheduledThreadPoolExecutor executor = new ScheduledThreadPoolExecutor(5);
executor.scheduleWithFixedDelay(new Runnable() {
@Override
public void run() {
System.out.println("开始执行:" + new SimpleDateFormat("yyyy-MM-dd HH:mm:ss").format(System.currentTimeMillis()));
try {
Thread.sleep(1000);
} catch (InterruptedException e) {
throw new RuntimeException(e);
}
}
},2,1,TimeUnit.SECONDS);
}
scheduleAtFixedRate与scheduleWithFixedDelay区别?
scheduleAtFixedRate的下一次执行时间是上一次执行时间+间隔时间
scheduleWithFixedDelay下一次执行时间是上一次执行时间结束时系统时间+间隔时间
scheduleAtFixedRate执行结果
scheduleWithFixedDelay执行结果
5、newSingleThreadScheduledExecutor:创建⼀个单线程的可以执⾏延迟任务的线程池;
private static void SingleThreadScheduleTest(){
System.out.println("当前时间:" + new SimpleDateFormat("yyyy-MM-dd HH:mm:ss").format(System.currentTimeMillis()));
ScheduledExecutorService executorService = Executors.newSingleThreadScheduledExecutor();
executorService.scheduleAtFixedRate(new Runnable() {
@Override
public void run() {
System.out.println("开始执行:" + new SimpleDateFormat("yyyy-MM-dd HH:mm:ss").format(System.currentTimeMillis()));
}
},2,1,TimeUnit.SECONDS);
}
6、newWorkStealingPool:创建⼀个抢占式执⾏的线程池(任务执⾏顺序不确定)
又称任务窃取线程池,可以传入线程的数量,不传入,则默认使用当前计算机中可用的cpu数量,
实际的线程数可能会动态增长和收缩,不能保证提交任务的执行顺序。
以下为设置线程数为4
private static void WorkStealingPoolTest(){
ExecutorService executorService = Executors.newWorkStealingPool(4);
for (int i =0;i<10;i++){
executorService.submit(new Runnable() {
@Override
public void run() {
String name = Thread.currentThread().getName();
System.out.println(name+"开始执行");
try {
Thread.sleep(1000);
} catch (InterruptedException e) {
e.printStackTrace();
}
System.out.println(name+"执行结束");
}
});
}
System.out.println("cpu核心数:"+ Runtime.getRuntime().availableProcessors());
try {
Thread.sleep(4000);
} catch (InterruptedException e) {
e.printStackTrace();
}
}
运行效果
这里的cpu核心数为8,如果不设置线程数则直接
ExecutorService executorService = Executors.newWorkStealingPool();
此时执行结果
线程池不允许使用Executors去创建,而是通过ThreadPoolExecutor的方式
《阿里巴巴JAVA开发手册》有这样一条强制规定:
线程池不允许使用Executors去创建,而应该通过ThreadPoolExecutor方式,这样处理方式更加明确线程池运行规则,
规避资源耗尽风险。
说明: Executors 返回的线程池对象的弊端如下:
(1) FixedThreadPool 和 SingleThreadPool :
允许的请求队列的长度可能会堆积大量的请求,从而导致 OOM。
(2) CachedThreadPool :
允许的创建线程数量为 Integer.MAX_VALUE,可能会创建大量的线程,从而导致 OOM。
ThreadPoolExecutor参数说明
corePoolSize - 线程池核心线程数量
maximumPoolSize - 线程池最大数量
keepAliveTime - 空闲线程存活时间
unit - 时间单位
workQuene - 线程池中所使用的缓冲队列
handler - 线程池对拒绝任务的处理策略
ThreadPoolExecutor执行流程
示例代码
ThreadPoolExecutor threadPoolExecutor = new ThreadPoolExecutor(2, 4, 10, TimeUnit.SECONDS, new ArrayBlockingQueue<>(3), new ThreadPoolExecutor.AbortPolicy());
for(int i =1;i<=7;i++){
String task = "task:"+i;
threadPoolExecutor.execute(new ThreadPoolTask(task));
try {
Thread.sleep(500);
} catch (InterruptedException e) {
e.printStackTrace();
}
}
任务具体实现类
static class ThreadPoolTask implements Runnable{
private String taskName;
ThreadPoolTask(String task){
this.taskName = task;
}
@Override
public void run() {
System.out.println("启动:"+taskName);
try {
Thread.sleep(10000);
} catch (InterruptedException e) {
e.printStackTrace();
}
}
}
这里假设每个任务需要执行10秒,每隔0.5秒执行一个任务。
当循环数为7,即不大于最大线程数+队列大小时,执行结果如下
启动:task:1
启动:task:2
启动:task:6
启动:task:7
启动:task:3
启动:task:4
启动:task:5
执行结果分析:
提交1、2两个任务,判断小于corePoolSize,会为每一个任务创建一个线程
提交3、4、5三个任务时,判断正在执行的任务数量为2,且每个任务执行时间为10s,所以会将这三个放入到workQueue中等待执行
提交6、7两个任务时,因为workQuene队列的大小为3,此时workQueue队列中存储的任务数量满了,
会判断当前线程池中正在执行的任务是否小于maximumPoolSize,这里是4,
如果小于4则创建新的线程来执行任务6和7,此时7个任务都提交完毕,那么等待任务3、4、5会在前面每个10s的任务执行完之后执行。
当修改循环数为10
启动:task:1
启动:task:2
启动:task:6
启动:task:7
Exception in thread "main" java.util.concurrent.RejectedExecutionException: Task com.ruoyi.demo.thread.threadpool.ThreadPool$ThreadPoolTask@deb6432 rejected from java.util.concurrent.ThreadPoolExecutor@28ba21f3[Running, pool size = 4, active threads = 4, queued tasks = 3, completed tasks = 0]
at java.util.concurrent.ThreadPoolExecutor$AbortPolicy.rejectedExecution(ThreadPoolExecutor.java:2063)
at java.util.concurrent.ThreadPoolExecutor.reject(ThreadPoolExecutor.java:830)
at java.util.concurrent.ThreadPoolExecutor.execute(ThreadPoolExecutor.java:1379)
at com.ruoyi.demo.thread.threadpool.ThreadPool.ThreadPoolExecutorTest(ThreadPool.java:230)
at com.ruoyi.demo.thread.threadpool.ThreadPool.main(ThreadPool.java:280)
启动:task:3
启动:task:4
启动:task:5
执行结果分析,当执行第8个任务时,判断执行线程总数大于最大线程数+队列大小,直接执行拒绝策略,同理任务9和10也是如此。
这里的拒绝策略是AbortPolicy
拒绝策略
1、AbortPolicy - 丢弃任务并抛出RejectedExecutionException异常
2、CallerRunsPolicy - 将被拒绝的任务添加到线程池正在运行的线程中去执行
将上面修改之后的执行结果
//启动:task:1
//启动:task:2
//启动:task:6
//启动:task:7
//启动:task:8
//启动:task:3
//启动:task:4
//启动:task:5
//启动:task:9
//启动:task:10
3、DiscardPolicy - 丢弃任务,但是不抛出异常
//此时执行结果如下
//启动:task:1
//启动:task:2
//启动:task:6
//启动:task:7
//启动:task:3
//启动:task:4
//启动:task:5
4、DiscardOldestPolicy - 丢弃队列最前面的任务,然后重新尝试执行任务
//此时执行结果如下
//启动:task:1
//启动:task:2
//启动:task:6
//启动:task:7
//启动:task:8
//启动:task:9
//启动:task:10