多线程
文章目录
线程的模型
进程是系统分配资源的最小单位,线程是系统调度的最小单位
多线程:一个进程中不只一个线程
线程的优点
1.更好的利用CPU资源,多线程可在主线程执行任务同时执行其任务,不需要等待
2.同一进程的各线程之间可以共享该进程的所有资源
3.创建线程代价比较小,而系统创建进程要为该进程分配资源
4.与进程之间切换相比,线程之间的切换需要OS做的工作要少得多
5.线程占有的资源比较少
6.计算密集型应用,可将计算分解到多个线程实现
7.I/O密集型应用,可将I/O操作重叠,线程可以同时等待不同的I/O操作
线程的操作
线程的创建
继承Thread类
private static class MyThread extends Thread {
@Override
public void run() {
for (int i = 0; i < 10; i++) {
System.out.println(i);
}
}
}
Thread a = new MyThread();
a.start();
Thread c = new Thread(new MyThread());
c.start();
实现Runnable接口
private static class MyRunnable implements Runnable {
@Override
public void run() {
for (int i = 0; i < 10; i++) {
System.out.println(i);
}
}
}
Thread b = new Thread(new MyRunnable());
b.start();
实现Callable接口
private static class MyCallable implements Callable<String> {
private String name;
public MyCallable(String name) {
this.name = name;
}
@Override
public String call() throws Exception {
return name;
}
}
//创建线程池
ExecutorService pool = Executors.newFixedThreadPool(5);
//创建用于接收结果的list
List<Future> list = new ArrayList<Future>();
for (int i = 0; i < 5; i++) {
//创建有返回值的线程实例
Callable c = new MyCallable(i+ " ");
//提交线程,并将结果保存到Future中,将Future保存到list中
Future future = pool.submit(c);
System.out.println("submit " +i);
list.add(future);
}
//关闭线程池,等待线程执行结束
pool.shutdown();
//遍历所有线程的运行结果
for (Future f:list) {
System.out.println("get result " + f.get().toString());
}
基于线程池
ExecutorService pool = Executors.newFixedThreadPool(10);
for (int i = 0; i < 10; i++) {
pool.execute(new Runnable() {
@Override
public void run() {
System.out.println(Thread.currentThread().getName() + " is running");
}
});
}
pool.shutdown();
其他变形
public static void UseAnonymous() {
//使用匿名类创建子类对象
// == 直接创建线程对象
Thread a = new Thread() {
@Override
public void run() {
//线程要执行的内容
}
};
//使用匿名类创建 Runnable 子类对象
// == 先创建目标对象,再创建线程对象
Thread b = new Thread(new Runnable() {
@Override
public void run() {
//线程要执行的内容
}
});
//使用lambda表达式创建 Runnable 子类对象
Thread c = new Thread(() -> {
//线程要执行的内容
});
}
Thread类
构造方法
属性
Thread t = Thread.currentThread(); //获取当前线程
System.out.println(t.getId()); //线程的唯一标识
System.out.println(t.getName()); //线程的名称
System.out.println(t.getState()); //线程的状态
System.out.println(t.getPriority()); //线程的优先级
System.out.println(t.isDaemon()); //是否为后台进程
//JVM会在所有非后台线程结束后,结束运行
System.out.println(t.isAlive()); //是否存活,只有 NEW/TERMINATED 返回false
System.out.println(t.isInterrupted()); //是否被中断
线程的状态
状态转移/生命周期
线程的基本方法
-
run():提供线程一个指令清单
-
start():启动线程,把线程放到就绪队列,使其拥有被调度的资格
-
interrupt():用于向线程发一个通知终止信号,会影响线程内部的一个中断标识位。
-
中断线程的另一种方式:共享标记,当子线程中有sleep时,无法实时响应
-
A线程通知B线程终止 配图
-
B线程正在sleep/join/wait
- 通知是以InterruptedException给出
-
状态位仍为false
-
B线程清醒(没有以上行为)
-
t 通过 t.isInterrupted() / Thread.interrupted()判断
- t.isInterrupted() 状态位不变、指t这个线程、用于第三方线程查看B状态
- / Thread.interrupted() 执行后将状态位改为false、指本线程、用于B自己查看
-
-
-
join():主线程阻塞在这里,等待其他线程结束。很多情况下,主线程生成并启动了子线程,需要等待子线程返回结果并收集和处理再退出,这是就用到join()。
-
wait():线程会进入WAITING状态,只有等到其他线程的通知或被中断后才会返回。另外,调用wait()会释放对象的锁,因此wait()一般被用于同步方法或同步代码块中。
-
sleep():导致当前线程休眠,线程进入TIMED-WATING状态。另外,调用sleep()不会释放对象占有的锁。
-
Thread.yield():主动放弃CPU,但保留争抢CPU的资格。
-
notify():用于唤醒在此对象监视器上等待的一个线程,是任意选择一个唤醒。
-
notifyAll():唤醒等待的所有线程。
-
setDaemon():定义守护线程/后台线程。
run() vs start():
run() | start() | |
---|---|---|
作用 | 用于线程运行时的代码 | 用于启动线程 |
调用 | 可重复调用 | 只能调用一次 |
时间 | 必须等待run()执行结束 | 无需等待run()执行完毕 |
状态 | 线程就转为死亡态 | 线程就转为就绪态 |
多线程 | 相当于一个普通函数,没有多线程特征 | 真正实现了多线程的运行 |
sleep() vs wait():
sleep() | wait() | |
---|---|---|
类 | Thread类的静态方法 | Object类的方法 |
锁 | 不释放 | 释放 |
用途 | 用于暂停线程的执行 | 用于线程的通信 |
用法 | 方法完成后,会自动苏醒 | 不会自动苏醒,需要别的线程调用同一个对象的notify()或notifyAll() |
线程池
工作原理
JVM先根据用户的参数创建一定数量的可运行的线程任务,并将其放在队列中,在线程创建后启动这些任务,如果线程数量超过了最大线程数量,则超出数量的线程排队等候,在有任务执行完毕后,线程池调度器会发现可用的线程,则再次从队列中取出任务并执行。
线程池的主要作用是线程复用、线程资源管理、控制操作系统的最大并发数,保证系统高效且安全地运行。
线程池的核心组件和核心类
Java中的线程池是通过Executor框架实现的。
其中ThreadPoolExcutor是构建线程的核心方法,其构造函数如下表。
参数 | 说明 |
---|---|
corePoolSize | 线程池中核心线程的数量 |
maximumPoolSize | 线程池中最大线程的数量 |
keepAliveTime | 当前线程数量超过corePoolSize时,空闲线程的存活时间 |
unit | keepAliveTime的时间单位 |
workQueue | 任务队列,被提交但未被执行的任务存放的地方 |
threadFactory | 线程工厂,用于创建线程,可使用默认的线程工厂或者自定义线程工厂 |
handler | 任务拒绝策略,由于任务过多或其他原因导致线程池无法处理时使用的任务拒绝策略 |
工作流程
1.线程池刚创建时,里面没有一个线程。任务队列是作为参数传进来的。不过,就算队列里面有任务,线程池也 不会马上执行它们。
2.当调用 execute() 添加一个任务时,线程池会做如下判断:
a. 如果正在运行的线程数量小于corePoolSize,那么马上创建线程运行这个任务;
b. 如果正在运行的线程数量大于或等于corePoolSize,那么将这个任务放入队列。
c. 如果队列满了,而且正在运行的线程数量小于maximumPoolSize,那么还是要创建线程运行这个任务;
d. 如果队列满了,而且正在运行的线程数量大于或等于maximumPoolSize,那么线程池会抛出异常,告诉调用者“我不能再接受任务了”。
3.当一个线程完成任务时,它会从队列中取下一个任务来执行。
4.当一个线程无事可做,超过一定的时间(keepAliveTime)时,线程池会判断,如果当前运行的线程数大于corePoolSize,那么这个线程就被停掉。所以线程池的所有任务完成后,它最终会收缩到corePoolSize 的大小。
拒绝策略
-
AbortPolicy:直接抛出异常,阻止线程正常运行。
-
CallerRunsPolicy:如果被丢弃的线程任务未关闭,则执行改线程任务。
-
DiscardOldestPolicy:移除线程队列最早的一个线程任务。
-
DiscardPolicy:丢弃当前的线程任务而不做任何处理。
-
自定义拒绝策略:如下实现一个自定义拒绝策略,该策略根据传入的参数丢弃最老的n个线程。
public class DiscardOldestNPolicy implements RejectedExecutionHandler { private int discardNum = 5; private List<Runnable> discardList = new ArrayList<>(); public DiscardOldestNPolicy (int discardNum) { this.discardNum = discardNum; } @Override public void rejectedExecution(Runnable r, ThreadPoolExecutor e) { if (e.getQueue().size() > discardNum) { //1.批量移除线程队列中n个线程任务 e.getQueue().drainTo(discardList, discardNum); //2.清空列表 discardList.clear(); if (!e.isShutdown()) { //3.尝试提交当前任务 e.execute(r); } } } }
常用的线程池
-
newCachedThreadPool:创建一个缓存线程池。在创建新线程时如果有可重用的线程,则重用他们,否则重新创建一个新的线程并将其添加到线程池中。
-
newFixedThreadPool:创建一个固定线程数量的线程池,并将县城资源放在队列中循环使用。
-
newScheduledThreadPool:创建一个可定是调度的线程池,可设置在给定的延迟时间后执行或定期执行某个线程任务。
-
newSingleThreadExecutor:保证永远有且只有一个可用的线程。
-
newWorkStealingPool:创建足够多线程的线程来达到快速运算的目的。
//缓存线程池 ExecutorService cachedThreadPool = Executors.newCachedThreadPool(); //固定大小的线程池 ExecutorService fixedThreadPool = Executors.newFixedThreadPool(5); //单线程的线程池 ExecutorService singleThread = Executors.newSingleThreadExecutor(); //大小无限,支持定时或周期性任务 ScheduledExecutorService scheduledThreadPool = Executors.newScheduledThreadPool(3); scheduledThreadPool.schedule(new Runnable() { @Override public void run() { System.out.println("delay 3s execu."); } }, 3, TimeUnit.SECONDS); scheduledThreadPool.scheduleAtFixedRate(new Runnable() { @Override public void run() { System.out.println("delay 1s, repeat execute every 3s"); } }, 1,3, TimeUnit.SECONDS);