为什么多线程
1)从计算机底层来说:线程运行比进程的负担要小,另外,多核CPU意味着多个线程可以同时运行,减少了线程上下文切换的开销。
2)从当代互联网发展趋势来说:利用好多线程可以大大提高系统整体的并发能力以及性能。
3)单核时代: 在单核时代多线程主要是为了提高 CPU 和 IO 设备的综合利⽤率。
4)多核时代: 多核时代多线程主要是为了提高 CPU 利⽤率。
多线程的创建
import java.util.concurrent.*;
/**
* @packageName: PACKAGE_NAME
* @user: lixi
* @date: 2023/2/23 17:01
* @email 1831309074@qq.com
* @description: 创建多线程
*/
public class xiancheng {
//继承Thread
static class xiancheng1 extends Thread{
@Override
public void run() {
System.out.println("继承Thread类");
}
}
//重写Runnable
static class xiancheng2 implements Runnable{
@Override
public void run() {
System.out.println("实现Runnable接口");
}
}
//实现Callable
static class xiancheng3 implements Callable{
@Override
public Object call() throws Exception {
System.out.println("实现Callable接口,有返回值");
int sum = 0;
for (int i = 0; i < 10; i++) {
sum += i;
}
return sum;
}
}
public static void main(String[] args) throws ExecutionException, InterruptedException {
new xiancheng1().start();
new Thread(new xiancheng2()).start();
FutureTask<Integer> futureTask = new FutureTask<Integer>(new xiancheng3());
new Thread(futureTask).start();
Integer integer = futureTask.get();
//等待1000再输出
Thread.sleep(1000);
System.out.println(integer);
//线程池
ExecutorService executorService1 = Executors.newCachedThreadPool();
executorService1.execute(()-> System.out.println(Thread.currentThread().getName()));
ExecutorService executorService2 = Executors.newFixedThreadPool(3);
executorService2.execute(()-> System.out.println(Thread.currentThread().getName()));
ExecutorService executorService3 = Executors.newSingleThreadExecutor();
executorService3.execute(()-> System.out.println(Thread.currentThread().getName()));
/**自定义线程池
参数1:Core Poll Size 核心线程数
参数2:Max Poll Size 最大线程数
参数3:Keep Alive Time存活时间
参数4:TimeUnit 存活时间的单位
参数5:WorkQueue 等待队列
参数6:Thread Factory 线程工厂
参数7:Handler 饱和策略
**/
ThreadPoolExecutor executor = new ThreadPoolExecutor(1,3,100,TimeUnit.SECONDS,new LinkedBlockingDeque<Runnable>());
executor.execute(()-> System.out.println(Thread.currentThread().getName()));
}
}
使用多线程可能带来什么问题
内存泄漏、上下文切换、死锁
- 内存泄露:Thread Local
- 上下文切换:当前任务在执行完CPU时间片切换到另一个任务之前会先保存自己的状态,以便下次在切换回这个任务时,可以再加载这个任务的状态。任务从保存到再加载的过程就是一次上下文切换。Linux 相⽐与其他操作系统有很多的优点,其中有⼀项就是,其上下⽂切换和模式切换的时间消耗⾮常少。
- 死锁:
线程死锁描述的是,多个线程同时被阻塞,它们中的一个或者全部都在等待某个资源被释放
产生死锁必须具备的四个条件:
- 互斥条件:该资源任意一个时刻只由一个线程占用。
- 请求与保持条件:一个进程因请求资源而阻塞时,对已获取的资源保持不放。
- 不剥夺条件:线程已获得的资源在未使用完之前不能被其他线程强行剥夺,只有自己使用完毕后才释放资源。
- 循环等待条件:若干进程之间形成一种头尾相接的等待资源关系。
如何避免死锁:
- 破坏互斥条件:这个条件无法破坏,因为用锁本来就是想让它们互斥。
- 破坏请求与保持条件:一次性申请所有的资源。
- 破坏不剥夺条件:占用部分资源的线程,进一步申请其他资源时,如果申请不到,可以主动释放它当前占用的资源。
- 破坏循环等待条件:使用按序申请资源来预防。按某种顺序申请资源,释放资源则反序释放。
4.线程池的状态
- Running:线程池一旦创建,就处于Running状态,能够接受新任务;
- ShutDown:线程池调用shutdown接口的时候,进入该状态,这个状态下线程池不能接收新任务,但是能处理已经添加的任务
- Stop:线程池调用shutdownNow接口的时候,进入该状态,这个线程下线程池不接受新任务,不处理已经添加的任务,并会中断正在处理的任务
- Tidying:当线程在Stop状态下,线程池中执行的任务为空时,就处于该状态
- Terminated:当线程在Tidying状态下,执行完terminated()之后,就进入该状态