怎么去创建多线程
线程是什么?
线程被称为轻量级进程,是程序执行的最小单位,它是指在程序执行过程中,能够执行代码的一个执行单位。每个程序程序都至少有一个线程,也即是程序本身。
线程的状态
Java语言定义了5种线程状态,在任意一个时间点,一个线程只能有且只有其中一个状态。这5种状态如下:
![image-20220307190549448](https://bearbrick0.oss-cn-qingdao.aliyuncs.com/images/img/image-20220307190549448.png)
- 新建(
New
):创建后尚未启动的线程处于这种状态 - 运行(
Runable
):Runable
包括了操作系统线程状态的Running
和Ready
,也就是处于此状态的线程有可能正在执行,也有可能正在等待着CPU为它分配执行时间。 - 等待(
Wating
):处于这种状态的线程不会被分配CPU执行时间。等待状态又分为无限期等待和有限期等待,处于无限期等待的线程需要被其他线程显示地唤醒,没有设置Timeout
参数的Object.wait()
、没有设置Timeout
参数的Thread.join()
方法都会使线程进入无限期等待状态;有限期等待状态无须等待被其他线程显示地唤醒,在一定时间之后它们会由系统自动唤醒,Thread.sleep()
、设置了Timeout
参数的Object.wait()
、设置了Timeout
参数的Thread.join()
方法都会使线程进入有限期等待状态。 - 阻塞(
Blocked
):线程被阻塞了,“阻塞状态”与”等待状态“的区别是:”阻塞状态“在等待着获取到一个排他锁,这个时间将在另外一个线程放弃这个锁的时候发生;而”等待状态“则是在等待一段时间或者唤醒动作的发生。在程序等待进入同步区域的时候,线程将进入这种状态。 - 结束(
Terminated
):已终止线程的线程状态,线程已经结束执行。
创建单线程的方式
- 继承
Thread
类
public class ThreadTest {
public static void main(String[] args) {
//设置线程名字
Thread.currentThread().setName("main thread");
MyThread myThread = new MyThread();
myThread.setName("子线程:");
//开启线程
myThread.start();
for(int i = 0;i<5;i++){
System.out.println(Thread.currentThread().getName() + i);
}
}
}
class MyThread extends Thread{
//重写run()方法
public void run(){
for(int i = 0;i < 10; i++){
System.out.println(Thread.currentThread().getName() + i);
}
}
}
- 实现
Runnable
接口
public class RunnableTest {
public static void main(String[] args) {
//设置线程名字
Thread.currentThread().setName("main thread:");
Thread thread = new Thread(new MyRunnable());
thread.setName("子线程:");
//开启线程
thread.start();
for(int i = 0; i <5;i++){
System.out.println(Thread.currentThread().getName() + i);
}
}
}
class MyRunnable implements Runnable {
@Override
public void run() {
for (int i = 0; i < 10; i++) {
System.out.println(Thread.currentThread().getName() + i);
}
}
}
- 实现
Callable
接口。相较于实现Runnable
接口的实现,方法可以有返回值,并且抛出异常。
import java.util.concurrent.Callable;
import java.util.concurrent.ExecutionException;
import java.util.concurrent.FutureTask;
//实现Callable接口
public class CallableTest {
public static void main(String[] args) {
//执行Callable 方式,需要FutureTask 实现实现,用于接收运算结果
FutureTask<Integer> futureTask = new FutureTask<Integer>(new MyCallable());
new Thread(futureTask).start();
//接收线程运算后的结果
try {
Integer sum = futureTask.get();
System.out.println(sum);
} catch (InterruptedException e) {
e.printStackTrace();
} catch (ExecutionException e) {
e.printStackTrace();
}
}
}
class MyCallable implements Callable<Integer> {
@Override
public Integer call() throws Exception {
int sum = 0;
for (int i = 0; i < 100; i++) {
sum += i;
}
return sum;
}
}
什么是FutureTask?
FutureTask
可⽤于异步获取执⾏结果或取消执⾏任务的场景。通过传⼊Runnable
或者Callable
的任务给FutureTask
,直接调⽤其run
⽅法或者放⼊线程池执⾏,之后可以在外部通过FutureTask
的get
⽅法异步获取执⾏结果,因此,FutureTask
⾮常适合⽤于耗时的计算,主线程可以在完成⾃⼰的任务后,再去获取结果。另外,FutureTask
还可以确保即使调⽤了多次run
⽅法,它都只会执⾏⼀次Runnable
或者Callable
任务,或者通过cancel
取消FutureTask
的执⾏等。futuretask
可⽤于执⾏多任务、以及避免⾼并发情况下多次创建数据机锁的出现。
Runnable接口 和 Callable接口有什么区别?
-
Runnable
接⼝中的run()
⽅法的返回值是void
,它做的事情只是纯粹地去执⾏run()
⽅法中的代码⽽已; -
Callable
接⼝中的call()
⽅法是有返回值的,是⼀个泛型,和Future、FutureTask
配合可以⽤来获取异步执⾏的结果。
start()⽅法和run()⽅法的区别?
start()
⽅法来启动⼀个线程,真正实现了多线程运⾏。- 如果直接调⽤
run()
,其实就相当于是调⽤了⼀个普通函数⽽已,直接调⽤run()
⽅法必须等待run()
⽅法执⾏完毕才能执⾏下⾯的代码,所以执⾏路径还是只有⼀条,根本就没有线程的特征,所以在多线程执⾏时要使⽤start()
⽅法⽽不是run()
⽅法。
什么是线程池?
很简单,简单看名字就知道是装有线程的池子,我们可以把要执行的多线程交给线程池来处理,和连接池的概念一样,通过维护一定数量的线程池来达到多个线程的复用。 池化技术的思想主要是为了减少每次获取资源的消耗,提⾼对资源的利⽤率。
我们知道不用线程池的话,每个线程都要通过new Thread(xxRunnable).start()
的方式来创建并运行一个线程,线程少的话这不会是问题,而真实环境可能会开启多个线程让系统和程序达到最佳效率,当线程数达到一定数量就会耗尽系统的 CPU 和内存资源,也会造成 GC频繁收集和停顿,因为每次创建和销毁一个线程都是要消耗系统资源的,如果为每个任务都创建线程这无疑是一个很大的性能瓶颈。所以, 线程池中的线程复用极大节省了系统资源,当线程一段时间不再有任务处理时它也会自动销毁,而不会长驻内存。
- 线程池核心类
![image-20220307192625996](https://bearbrick0.oss-cn-qingdao.aliyuncs.com/images/img/image-20220307192625996.png)
- 线程池的执行原理
![image-20220307194146967](https://bearbrick0.oss-cn-qingdao.aliyuncs.com/images/img/image-20220307194146967.png)
提交一个任务到线程池中,线程池的执行顺序如下:
-
当线程数⼩于
corePoolSize
时,创建线程执⾏任务。 -
当线程数⼤于等于
corePoolSize
并且workQueue
没有满时,放⼊workQueue
中 -
线程数⼤于等于
corePoolSize
并且当workQueue
满时,新任务新建线程运⾏,线程总数要⼩于maximumPoolSize
-
当线程总数等于
maximumPoolSize
并且workQueue
满了的时候执⾏handler
的rejectedExecution
。也就是拒绝策略。
- 线程池的创建
一类是通过 ThreadPoolExecutor
创建的线程池;另一个类是通过 Executors
创建的线程池。
![image-20220307200407253](https://bearbrick0.oss-cn-qingdao.aliyuncs.com/images/img/image-20220307200407253.png)
//三大方法、七大参数、4中拒绝策略
ExecutorService threadPool = Executors.newSingleThreadExecutor(); //单个线程池
ExecutorService threadPool = Executors.newFixedThreadPool(5); //创建一个固定大小的线程池
ExecutorService threadPool =Executors.newCachedThreadPool(); //可以伸缩的线程池
在阿里巴巴的《Java开发手册》中强制规定线程池的创建不允许使用Executors
去创建线程,而是通过ThreadPoolExecutor
的方式,这样的处理方式让写的同学,更加明确线程的运行规则,规避资源耗尽的风险
![image-20220307200701855](https://bearbrick0.oss-cn-qingdao.aliyuncs.com/images/img/image-20220307200701855.png)
//7大参数
int corePoolSize 核心线程大小
int maximumPoolSize 最大核心线程池大小
long keepAliveTime 超时了 没有人连接就会释放
TimeUnit unit 超时单位
BlockingQueue<Runnable> workQueue 阻塞队列
ThreadFactory 线程工厂
RejectedExecutionHandler handler 拒绝策略
//4 中拒绝策略
AbortPolicy 超出最大承载数就会抛出异常 RejectedExecutionException
CallerRunsPolicy 那来的去哪里 直接运行这个任务的run方法,但并非是由线程池的线程处理,而是交由任务的调用线程处理
DiscardOldestPolicy 将当前处于等待队列列头的等待任务强行取出,然后再试图将当前被拒绝的任务提交到线程池执行 队列满了,尝试去和 最早地去竞争 也不会抛出异常
DiscardPolicy 直接丢弃任务,不抛出任何异常 队列满了,不会抛出异常
为什么要使用线程池创建多线程?
我们知道不用线程池的话,每个线程都要通过 new Thread(xxRunnable).start()的方 式来创建并运行一个线程,线程少的话这不会是问题,而真实环境可能会开启多个线程让系统和程序达到最佳效率,当线程数达到一定数量就会耗尽系统的 CPU 和 内存资源,也会造成 GC频繁收集和停顿,因为每次创建和销毁一个线程都是要消 耗系统资源的,如果为每个任务都创建线程这无疑是一个很大的性能瓶颈。所以, 线程池中的线程复用极大节省了系统资源,当线程一段时间不再有任务处理时它也会自动销毁,而不会长驻内存。
ThreadPool
优点
-
减少了创建和销毁线程的次数,每个⼯作线程都可以被重复利⽤,可执⾏多个任务
-
可以根据系统的承受能⼒,调整线程池中⼯作线线程的数⽬,防⽌因为因为消耗过多的内存,⽽把服务器累趴下(每个线程需要⼤约
1MB
内存,线程开的越多,消耗的内存也就越⼤,最后死机)-
减少在创建和销毁线程上所花的时间以及系统资源的开销
-
如不使⽤线程池,有可能造成系统创建⼤量线程⽽导致消耗完系统内存
-
使用线程池创建多线程的方式
package org.yun.ThreadPool;
import java.security.AccessController;
import java.util.concurrent.*;
/**
* 线程池的创建
* 线程的三大方法
* 7大参数
* 4中拒绝策略
*/
public class Demo1 {
public static void main(String[] args) {
//线程池的三大方法
// ExecutorService threadPool = Executors.newSingleThreadExecutor(); //单个线程
// ExecutorService threadPool = Executors.newFixedThreadPool(5); //创建一个固定大小的线程池
// ExecutorService threadPool =Executors.newCachedThreadPool(); //可以伸缩的线程池
//自定义线程池!工作 ThreadPoolExecutor
//获取CPU的最大核数
// System.out.println(Runtime.getRuntime().availableProcessors());
ExecutorService threadPool = new ThreadPoolExecutor(2,
5,
3,
TimeUnit.SECONDS,
new LinkedBlockingQueue<>(3),
Executors.defaultThreadFactory(),
//拒绝策略 银行满了,还有人进来,不处理这个人,抛出异常
new ThreadPoolExecutor.DiscardPolicy());
try {
//使用线程池创建线程
//最大承载 : queue+maX
//超出最大承载数就会抛出异常 RejectedExecutionException
for (int i = 1; i <= 9; i++) {
threadPool.execute(() -> {
System.out.println(Thread.currentThread().getName() + " ok");
});
}
} catch (Exception e) {
e.printStackTrace();
} finally {
//线程池用完要关闭
threadPool.shutdown();
}
}
}
本文来自博客园,作者:{BearBrick0},转载请注明原文链接:{https://www.cnblogs.com/bearbrick0}