Java多线程学习路线
学习阶段 | 学习内容 |
---|---|
初级 | 多线程的概念、线程的生命周期、线程的状态 |
继承 Thread 类创建线程、实现 Runnable 接口创建线程 | |
线程同步、线程通信、死锁 | |
中级 | 线程池的概念和使用 |
线程安全的集合类、原子类 | |
synchronized 关键字、ReentrantLock 类的使用 | |
Condition 类的使用、ReadWriteLock 的使用 | |
高级 | 并发编程工具类的使用、volatile 关键字的使用 |
原子类的使用、ThreadLocal 的使用 | |
并发编程的设计模式、Java 并发编程的性能优化 |
Java多线程介绍
Java多线程可以用于处理复杂的任务,例如网络编程、GUI编程和并发编程等。
- 在网络编程中,一个Java应用程序可以同时处理多个客户端请求;
- 在GUI编程中,Java多线程可以使应用程序保持响应,并且可以在后台执行长时间运行的任务;
- 在并发编程中,Java多线程可以实现多个线程的并发执行,从而提高应用程序的性能和响应速度。
每个Java程序都至少有三个线程——main主线程、gc()垃圾回收机制的运行线程和异常处理线程;
Java中的多线程是通过Thread类和Runnable接口来实现的。
Thread类是Java中处理多线程的核心类,它提供了多种线程相关的方法,包括创建线程、启动线程、中断线程等。一个Java应用程序可以同时创建多个线程,每个线程可以执行不同的任务。
Runnable接口是Java多线程的另一个重要接口,它定义了线程的执行代码。一个线程可以通过实现Runnable接口来定义自己的执行代码,然后通过Thread类的构造方法将其包装为一个线程对象。
Java多线程的优点:
- 提高程序性能和效率
- 可以同时处理多个任务
- 使应用程序保持响应,增加用户体验
- 支持并发编程,实现多个线程的并发执行
- 可以通过同步机制避免竞态条件和死锁等问题
Java多线程的缺点:
- 多线程编程难度较高,需要掌握一些复杂的概念和技术
- 多线程会占用更多的系统资源,对系统的负担会更大
- 多线程容易导致竞态条件、死锁等问题,需要谨慎处理
Java多线程的注意事项:
- 主线程任务结束后并且子线程任务没有结束,则主线程退出子线程继续执行
并发与并行
- 并发(concurrent)是同一时间应对(dealing with)多件事情的能力;
- 并行(parallel)是同一时间动手做(doing)多件事情的能力;
简单案例
创建一个线程有两种方法,一种是继承Thread类,另一种是实现Runnable接口
继承Thread类案例
public class MyThread extends Thread {
public void run() {
try {
for (int i = 0; i < 5; i++) {
System.out.println("线程正在运行: " + i);
Thread.sleep(1000);
}
} catch (InterruptedException e) {
System.out.println("子线程已完成");
}
}
}
public class Main {
public static void main(String[] args) {
MyThread t = new MyThread();
t.start();
try {
t.join();
} catch (InterruptedException e) {
System.out.println("主线程中断");
}
System.out.println("主线程已完成");
}
}
实现Runnable接口案例
public class Test {
public static void main(String[] args) {
// 创建一个Runnable实例,代表一个任务
Runnable task = new Runnable()//匿名内部类
{
//该对象的run方法会单独在一个线程中被调用
@Override
public void run() {
// 执行任务
System.out.println("子线程执行任务");
}
};
// 创建一个线程
Thread thread = new Thread(task);
// 启动线程
thread.start();
// 主线程继续执行其他任务
System.out.println("主线程继续执行其他任务");
}
}
实现原理
start()方法最终调用的是start()
start0()是真正实现多线程的方法
线程 生命周期中的六种状态
等待状态和阻塞状态在一些文献中会合并为一种状态
线程基础状态 | 描述 |
---|---|
new | 新创建的线程,但未启动 |
runnable | 正在JVM中运行的线程 |
blocked | 线程阻塞于锁 |
waiting | 等待另一个线程的动作 |
timed_waiting | 在指定的时间内等待另一个线程的动作 |
terminated | 已退出线程的生命周期 |
线程组合状态 | 描述 |
---|---|
new -> runnable | 线程调用start()方法后进入此状态 |
runnable -> terminated | 线程执行完run()方法后进入此状态 |
runnable -> blocked | 线程在竞争锁的过程中进入此状态 |
runnable -> waiting | 线程调用wait()方法后进入此状态 |
runnable -> timed_waiting | 线程调用sleep()、join()或者wait(timeout)方法后进入此状态 |
blocked -> runnable | 线程获取到锁后进入此状态 |
waiting -> runnable | 线程被notify()、notifyAll()方法唤醒后进入此状态 |
timed_waiting -> runnable | 等待时间到达后进入此状态 |
状态 | 描述 | 状态转换 |
---|---|---|
新建状态(new) | 创建了线程对象,但还没有调用start()方法启动线程 | 调用start()方法启动线程 |
就绪状态(runnable) | 线程已经启动,但是调度程序还没有选择它来执行 | 调用start()方法后,线程进入就绪状态;当等待锁或者等待输入输出操作完成时,线程会进入阻塞状态 |
运行状态(blocked) | 调度程序已经选择了线程,并且线程正在执行中 | 线程进入运行状态后,可以调用sleep()、yield()、wait()等方法来暂停执行,使线程进入阻塞状态或等待状态;当线程执行完run()方法,或者因为异常退出run()方法时,线程会进入终止状态 |
阻塞状态(waiting) | 线程暂时停止执行,等待某个条件的满足,例如等待I/O操作的完成或等待获取对象的锁 | 当等待的条件被满足时,线程会重新进入就绪状态;当调用sleep()、yield()、wait()等方法后,线程会进入等待状态;当等待时间到达或者被调用notify()、notifyAll()方法唤醒时,线程会重新进入就绪状态 |
等待状态(timed_waiting) | 线程等待其他线程发出通知或者指定时间到达,进入等待池 | 当等待的条件被满足时,线程会进入就绪状态;当等待时间到达时,线程会重新进入就绪状态;当被调用notify()、notifyAll()方法唤醒时,线程会重新进入就绪状态 |
终止状态(terminated) | 线程已经执行完run()方法或者因为异常退出run()方法,不再具有继续执行的能力 | 线程进入终止状态后,不可以再次启动,否则会抛出IllegalThreadStateException异常 |
Java多线程常用类
类名 | 描述 |
---|---|
Thread | 线程类,用于创建和管理线程。 |
Runnable | 线程执行的任务接口。 |
ThreadGroup | 线程组,用于管理一组线程。 |
Executors | 线程池工具类,用于创建和管理线程池。 |
Lock | 互斥锁,用于控制对共享资源的访问。 |
ReentrantLock | 可重入锁,Lock接口的实现类。 |
Condition | 条件对象,用于线程之间的协调。 |
Semaphore | 信号量,用于控制对共享资源的访问。 |
CountDownLatch | 倒计时锁,用于等待一组线程执行完毕后再执行主线程。 |
CyclicBarrier | 循环屏障,用于等待一组线程到达某个状态后再一起执行。 |
ExecutorService | 线程池接口,用于管理线程池。 |
ScheduledExecutorService | 定时执行线程池接口,用于定时执行任务。 |
Future | 异步任务的结果接口,可以用于获取异步任务的结果。 |
Java多线程API
方法 | 描述 |
---|---|
synchronized | 保证同一时刻只有一个线程可以执行某个方法或代码块 |
volatile | 保证变量的可见性,即一个线程修改了该变量的值,其他线程立即可以看到修改后的值 |
wait() | 让线程等待 |
notify() | 唤醒等待的线程 |
notifyAll() | 唤醒所有等待的线程 |
join() | 等待线程执行结束 |
yield() | 让出CPU时间 |
setDaemon() | 设置线程为守护线程 |
isAlive() | 判断线程是否还存活 |
interrupt() | 中断线程 |
isInterrupted() | 判断线程是否被中断 |
start() | 启动线程 |
run() | 线程执行体 |
sleep() | 让当前线程休眠指定时间 |
getPriority() | 获取线程优先级 |
setPriority() | 设置线程优先级 |
getName() | 获取线程名字 |
setName() | 设置线程名字 |
getId() | 获取线程ID |
线程通信
sleep()和wait()的区别
同
- 一旦使用,均可使当前线程进入阻塞状态;
异
- 声明位置不同:sleep()声明在Thread类中,wait()声明在Object类中;
- 调用要求不同:sleep()可以使用在各种需要的地方,而wait()只能用在同步代码块或同步方法里;
- sleep()使用不释放锁,而wait()使用后会释放锁。
在多线程中底层的静态代理:
在Java中,当我们想要启动一个新的线程时,我们需要创建一个Thread对象,并调用其start方法来启动线程。实际上,在Thread类的内部,start方法是通过JNI调用底层操作系统提供的线程实现来启动一个新的线程。
在这个过程中,Thread类可以被看作是用户与底层线程实现之间的代理。用户通过Thread类来创建和控制线程,而底层线程实现则负责实际的线程执行过程。这种代理模式就是静态代理模式。
具体来说,Thread类提供了一系列控制线程执行的方法,如sleep、join等方法,使得用户可以方便地控制线程的执行过程。而底层线程实现则负责实际的线程执行过程,包括线程的创建、销毁、调度等过程。
在调用Thread类的start方法时,实际上是在使用静态代理模式,将用户提交的任务委托给底层线程实现,并启动一个新的线程来执行任务。这样,通过代理的方式,将线程的创建与销毁过程与线程的执行过程分离开来,使得线程的实现更加灵活、可控,更加符合用户的需求。
线程池
介绍
线程池是一种基于多线程的设计模式,它创建一定数量的线程来执行任务,并重用这些线程来执行多个任务,从而避免了频繁创建和销毁线程所带来的开销。线程池主要由以下几个部分组成:
- 任务队列(待执行队列):用于存放待执行的任务,一般使用阻塞队列(如 LinkedBlockingQueue)。
- 线程池中的线程(执行队列):从任务队列中取出任务并执行。当任务队列为空时,线程会阻塞等待新的任务。
- 线程池管理器:管理线程池的创建、销毁以及线程数量的调整等。