了解多线程的意义和使用
什么是线程
并发与高并发
客户端向Tomcat发送请求,在Tomcat中存在多个个并发数,这个并发数由什么决定的。
并发:单位时间内能够同时处理的请求数。
默认情况下Tomcat可以支持的最大请求数是150,也就是同时支持150个并发。
影响服务器吞吐量的因素
硬件
CPU,内存,磁盘,网络
软件
最大化的利用硬件资源
线程数量、JVM内存分配大小、网络通信机制(BIO、NIO、AIO)、磁盘IO
线程数量如何提升服务端的并发数量
并发和并行
并行:指两个或多个事件在同一时刻发生。多核CPU,可以同时处理多个线程。
并发:指两个或多个事件在同一时间间隔内发生。单核CPU在一段时间内可以给通过时间片的切换完成对多线程的处理。
多线程的特点
- 异步
- 并行
多线程:是对cpu剩余劳动力的压榨,是一种技术。
异步:强调的是非阻塞编程,是一种编程模式(pattern),主要解决UI响应被阻塞的问题,可借助线程技术或者硬件本身的计算能力解决。
并行:同样是对CPU剩余劳动力的压榨,但是基于多线程技术,它强调的是高效完成计算任务,而不是并发量。
举例:有两辆车,需要从A开到B
- 阻塞式编程:先开回去一辆,再回来开另一辆。
- 传统异步变成:将一辆车快递,开一辆车回去。注意,快递公司派件(回调)时我不一定已经开到B,如果需要本人签收,会比较麻烦。–通过这种回调进行异步变成的方法,没有办法编写符合逻辑思维顺序的代码。
- 基于多线程的异步编程:获得瞬间移动的超能力(CPU算力提升)以毫秒级的速度在两辆车之间进行切换驾驶。其中一辆车(主线程)上友车载电话,可以处理其他事情。–期间频繁的上下文切换,会造成额外损耗,造成反应能力比较差,只能开到60迈。
- 并行变成:获得分身能力。两个人驾驶两辆车。充分发挥CPU的功能,没有额外上下文切换。
Java中的线程
public class RunnableDemo implements Runnable{
@Override
public void run() {
Thread.sleep(1000);
System.out.println("come on baby");
}
public static void main(String[] args) {
RunnableDemo runnableDemo = new RunnableDemo();
Thread thread = new Thread(runnableDemo);
thread.start();
System.out.println("hello");
}
}
- Thread(类似Runnable)
- Callable/feture(带返回值)
public class CallableDemo implements Callable<String> {
//线程的控制是由操作系统控制,不由Java程序控制
@Override
public String call() throws Exception {
System.out.println("come on baby!");
Thread.sleep(3000);
return "SUCCESS";
}
public static void main(String[] args) throws ExecutionException, InterruptedException {
ExecutorService executorService = Executors.newFixedThreadPool(1);
CallableDemo callableDemo = new CallableDemo();
Future<String> future = executorService.submit(callableDemo);
System.out.println(future.get()); //线程阻塞
System.out.println("hello");
}
多线程使用情景
- 网络请求分发
- 文件导入
- 短信发送
注册时发送邮件
@postMapping("/sms/user")
public String register(User user) {
long start = System.currentTimeMillis();
userService.insert(user);
//异步
new Thread(()->{
smsClient.sendSms("");
}).start()
long end = System.currentTimeMillis();
return "SUCCESS" + (end - start);
}
在实际开发的时候使用线程池对线程进行管理。
ExecutorService executorService = Executors.newFixedThreadPool(10);
@postMapping("/sms/user")
public String register(User user) {
long start = System.currentTimeMillis();
userService.insert(user);
//异步
executorService.submit(new Runnable(){
@Override
public void run(){
smsClient.sendSms("");
}
})
long end = System.currentTimeMillis();
return "SUCCESS" + (end - start);
}
线程的基础
线程的生命周期
阻塞:
WAITING
TIME_WAITING
BLOCKED
IO阻塞
Java的线程状态:
- 初始(NEW):新创建一个对象,但是还没有调用start()方法。
- 运行(RUNNABLE):Java线程中准备就绪(ready)和运行中(running)两种状态统称为“运行”。线程对象创建后,其他线程(比如main线程)调用了该对象的start()方法。该状态的线程位于可运行线程池中,等待被线程调度选中,获取CPU的使用权,此时处于就绪状态(ready)。就绪状态的线程在获得CPU时间片后变成运行中状态(running)。
- 阻塞(BLOCKED):标识线程阻塞与锁。
- 等待(WAITING):进入该状态的线程需要等待其他线程做出一些特定动作(通知或终端)。
- 超时等待(TIMED_WAITING):该状态与WAITING,它可以在指定的时间后自行返回。
- 终止(TERMINATED):表示线程已经执行完毕。
操作系统中的线程去除new和terminated状态,一个线程真实存在的状态只有:
ready:表示线程已经创建,正在等待系统调度分配CPU使用权。
running:表示线程获得了CPU使用权,正在进行运算。
waiting:便是线程等待(挂起),让出CPU资源给其他线程使用。
加上新建状态和死亡状态一共5种。
线程的启动
new Thread().start(); //启动一个线程
Thread t1 = new Thread();
t1.run(); //调用实例方法
实现原理:
首先Java调用Thread的start()方法,在start()方法中调用了JVM的start0()方法。jvm根据系统环境(例如:windows平台)调用os(操作系统)的创建线程方法create_thread然后调用线程:start_thread。
os中的线程调用jvm中的run方法:thread.run获取需要的run,而JVM需要去Java中调用run。
线程的终止
线程的终止:
public class RunnableDemo extends Thread implements Runnable{
@Override
public void run() {
while (!Thread.currentThread().isInterrupted()){
System.out.println("come on baby");
}
}
public static void main(String[] args) {
RunnableDemo runnableDemo = new RunnableDemo();
Thread thread = new Thread(runnableDemo);
thread.start();
System.out.println("hello");
thread.interrupt(); //设置interrupt=true
}
}
interrupt()的使用
但凡是让线程阻塞的机制,都抛出InterruptException异常
interrupt()方法并不像for循环语句中使用break语句那样干脆,马上就停止循环。调用interrupt()方法仅仅是在当前线程中打一个停止的标记,并不是真正的停止线程。
也就是说,线程中断并不会立即终止线程,而是通知目标线程,有人希望你终止。至于目标线程收到通知后会如何处理,则完全由目标线程自行决定(决定语句写在catch(InterruptException) 的语句中)。
所以Interrupt的作用:
- 设置一个共享变量的值true。
- 唤醒处于阻塞状态下的线程。