深入理解并发编程之多线程入门
文章目录
一、什么是多线程?
1、什么是进程?
进程(Process)是计算机中的程序关于某数据集合上的一次运行活动,是系统进行资源分配和调度的基本单位,是操作系统结构的基础。在早期面向进程设计的计算机结构中,进程是程序的基本执行实体;在当代面向线程设计的计算机结构中,进程是线程的容器。程序是指令、数据及其组织形式的描述,进程是程序的实体。任务管理器里面我们看到的就是进程。
2、什么是线程?
线程是操作系统能够进行运算调度的最小单位,被包含在进程之中,是进程中的实际运作单位。一条线程指的是进程中一个单一顺序的控制流,一个进程中可以并发多个线程,每条线程并行执行不同的任务。
线程有五种状态: 新建,就绪,运行,阻塞以及死亡。
3.什么是守护线程
守护线程,也可称为服务线程,守护线程是程序运行的时候在后台提供一种通用服务的线程,当所有用户线程停止,进程会停掉所有守护线程,退出程序。因此,守护线程的优先级比较低,用于为其他的线程等提供服务。
常见的守护线程例如:Java的垃圾回收线程。
创建一个守护线程的步骤:
//创建一个线程
Thread daemonThread = new Thread();
//设置为守护线程 true:守护线程 false:用户线程
daemonThread.setDaemon(true);
//启动线程
daemonThread.start();
注意事项:
- thread.setDaemon(true)必须在thread.start()之前设置,否则会抛出一个IllegalThreadStateException异常。你不能把正在运行的常规线程设置为守护线程。
- 在Daemon线程中产生的新线程也是Daemon的。
- 守护线程应该永远不去访问固有资源,如文件、数据库,因为它会在任何时候甚至在一个操作的中间发生中断。
3、什么是多线程?
多线程(multithreading),是指从软件或者硬件上实现多个线程并发执行的技术。具有多线程能力的计算机因有硬件支持而能够在同一时间执行多于一个线程,进而提升整体处理性能。具有这种能力的系统包括对称多处理机、多核心处理器以及芯片级多处理或同时多线程处理器。在一个程序中,这些独立运行的程序片段叫作“线程”(Thread),利用它编程的概念就叫作“多线程处理” 。
CPU的一个核心在同一个时刻只能够运行一个线程。
二、为什么使用多线程
多线程的优点
- 使用线程可以把占据时间长的程序中的任务放到后台去处理。
- 更高效率的使用CUP,程序的运行速度可能加快。
- 快速响应给客户端,给用户更加好的体验。
多线程的缺点
- 如果有大量的线程,会影响性能,因为操作系统需要在它们之间切换。
- 更多的线程需要更多的内存空间。
- 线程可能会给程序带来更多“bug”,因此要小心使用。
- 线程的中止需要考虑其对程序运行的影响 。
- 通常块模型数据是在多个线程间共享的,需要防止线程死锁情况的发生。
三、怎么使用多线程
1.如何创建一个线程
- 继承Thread类
public class Thread001 extends Thread {
@Override
public void run() {
System.out.println("我是子线程:" + Thread.currentThread().getName());
}
}
- 使用Runnable
public class Thread002 implements Runnable {
public void run() {
System.out.println("我是子线程:" + Thread.currentThread().getName());
}
}
- 使用Callable
public class MyCallable implements Callable<String> {
public String call() throws Exception {
System.out.println(Thread.currentThread().getName() + "正在异步调用接口发送短信");
return "短信发送成功";
}
}
- 线程池的方式
ExecutorService executorService = Executors.newCachedThreadPool();
executorService.execute(new Runnable() {
public void run() {
System.out.println(Thread.currentThread().getName());
}
});
常见的四种线程池:
//缓存线程池,线程池的大小由jvm决定,如果有空闲线程会回收
Executors.newCachedThreadPool();
//单线程线程池,可保证任务执行的顺序就是任务提交的顺序
Executors.newSingleThreadExecutor();
//固定大小线程池(服务端推荐使用)
Executors.newFixedThreadPool(3);
//周期性线程池,可周期性执行任务
Executors.newScheduledThreadPool(3);
线程池其实是通过实现ThreadPoolExecutor来实现的,有兴趣的可以看一下。
public ThreadPoolExecutor(int corePoolSize,
int maximumPoolSize,
long keepAliveTime,
TimeUnit unit,
BlockingQueue<Runnable> workQueue,
ThreadFactory threadFactory,
RejectedExecutionHandler handler)
2.如何正确的停止一个线程
- 通过thread.stop()停止线程(不推荐),Thread.stop()方法已经被官方移除,我们看代码上面是有删除线的,因为stop()方法是不安全的,它是强制停止的线程,不确定当前线程是否执行结束以及执行到什么程度,这样我们失去了对于代码内部的掌控。
//假设创建了一个耗时比较长的线程
Thread thread = new Thread();
//开始执行线程
thread.start();
//停止线程
thread.stop();
- 通过标志位停止线程,在线程内部增加个标志位,这样运行的线程是比较安全的,因为线程最外层是where标志位判断,当为false的时候停止线程,此时一次完整的线程已经执行结束了才会到下一次的where判断。
public class Thread004 extends Thread {
private volatile boolean flag = true;
@Override
public void run() {
while (flag) {
...线程具体执行内容
}
}
/**
* 修改标志位停止线程
*/
public void stopThrad() {
this.flag = false;
}
public static void main(String[] args) {
Thread004 thread004 = new Thread004();
thread004.start();
try {
Thread.sleep(3000);
} catch (Exception e) {
}
thread004.stopThrad();
}
}
- 通过线程中断停止线程,这种方法也是比较安全的,因为中断一般放在线程的头部或尾部,此时线程也已经正确的执行完了一次循环。
public class Thread005 extends Thread {
public void run() {
try {
while (true) {
//如果线程中断了直接抛出异常停止线程
if (this.isInterrupted()) {
throw new InterruptedException();
}
...线程具体执行内容
}
} catch (InterruptedException e) {
e.printStackTrace();
}
}
public static void main(String[] args) {
Thread thread = new Thread005();
thread.start();
try {
Thread.sleep(3000);
} catch (InterruptedException e) {
}
thread.interrupt();
}
}
总结: 如何正确的结束一个线程的标志其实就是查看线程内部是否是正确的执行完了一次操作,所以只要保证当线程正确的执行完一次操作后直接关闭线程就是正确的结束线程,stop() 执行的时间是不确定的,所以stop()不安全。
四、多线程的应用场景
- 使用多线程实现异步发送短信。
- 使用多线程实现异步的记录日志。
- 大文件的切片上传下载。
- 使用多线程处理一些比较耗时间的业务逻辑,例如大数据量复杂处理,可以分批建立线程处理。
内容来源:蚂蚁课堂