Java多线程
多线程编程
本章目标
- 掌握线程的基本概念
- 掌握线程与进程的区别
- 重点掌握线程的实现方式
- 掌握线程的管理
- 掌握线程的生命周期
- 重点掌握线程同步
- 重点掌握线程池
- 掌握线程通信
- 掌握线程定时器
什么是进程
进程就是正在运行的程序,它是系统进行资源分配和调度的基本单位,各个进程之间相互独立,系统给每个进程分配不同的地址空间和资源
Win 操作系统任务管理器查看应用程序运行的进程
什么是线程
线程就是程序(进程)执行的任务(分为单线程和多线程)
进程与线程的区别
地址空间
进程之间是独立的地址空间,但同一进程的线程共享本进程的地址空间
资源占用
同一进程内的线程共享本进程的资源,如内存、I/O、CPU 等,但是进程之间的资源是独立的
健壮性
一个进程崩溃后不会对其他进程产生影响;一个线程崩溃则整个进程都死掉,所以多进程要比多线程更健壮
执行过程
进程可以独立执行,线程不能独立执行,线程必须依存于进程
并发与资源消耗
进程和线程都可以并发(同时)执行,但进程创建和切换消耗资源大,线程创建和切换消耗资源小
创建线程
方式一:继承 Thread 类,并重写 run( ) 方法
public class MyThread extends Thread {
public void run() {
for ( int i = 0; i < 10; i++ ) {
System.out.println(“子线程");
}
}
public static void main(String[] args) {
MyThread myThread = new MyThread();
myThread.start();
for ( int i = 0; i < 10; i++ ) {
System.out.println(“主线程"); }
}
}
方式二:实现 Runnable 接口,并实现 run( ) 方法
public class MyThread implements Runnable {
public void run() {
for ( int i = 0; i < 10; i++ ) {
System.out.println(“子线程");
}
}
public static void main(String[] args) {
MyThread myThread = new MyThread();
myThread.start();
for ( int i = 0; i < 10; i++ ) {
System.out.println(“主线程"); }
}
}
线程的状态
当线程被创建并启动以后,它既不是一启动就进入了执行状态,也不是一直处于执行状态。在线程的生命周期中,它要经过新建(New)、就绪(Runnable)、运行(Running)、阻塞(Blocked)和死亡(Dead)5种状态。尤其是当线程启动以后,它不可能一直"霸占"着CPU独自运行,所以CPU需要在多条线程之间切换,于是线程状态也会多次在运行、阻塞之间切换。
-
新建状态:当程序使用new关键字创建了一个线程之后,该线程就处于新建状态,此时仅由JVM为其分配内存,并初始化其成员变量的值
-
就绪状态:当线程对象调用了start()方法之后,该线程处于就绪状态。Java虚拟机会为其创建方法调用栈和程序计数器,等待调度运行
-
运行状态:如果处于就绪状态的线程获得了CPU,开始执行run()方法的线程执行体,则该线程处于运行状态
-
阻塞状态:当处于运行状态的线程失去所占用资源之后,便进入阻塞状态
-
死亡状态:当线程的 run() 方法执行完成,线程正常结束或抛出一个未捕获的异常,也可以直接调用 stop() 方法结束线程(容易导致死锁,不推荐使用)
线程暂停执行条件
- 线程优先级比较低,不能获得 CPU 时间片
- 使用 sleep( ) 方法使线程睡眠
- 通过调用 wait( ) 方法,使线程处于等待状态
- 通过调用 yield( ) 方法,线程主动出让 CPU 控制权
- 线程由于等待一个I/O事件处于阻塞状态
线程优先级
Java 中线程优先级是在 Thread 类中定义的常量
-
NORM_PRIORITY : 值为 5
-
MAX_PRIORITY : 值为 10
-
MIN_PRIORITY : 值为 1
缺省优先级为 NORM_PRIORITY
修改和查看线程优先级方法
-
final void setPriority(int newPriority) //修改当前线程的优先级
-
final int getPriority()//查看当前线程的优先级
多线程
多线程并发问题
线程 A 和线程 B 同时操作(读写)同一进程下的共享资源,这将导致数据不一致的问题,被称为多线程并发问题。
线程同步
多线程共享数据时,可能会发生数据不一致的情况,而线程同步就是为了解决多线程并发问题,它可以确保在任何时间点一个共享的资源只被一个线程使用
实现线程同步的三种方式
- 使用同步代码块
- 使用同步方法
- 使用互斥锁 (更灵活的代码控制)
线程死锁
线程 A 和线程 B 都想访问对方的资源,但都不愿意让对方先访问,这样谁也无法继续执行下去,这就是线程死锁(线程死锁很少发生,但一旦发生就很难调试)
线程池
如果并发的线程数量很多,并且线程执行一个时间很短的任务就结束了,这样就会频繁的创建和销毁线程,导致大大降低系统的运行效率
线程池工作原理
- 程序启动时向线程池中提前创建一批线程对象
- 当需要执行任务时,从线程池中获取一个空闲的线程对象
- 任务执行完毕后,不销毁线程对象,而是将其返还给线程池,并再次将状态设置为空闲
线程池优缺点
- 减少频繁创建和销毁线程对象的时间消耗,提高了程序运行的性能(优点)
- 线程池中空闲的线程对象,会占用系统更多的内存存储空间(缺点)
线程池是一种以时间换空间的性能优化策略
Java四种内置线程池
Java 语言提供了一系列线程池的实现,以解决实际开发中各种对线程池的需求
- newCachedThreadPool
- newFixedThreadPool
- newScheduledThreadPool
- newSingleThreadExecutor
Java 两个基础线程池
如果内置四个线程池实现仍无法满足需求,则 Java 语言还提供了两个基础线程池
- ThreadPoolExecutor 类
- ScheduledThreadPoolExecutor 类
这两个基础线程池用于用户创建自定义线程池,以获得更大的灵活度,同时开发难度也更大
线程间通信
wait-notify 机制
线程同步能够解决多线程并发问题,但它没有实现线程间的通信
Java 提供了一个精心设计的线程间通信机制,使用wait()、notify() 和 notifyAll() 方法,这些方法是作为 Object 类中的 final 方法实现的。这三个方法仅在 synchronized 方法中才能被调用
- wait() 方法:方法告知被调用的线程退出监视器并进入等待状态,直到其他线程进入相同的监视器并调用 notify( ) 方法
- notify( ) 方法:通知同一对象上第一个调用 wait( )线程
- notifyAll() 方法:通知调用 wait() 的所有线程,具有最高优先级的线程将先运行
线程定时器
Time 类
Timer是一个普通的类,其中有几个重要的方法
- schedule() 方法:启动定时器一个定时任务
- cancel()方法:终止定时器所有定时任务
启动一个定时任务就创建一个线程,线程会一直执行下去,直到调用终止定时任务
TimerTask 类
TimerTask 是一个抽象类,需要实现该类
- run()方法:定时任务逻辑
//创建一个定时任务
Timer timer = new Timer();
timer.schedule(new TimerTask() {
@Override
public void run() {
// 任务执行代码
}
}, 5000,1000); //延时 5s 每间隔1s 执行一次