Thread
标签(空格分隔): java android
总结在末尾。
操作系统运行一个程序时,为其创建一个进程process
。
一个进程内可以有多个线程thread
,它是操作系统调度的最小单元,拥有各自的计数器、堆栈、局部变量等,可以访问(进程内的)共享内存。
1. 创建线程
有三种方式,分别是
- 实现
Runnable
接口 - 继承
Thread
并重写run
方法 - 实现
Callable
,实现call
方法,并用FutureTask
获取结果
1.1 实现Runnable
接口
class MyRunable implements Runnable {
@Override
public void run() {
//do sometting
// Thread.currentThread().xxxx;
}
}
//调用方法是
Thread myThread = new Thread(new MyRunable());
myThread.start();
//如果直接调用MyRunnabe的run方法,就直接在该线程执行
1.2 继承Thread
并重写run
方法
class MyThread extends Thread {
@Override
public void run() {
super.run();
}
}
//调用时
new MyThread().start();
run
方法会在新线程执行,注意Thread
自身也实现了Runnable
。
1.3 实现Callable
,实现call
方法,并用FutureTask
获取结果
这种方式执行的线程可以获取结果和响应中断。
class MyCallable implements Callable<String>{
@Override
public String call() throws Exception {
return null;
}
}
//让线程运行
FutureTask<String> task = new FutureTask<String>(new MyCallable());
Thread thread = new Thread(task);
thread.start();
//获取结果
try {
//会阻塞
String result = task.get();
} catch (ExecutionException | InterruptedException e) {
}
2. 线程的基本属性
优先级、守护线程。
2.1 优先级 priority
操作系统采用时间片(CPU 单次执行某线程的时间)的形式来调度线程的运行,线程被 CPU 调用的时间超过它的时间片后,就会发生线程调度。
优先级影响线程得到的时间片多少。
java中,默认为5,最高10,最低1。
2.2 守护线程
private boolean deamon
标识是否为守护线程。
要设置deamon
属性,需要在start
之前调用。
守护线程用于做一些后台调度、支持性工作,比如垃圾回收、内存管理等。
一个进程中,如果所有线程(不包括守护线程)都退出了,虚拟机就会退出。
守护进程的finally
块中的方法不一定被执行,所以其资源清理工作不能放在finally
中。
3. 线程的生命周期
3.1 表格描述
线程状态 | 介绍 | 备注 |
---|---|---|
NEW | 新创建 | 还未调用 start() 方法;还不是活着的 (alive) |
RUNNABLE | 就绪 | 调用了 start() ,此时线程已经准备好被执行,处于就绪队列;是活着的(alive) |
RUNNING(java没有,RUNNABLE替代了) | 运行中 | 线程获得 CPU 资源,正在执行任务;活着的 |
BLOCKED | 阻塞的 | 线程阻塞于锁或者调用了 sleep;活着的 |
WAITING | 等待中 | 线程由于某种原因等待其他线程;活着的 |
TIME_WAITING | 超时等待 | 与 WAITING 的区别是可以在特定时间后自动返回;活着的 |
TERMINATED | 终止 | 执行完毕或者被其他线程杀死;不是活着的 |
--- | --- | --- |
3.2 进入Waiting
的四种方法:
- Object.wait();
- Thread.join();
- LockSupport.park();
- Lock.lock();
3.3 判定线程是否存活
public final native boolean isAlive()
除了NEW
和TERMINITED
状态都返回true
。
4. 线程的关键方法
4.1 sleep
底层是本地方法,通过系统调度暂停当前线程。
- 阻塞当前线程。如果在主线程调用其他线程的
sleep
。 - 让出CPU但不释放锁。
public static native void sleep(long millis) throws InterruptedException;
4.2 Object wait
是对象级别的方法,本质是获取、释放ObjectMonitor
。
- 调用前要先获取到对象锁。
- 让出CPU,释放对象锁。
- 调用后使该线程进入目标对象监视器的等待队列。
4.3 Thread.yield
静态方法,将当前线程切换到就绪状态,让系统重新分配CPU执行时间。
如果分配结果是调用了yield
的线程继续执行,那么它就继续执行。
4.4 Thread join
如果线程A执行thread.join
,表示线程A等待thread执行终止后才从join
返回。
允许提供时间参数,超时后线程A从join
中返回。
底层靠wait
实现
//加锁当前线程对象
public final synchronized void join(long millis)
throws InterruptedException {
long base = System.currentTimeMillis();
long now = 0;
//millis不得小于0
if (millis < 0) {
throw new IllegalArgumentException("timeout value is negative");
}
//如果millis等于0,即没有超时设置
if (millis == 0) {
//条件不满足,就继续等待
while (isAlive()) {
wait(0);
}
} else {
//如果条件不满足,并且在延迟时间内,就继续等待
while (isAlive()) {
//计算延迟时间
long delay = millis - now;
//判断是否到了延迟时间,如果到了,直接返回
if (delay <= 0) {
break;
}
//继续等待
wait(delay);
now = System.currentTimeMillis() - base;
}
}
}
很好懂,如果没到超时时间,当前线程继续wait
,到了就break
继续运行(调用thread.join
的)当前线程。
4.5 线程的中断
thread.wait
或者thread.sleep
都可以。
同时Thread
也提供了设置中断标志的api。
public void interrupt();//设置中断标志位为true,注意只是设置标志位,而不会中断一个正在执行的线程。
public boolean isInterrupted();//返回线程中断标志位
public static boolean interrupted();//返回线程标志位,同时将标志位复位为false
interrupt
改变线程的中断状态,给受阻塞的线程发出中断信号,而不会中断正在运行的线程。
isInterrupted
返回目标线程的中断状态。
当一个线程在sleep
或者wait
状态被中断,在收到中断请求,抛出InterruptedException
之前,JVM会将isInterrupted
标志复位。就是说在catch(InterruptedException e)
代码块里调用isInterrupted()
会返回false
。
static interrupted
返回当前线程的中断状态并重置为false
。
使用interrupt
的正确姿势。
可以在捕捉到异常时再次设置中断标志位。或者自定义设置标志位,并允许其他线程调用,就可以显式地在其他线程中断。下边是第一种的示例:
while (!Thread.interrupted()) {
System.out.println("Runnable " + name + " is running");
try {
Thread.sleep(sleep);
} catch (InterruptedException e) {
System.out.println("Runnable " + name + " is interrupted when sleep");
Thread.currentThread().interrupt();
}
}
System.out.println("Runnable " + name + " is interrupted");
被废弃的stop
当调用stop
,线程会立即停止,并抛出ThreadDeathError
。由于以下两种原因不建议使用它:
-
stop
方法是同步的,当run
方法也是同步,会导致工作线程的run
方法结束之后才stop
,时效性受到影响。 -
sop
方法线程不安全。执行时会强制释放持有的所有锁,可能引发错误数据。
5. 总结
1.线程用法。实现Runnable
、继承Thread
、实现Callable
,结合Task
。
2.优先级、守护进程。(这段是总结的不太好,可以看相关文章)。
3.线程的生命周期。
4.线程的中断机制。
参考文章
hapjin-JAVA多线程之中断机制(如何处理中断?)
拭心-并发编程1:全面认识 Thread)
leeon_l-Java中断interrupt详解)