复习线程内容
程序由指令和数据组成,但这些指令要运行,数据要读写,就必须将指令加载至CPU,数据加载至内存(程序的指令和数据都是首先加载到内存中的,然后根据需要由CPU从内存中读取指令和数据进行处理。)。在指令运行过程中还需要用到磁盘、网络等设备。进程就是用来加载指令、管理内存管理IO的。
当一个程序被执行时,操作系统会创建一个对应的进程来管理这个程序的执行。这个过程中,程序的代码(指令)和数据会被加载到内存中。进程控制块(Process Control Block, PCB)用于存储进程的相关信息,操作系统通过PCB来管理和调度进程。
进程概念
进程是操作系统进行资源分配和调度的基本单位,是程序执行过程中的一个实例。它代表了一个正在运行的程序,并且包含了运行该程序所需的所有资源,包括但不限于内存空间、系统文件、状态信息、处理器状态、堆栈、寄存器值等。
线程概念
线程是进程内部的一个执行路径,是进程内的最小执行单元,是一个独立的任务,是CPU调度和分派的基本单位。
创建线程的方法
-
类 继承Thread类
-
类 实现Runnable接口 重写public void run(){} new Thread(任务)
-
类 实现Callable接口 重写public T call() throws Exception{}
-
使用线程池创建线程(项目中使用的方法)
那么Runnable接口 和 Callable接口 有什么区别?
1)Runnable接口run方法没有返回值
2)Callable接口call方法有返回值,返回值类型是泛型,和Future、FutureTask配合可以用来获取异步执行的结果
3)Callable接口的call()方法允许抛出异常;而Runnable接口的run方法的异常只能在内部消化,不能继续上抛(也就是方法签名处不能throws)
在启动线程时候可以使用run方法吗?run方法与start方法有什么区别?
调用run方法和使用普通方法一样,调用一次执行一次。可以多次调用
调用start方法是开启线程的,通过该线程调用run方法执行run方法中所定义的逻辑代码。start方法只能被调用一次。如果尝试再次调用 start() 方法,会抛出IllegalThreadStateException 异常。start 方法内部会调用 run 方法,但这是由 JVM 在新线程中完成的,而不是直接调用。
线程常用方法
1. run()
- 作用:定义线程执行的任务。
- 使用场景:通常通过重写
Thread
类的run()
方法或传递一个Runnable
对象来实现具体任务。 - 注意:直接调用
run()
不会启动新线程,而是作为普通方法在当前线程中执行。
2. start()
- 作用:启动一个新线程,并让新线程执行
run()
方法中的任务。 - 使用场景:用于真正启动线程并实现并发执行。
- 注意:每个线程只能调用一次
start()
,多次调用会抛出IllegalThreadStateException
。
3. sleep(long millis)
- 作用:让当前线程暂停执行指定的时间(毫秒),进入
TIMED_WAITING
状态。 - 使用场景:用于模拟延迟、定时任务或降低线程资源占用。
- 注意:
sleep()
是静态方法,作用于当前线程;不会释放锁。
Thread.sleep(1000); // 当前线程休眠 1 秒
4. join()
- 作用:让其他线程等待当前线程执行完毕后再继续执行。
- 使用场景:用于确保某些线程按顺序执行。
- 变体:
join(long millis)
:等待指定时间后继续。join(long millis, int nanos)
:等待指定时间(精确到纳秒)后继续。
thread.join(); // 主线程等待 thread 执行完毕
5. currentThread()
- 作用:返回当前正在执行的线程对象。
- 使用场景:获取当前线程信息,如线程名称、优先级等。
Thread current = Thread.currentThread();
System.out.println("Current thread: " + current.getName());
其他常用方法
6. isAlive()
- 作用:判断线程是否处于活动状态(即是否已启动但尚未结束)。
- 返回值:
true
表示线程正在运行,false
表示线程已经终止。
if (thread.isAlive()) {System.out.println("Thread is still running.");
}
7. interrupt()
- 作用:中断线程,设置线程的中断标志位为
true
。 - 使用场景:用于通知线程停止工作或退出阻塞状态。
- 注意:线程需要自己检查中断状态并做出响应。
thread.interrupt(); // 中断线程
8. isInterrupted()
- 作用:判断线程是否被中断。
- 返回值:
true
表示线程已被中断,false
表示未被中断。
if (thread.isInterrupted()) {System.out.println("Thread has been interrupted.");
}
9. interrupted()
- 作用:静态方法,检查当前线程是否被中断,并清除中断状态。
- 返回值:
true
表示当前线程被中断,false
表示未被中断。 - 注意:此方法会清除中断标志位。
if (Thread.interrupted()) {System.out.println("Current thread was interrupted.");
}
10. setPriority(int priority)
和 getPriority()
- 作用:设置或获取线程的优先级。
- 取值范围:1 到 10,默认优先级为 5。
- 注意:优先级高的线程有更大机会被调度,但不保证一定先执行。
thread.setPriority(Thread.MAX_PRIORITY); // 设置最高优先级
int priority = thread.getPriority(); // 获取线程优先级
11. setName(String name)
和 getName()
- 作用:设置或获取线程的名称。
- 使用场景:用于标识线程,方便调试。
thread.setName("MyThread");
System.out.println("Thread name: " + thread.getName());
12. yield()
- 作用:提示线程调度器当前线程愿意让出 CPU 资源,允许其他线程运行。
- 使用场景:用于优化线程调度,但实际效果依赖于操作系统的实现。
Thread.yield(); // 当前线程让出 CPU
13. getState()
- 作用:返回线程的状态。
- 可能值:
NEW
:线程尚未启动。RUNNABLE
:线程正在运行。BLOCKED
:线程被阻塞。WAITING
:线程在等待另一个线程。TIMED_WAITING
:线程在等待一段时间。TERMINATED
:线程已终止。
Thread.State state = thread.getState();
System.out.println("Thread state: " + state);
14. wait()
, notify()
, notifyAll()
- 作用:用于线程间的协作,必须在同步代码块中使用。
wait()
:使当前线程等待,直到其他线程调用notify()
或notifyAll()
。notify()
:唤醒一个等待的线程。notifyAll()
:唤醒所有等待的线程。
synchronized (lock) {lock.wait(); // 当前线程等待lock.notify(); // 唤醒一个等待线程
}
补充说明
以上列出的方法是 Java 线程管理中最常用的 API。它们可以分为以下几类:
- 线程生命周期相关:
start()
,run()
,isAlive()
,getState()
。 - 线程调度相关:
sleep()
,yield()
,join()
,setPriority()
。 - 线程中断相关:
interrupt()
,isInterrupted()
,interrupted()
。 - 线程协作相关:
wait()
,notify()
,notifyAll()
。
需要更深入地学习线程管理,建议了解线程池(ExecutorService
)、并发工具类(如 CountDownLatch
、CyclicBarrier
)以及锁机制(如 ReentrantLock
)。
线程状态(Java线程状态和操作系统线程状态)
Java中线程state: 新建state、可运行(包含就绪和运行)、阻塞state(获取锁失败)、等待state(wait)、含time的等待state(sleep)、死亡state
操作系统中线程state:
创建线程 new Thread()
就绪状态 start() 把线程注册到操作系统
运行状态 获得了cpu的执行权(cpu的时间片)
阻塞状态 sleep() join wait 等待synchronized,期间操作系统就不再调用 等待阻塞动作完成后,再回到就绪状态
死亡状态 任务运行结束 出异常没有处理
新建多个线程后,如何保证按顺序执行?
可以用线程中的join方法解决
在Java中wait方法和sleep方法的不同?
共同点:
放弃cpu使用权进入阻塞状态
不同点:
1.方法归属不同:
- sleep(long)是Thread的静态方法
- 而wait(),wait(long)都是Object的成员方法 ,每个对象都有
2.醒来时机不同
- 执行sleep(long) 和 wait(long) 的线程都会在等待相应毫秒后醒来
- wait(long) 和 wait() 还可以被 notify 唤醒, wait()如果不唤醒就会一直等待下去
- 它们都可以被打断唤醒
3.锁特性不同 (warning!!!)
- wait方法的调用必须先获取 wait 对象的锁,而sleep则没有这个限制
- wait 方法执行后会释放对象锁, 允许其它线程获得该对象锁 (我放弃 cpu使用权,你们还可以用)
- 而sleep 如果在synchronized 代码块中执行, 并不会释放对象锁 (我放弃 cpu,你们也用不了)
如何停止一个正在运行的线程?
有三种方法可以停止线程.
- 使用退出标志,使线程正常退出,也就是当run方法完成后线程终止
- 使用stop方法强行终止(不推荐,方法已作废)
- 使用interrupt方法中断线程 (有两种方式)
打断阻塞的线程(sleep,wait,join)的线程,线程会抛出InterruptedException异常
打断正常的线程,可以根据打断状态来标记是否退出线程
多线程访问共享数据(竞态条件)
存在资源竞争使用问题,
加锁
使用synchronized关键字修饰代码块和方法
同步锁对象,任意类的对象都可以,但是只能是唯一的一个对象,记录有没有线程进入到同步代码块
synchronized (同步锁对象){
}
synchronized修饰方法时,同步锁对象不需要我们指定
非静态方法,锁对象默认是this
静态方法,锁对象是类的Class对象,类的Class对象只有一个
public synchronized void print(){
}
ReentrantLock类实现
lock();加锁
unlock();释放锁
synchronized与ReentrantLock 区别:
线程通信(生产者,消费者模型)
wait() notify() notifyAll() 都只能在同步代码块中使用,调用的对象只能是锁对象
sleep()和wait()的区别:
sleep(时间) 休眠指定的时间,时间到了会自动进入就绪状态,期间不会释放锁,sleep是Thread类中的方法
wait() 线程等待,不会自己醒来,需要其它线程唤醒,wait方法是会释放锁的,wait是Object类中的方法