线程系列目录
- Thread线程从零认识到深层理解——初识
- Thread线程从零认识到深层理解——六大状态
- Thread线程从零认识到深层理解——wait()与notify()
- Thread线程从零认识到深层理解——线程安全
- 线程池从零认识到深层理解——初识
- 线程池从零认识到深层理解——进阶
博客创建时间:2020.09.28
博客更新时间:2021.02.25
注意:本系列博文源码分析取自于Android SDK=30,与网络上的一些源码可能不一样,可能他们分析的源码更旧,无需大惊小怪。
前言
在讲解线程知识的时候必不可少的需要线科普下什么是线程,线程与进程之间的关系。
1. 进程
进程是资源(CPU、内存等)分配的基本单位,它是程序执行时的一个实例。程序运行时系统就会创建一个进程,并为它分配资源,然后把该进程放入进程就绪队列,进程调度器选中它的时候就会为它分配CPU时间,程序开始真正运行。进程是表示资源分配的基本单位,又是调度运行的基本单位。
2. 线程
线程是一条执行路径,是程序执行时的最小单位,它是进程的一个执行流,是CPU调度和分派的基本单位。
线程由CPU独立调度执行,在多CPU环境下就允许多个线程同时运行。同样多线程也可以实现并发操作,每个请求分配一个线程来处理。
线程是一条可以执行的路径。多线程就是同时有多条执行路径在同时(并行)执行。
进程与线程的关系
- 一个线程只能属于一个进程,而一个进程可以有多个线程,但至少有一个线程。线程是操作系统可识别的最小执行和调度单位。
- 资源分配给进程,同一进程的所有线程共享该进程的所有资源。同一进程中的多个线程共享代码段(代码和常量),数据段(全局变量和静态变量),扩展段(堆存储)。但是每个线程拥有自己的栈段,栈段又叫运行时段,用来存放所有局部变量和临时变量,即每个线程都有自己的堆栈和局部变量。
- 处理CPU片分给线程,即真正在处理机上运行的是线程。
- 线程在执行过程中,需要协作同步。不同进程的线程间要利用消息通信的办法实现同步。
线程的调度问题,这里有一个很好的举例来说明:
如果把上课的过程比作进程,把老师比作CPU,那么可以把每个学生比作每个线程,所有学生共享这个教室(也就是所有线程共享进程的资源),上课时学生A向老师提出问题,老师对A进行解答。此时可能会有学生B对老师的解答不懂会提出B的疑问(注意:此时可能老师还没有对A同学的问题解答完毕),此时老师又向学生B解惑,解释完之后又继续回答学生A的问题,同一时刻老师只能向一个学生回答问题(即:当多个线程在运行时,同一个CPU在某一个时刻只能服务于一个线程,可能一个线程分配一点时间,时间到了就轮到其它线程执行了,这样多个线程在来回的切换)
一、为什么要用多线程?
线程是最小的执行单位,当程序中有多个线程是,最明显的利好就是能提高程序的执行效率不用彼此等待。除此还有其他诸多优点:
- 更高的运行效率,——并行;
- 多线程是模块化的编程模型;
- 与进程相比,线程的创建和切换开销更小,通信更方便;
- 简化程序的结构,便于理解和维护;更高的资源利用率。
- 每个线程之间独立互不影响,即使一个线程阻塞也不会影响其他线程
public class Thread implements Runnable {
...
private Runnable target ;
/**
* 线程优先级
*/
private int priority;
/**
*如果线程中target不为null,则执行target.run()。
* 否则run()无实际意义,需要要求Thread的子类必须重写run()方法
*/
@Override
public void run() {
if (target != null) {
target.run();
}
}
...
}
由代码可知,Thread本身实现了Runnable,但是它自身有一个Runnable成员Runnable ,在run方法中默认调用的是target.run()。
二、线程的常见属性
priority优先级
优先级的使用意义
当在某个线程中运行创建一个新的 Thread对象时, 新的线程的优先级默认等于创建线程的优先级,且是否是守护进程线程也同创建线程一致,如需设置优先需在创建时给定设置,后面不能再修改。
如果UI线程创建出了十几个工作线程,为了不让工作线程和主线程抢占CPU资源,需要工作线程的优先级进行降级,让CPU能够识别主次,提高主线程能够得到的系统资源。
优先级设定
每个线程都有优先级属性,具有较高优先级的线程优先于优先级较低的线程执行,每个线程可能会被标记为守护进程。
线程的优先级用数字来表示,范围从1~10,主线程的默认优先级为5,在线程构造初始化后,可以通过set方法设置具体值。
Thread.MIN_PRIORITY=1;Thread.MAX_PRIORITY=10;Thread.NORM_PRIORITY=5 )
三、线程的常见方法
构造函数
Thread的构造函数有很多种,我这里只列举几种常见的构造方法。
public Thread() {
init(null, null, "Thread-" + nextThreadNum(), 0);
}
public Thread(Runnable target) {
init(null, target, "Thread-" + nextThreadNum(), 0);
}
public Thread(ThreadGroup group, Runnable target) {
init(group, target, "Thread-" + nextThreadNum(), 0);
}
public Thread(String name) {
init(null, null, name, 0);
}
public Thread(ThreadGroup group, Runnable target, String name,
long stackSize) {
init(group, target, name, stackSize);
}
通过源码分析,最终都走向了init()这个方法,我们来进行源码分析。
/**
* Initializes a Thread.
* 初始化一个线程
*
* @param g the Thread group 线程组
* @param target the object whose run() method gets called 调用run()方法的对象
* @param name the name of the new Thread 新线程的名称
* @param stackSize the desired stack size for the new thread, or
* zero to indicate that this parameter is to be ignored.
* 新线程的所需堆栈大小,或0表示该参数将被忽略
* @param acc the AccessControlContext to inherit, or
* AccessController.getContext() if null
* 要继承的AccessControlContext;如果为null,则为* AccessController.getContext()
*/
private void init(ThreadGroup g, Runnable target, String name,
long stackSize, AccessControlContext acc) {
if (name == null) {
throw new NullPointerException("name cannot be null");
}
this.name = name;
// 当前正在执行的线程,即创建该线程正在运行的线程
Thread parent = currentThread();
//如果未设置ThreadGroup,则默认与创建者线程在同一group中
if (g == null) {
g = parent.getThreadGroup();
}
//增加线程组中未启动线程的数量,如果ThreadGroup未destroyed则nUnstartedThreads++,即使该线程最终未启动也会nUnstartedThreads++
g.addUnstarted();
this.group = g;
//如果创建者程序时守护程序线程,则它创建的线程也是守护程序线程,除非手动修改setDaemon(boolean on)
this.daemon = parent.isDaemon();
//默认新线程与父线程的优先级是一样的,如果想修改优先级需要额外调用setPriority(int newPriority)方法进行重新设定
this.priority = parent.getPriority();
this.target = target;
init2(parent);
//设置线程请求的堆栈大小
this.stackSize = stackSize;
//为下个Thread Id做准备 ++ threadSeqNumber
tid = nextThreadID();
}
根据分析init()方法可以获悉如下:
- 这是一个线程的初始化方法,创建Thread最终都会执行该方法
- 线程必须有线程名,如果创建者未定义线程名,则系统默认会取名"Thread-" + nextThreadNum(),如"Thread-643636"。后面的数字由线程的一个全局静态变量threadInitNumber决定,它会自增。
- 增加ThreadGroup 中未启动线程的数量,如果ThreadGroup未destroyed则nUnstartedThreads++,即使该线程最终未启动也会nUnstartedThreads++
- 在初始化方法中daemon、priority 等属性默认与父线程相同,不过Thread中提供了相关set方法,可以在线程创建完毕后自定义修改
run()方法
线程实现了Runnable接口,需要重写Runnable#run()方法。
在我们继承Thread重写run()方法时,常常看到super.run会不会感到奇怪,这个有啥用。其实这里调用的是Runnable对象target#run()。target是private的且只能通过Thread的构造函数进行赋值,所以如果在Thread创建的构造函数中未传target参数,其实super.run()无卵用。
private class TestThread : Thread() {
override fun run() {
super.run()
println("TestThread =$name")
}
}
我通过测试发现如果Thread的子类既重写了自身的run()方法,有实现了Runnable重写了Runnable的run()方法,则两个方法中的代码都会执行。
@JvmStatic
fun main(args: Array<String>) {
object : Thread(Runnable {
println("执行Runnable中的run方法") }) {
override fun run() {
super.run()
println("执行Thread中的run方法")
}
}.start()
}
运行结果:
执行Runnable中的run方法
执行Thread中的run方法
start()方法
/**
* Causes this thread to begin execution; the Java Virtual Machine
* calls the <code>run</code> method of this thread.
* <p>使该线程开始执行; Java虚拟机调用此线程的`run`方法。
* It is never legal to start a thread more than once.
* In particular, a thread may not be restarted once it has completed
* execution.
*
* @exception IllegalThreadStateException if the thread was already
* started.
* @see #run()
* @see #stop()
*/
public synchronized void start() {
/**
* This method is not invoked for the main method thread or "system"
* group threads created/set up by the VM.
* VM创建设置的main方法线程或系统group线程不会调用此方法
* Any new functionality added to this method in the future may have to also be added to the VM.
* 新的特性或许未来会在此增加
* A zero status value corresponds to state "NEW".
*/
if (started)
throw new IllegalThreadStateException();
/*
* 通知组该线程即将开始,以便可以将其添加到组的线程列表中,并且该组的未启动计数可以减少nUnstartedThreads--
*/
group.add(this);
started = false;
try {
// 使用Android特定的nativeCreate()方法创建/启动线程
nativeCreate(this, stackSize, daemon);
started = true;
} finally {
try {
if (!started) {
// start失败
group.threadStartFailed(this);
}
} catch (Throwable ignore) {
/* do nothing. If start0 threw a Throwable then
it will be passed up the call stack */
}
}
}
通过start()方法的注释和代码分析,知道:
- 当线程的实例调用start()方法后,JVM会调用该线程的run()方法
- 一旦线程已经启动,started=true,如果再次调用start()方法将抛IllegalThreadStateException
- VM创建设置的main方法线程或系统group线程不会调用此方法
- 线程一旦执行完成,则无法再次调用start()启动。
- Thread的启动实际是由native方法 nativeCreate启动的,它是Android特有的显示提供的方法,其虚拟机中是调用的是start0()。
- 由于内存不够,线程数超过限制等原因,nativeCreate()调用可能失败,此时需要将该Thread从线程组列表中移出,nUnstartedThreads++
stop()方法
@Deprecated
public final void stop() {
/*
* The VM can handle all thread states stop0(new ThreadDeath());
*/
throw new UnsupportedOperationException();
}
stop方法很简单,此方法最初旨在强制线程停止*并抛出{@code ThreadDeath}作为异常,本质上是不安全的。所以它已被@Deprecated标记,实际开发中请不要在使用该方法,如确实需要停止线程请使用中断的方式。线程中断在后面将会详细描述。
在旧的版本中stop()方法是这样的:
@Deprecated
public final void stop() {
stop(new ThreadDeath());
}
旧版SDK中通过stop(new ThreadDeath())来停止线程,它实质调用了native方法中的stop0()方法,它的方法实质也是抛出了一个UnsupportedOperationException()异常。在调用stop()方法时抛出UnsupportedOperationException异常,意在强制停止线程是不安全和被认可的。
sleep
/**
* Causes the currently executing thread to sleep (temporarily cease
* execution) for the specified number of milliseconds,
* subject to the precision and accuracy of system timers and schedulers.
* 使当前正在执行的线程在指定的毫秒数内进入睡眠状态(暂时停止执行),这取决于系统计时器和调度程序的精度和准确性
* The thread does not lose ownership of any monitors.
* 该线程不会失去任何锁的所有权。
*
* @param millis 睡眠时间(以毫秒为单位)
* @throws IllegalArgumentException 如果{@code millis}的值为负
* @throws InterruptedException if any thread has interrupted the current thread. The
* <i>interrupted status</i> of the current thread is
* cleared when this exception is thrown.
* 如果有任何线程中断了当前线程。引发此异常时,将清除当前线程的中断状态。
*/
public static void sleep(long millis) throws InterruptedException {
sleep(millis, 0);
}
/**
* 在安卓中使用共享的native来实现sleep()方法
*/
@FastNative
private static native void sleep(Object lock, long millis, int nanos)
throws InterruptedException;
/**
* Causes the currently executing thread to sleep (temporarily cease
* execution) for the specified number of milliseconds plus the specified
* number of nanoseconds, subject to the precision and accuracy of system
* timers and schedulers.
* 根据系统计时器和调度程序的精度和准确性,使当前正在执行的线程进入睡眠状态(暂时停止*执行)
* 指定的毫秒数加上指定的*纳秒数。
*
* @param millis 睡眠时间(以毫秒为单位)
* @param nanos {@code 0-999999}额外的纳秒睡眠时间
* @throws IllegalArgumentException 如果{@code millis}的值为负,或者* {@code nanos}的值不
* 在{@code 0-999999}范围内
* @throws InterruptedException 如果有任何线程中断了当前线程。引发此异常时,将清除当前线程的中断状态。
*/
public static void sleep(long millis, int nanos)
throws InterruptedException {
if (millis < 0) {
throw new IllegalArgumentException("millis < 0: " + millis);
}
if (nanos < 0) {
throw new IllegalArgumentException("nanos < 0: " + nanos);
}
if (nanos > 999999) {
throw new IllegalArgumentException("nanos > 999999: " + nanos);
}
if (millis == 0 && nanos == 0) {
if (Thread.interrupted()) {
throw new InterruptedException();
}
return;
}
final int nanosPerMilli = 1000000;
long start = System.nanoTime();
long duration = (millis * nanosPerMilli) + nanos;
Object lock = currentThread().lock;
// The native sleep(...) method actually performs a special type of wait,
// 本机sleep(...)方法实际上执行一种特殊的等待
// which may return early, so loop until sleep duration passes.
// 它可能会提早返回,所以循环直到睡眠持续时间过去
synchronized (lock) {
while (true) {
sleep(lock, millis, nanos);
long now = System.nanoTime();
long elapsed = now - start;
if (elapsed >= duration) {
break;
}
duration -= elapsed;
start = now;
millis = duration / nanosPerMilli;
nanos = (int) (duration % nanosPerMilli);
}
}
}
}
- 使当前正在执行的线程在指定的毫秒数内进入睡眠状态(暂时停止执行),这取决于系统计时器和调度程序的精度和准确性。即sleep(1000),事实上算上代码执行时间可能它运行了1000.2ms。
- 执行sleep()方法后,线程处于TIMED_WAITING状态,但是该线程不会失去任何锁的所有权。当超过指定时间线程自动苏醒进入就绪状态
- Object.wait()方法调用后会释放对象锁,而线程的Thread.sleep()方法不会让出对象锁,只会让出CPU时间片。两者都会让线程进入WAITING状态
yield
/**
* 向调度程序提示当前线程愿意产生当前使用的处理器.调度程序可以随意忽略此提示
*
* Yield是一种启发式尝试,旨在提高线程之间的相对进程否则会过度利用CPU
*
* 应将其使用与详细的性能分析和基准测试结合使用,以确保它确实具有所需的效果。
* 很少适合使用此方法。 *对于调试或测试目的可能是有用的,在某些情况下它可能有助于重现*由于竞争条件而引起的错误。
* 在设计并发控制结构(例如* {@link java.util.concurrent.locks}包中的结构)时,它可能也很有用。
*/
public static native void yield();
yield()方法是一种native方法,一般很少有机会使用该方法。它是一种让步方法,调用该方法可使当前线程放弃获取的CPU时间片,但不释放锁资源,由运行状态变为就绪状态,让OS再次选择线程。
实际使用中无法保证yield()达到让步目的,因为让步的线程还有可能被线程调度程序再次选中。Thread.yield()不会导致阻塞。该方法与sleep()类似,只是不能由用户指定暂停多长时间。
join
/**
* Waits at most {@code millis} milliseconds for this thread to
* die. A timeout of {@code 0} means to wait forever.
* 等待最多{@code millis}毫秒以使该线程死亡。 {@code 0}超时意味着永远等待。
* 如ThreadB中执行ThreadA#join(50),则等待50ms后ThreadB死亡
*
* <p> This implementation uses a loop of {@code this.wait} calls
* conditioned on {@code this.isAlive}.
* As a thread terminates the {@code this.notifyAll} method is invoked.
* It is recommended that applications not use {@code wait}, {@code notify}, or
* {@code notifyAll} on {@code Thread} instances.
* 建议应用程序不要在{@code Thread}实例上使用{@code wait},{@code notify}或{@code notifyAll}。
*
*/
// 在单独的锁对象而不是此线程上同步
public final void join(long millis) throws InterruptedException {
synchronized (lock) {
long base = System.currentTimeMillis();
long now = 0;
if (millis < 0) {
throw new IllegalArgumentException("timeout value is negative");
}
if (millis == 0) {
while (isAlive()) {
lock.wait(0);
}
} else {
while (isAlive()) {
long delay = millis - now;
if (delay <= 0) {
break;
}
lock.wait(delay);
now = System.currentTimeMillis() - base;
}
}
}
}
/**
* @param millis the time to wait in milliseconds
* @param nanos {@code 0-999999} additional nanoseconds to wait
* @throws IllegalArgumentException 如果{@code millis}的值为负,或者{@code nanos}的值
* 不在{@code 0-999999}范围内
* @throws InterruptedException 如果有任何线程中断了当前线程。抛出此异常时,将清除当前线程的中断状态
*/
// 在单独的锁对象而非此线程上同步。
public final void join(long millis, int nanos) throws InterruptedException {
synchronized (lock) {
if (millis < 0) {
throw new IllegalArgumentException("timeout value is negative");
}
if (nanos < 0 || nanos > 999999) {
throw new IllegalArgumentException(
"nanosecond timeout value out of range");
}
if (nanos >= 500000 || (nanos != 0 && millis == 0)) {
millis++;
}
join(millis);
}
}
public final void join() throws InterruptedException {
join(0);
}
- join()或者join(long)一般使用在一个线程中调用其它线程t的join方法,当前线程进入WAITING/TIMED_WAITING状态,当前线程不会释放已经持有的对象锁。线程t执行完毕或者millis时间到,当前线程进入就绪状态继续执行代码。
- join()方法会响应线程中断抛出InterruptedException异常
setPriority
设置线程的优先级,线程优先级在1~10,不在范围则抛异常。
/**
* 修改线程的优先级,默认是Thread Group的最大优先级,一般为5
*
* @exception IllegalArgumentException 不在1~10范围内,则抛异常
* @exception SecurityException if the current thread cannot modify this thread.
*/
public final void setPriority(int newPriority) {
ThreadGroup g;
checkAccess();
if (newPriority > MAX_PRIORITY || newPriority < MIN_PRIORITY) {
// Android-changed: Improve exception message when the new priority is out of bounds.
throw new IllegalArgumentException("Priority out of range: " + newPriority);
}
if((g = getThreadGroup()) != null) {
if (newPriority > g.getMaxPriority()) {
newPriority = g.getMaxPriority();
}
// Android-changed: Avoid native call if Thread is not yet started.
// setPriority0(priority = newPriority);
synchronized(this) {
this.priority = newPriority;
if (isAlive()) {
// BEGIN Android-added: Customize behavior of Thread.setPriority().
// http://b/139521784
// setPriority0(newPriority);
ThreadPrioritySetter threadPrioritySetter =
RuntimeHooks.getThreadPrioritySetter();
int nativeTid = this.getNativeTid();
if (threadPrioritySetter != null && nativeTid != 0) {
threadPrioritySetter.setPriority(nativeTid, newPriority);
} else {
setPriority0(newPriority);
}
// END Android-added: Customize behavior of Thread.setPriority().
}
}
}
}
四、如何使用线程
线程使用方法有三种如下:
- 继承Tread类创建线程
private class TestThread extends Thread{
@Override
public void run() {
super.run();
...
}
}
new TestThread().start();
- 实现Runnable接口创建线程
private class TestRunnable implements Runnable{
@Override
public void run() {
...
}
}
new Thread(new TestRunnable()).start();
- 继承Tread类创建线程
FutureTask<Integer> futureTask = new FutureTask<>(new Callable<Integer>() {
@Override
public Integer call() throws Exception {
...
return null;
}
});
new Thread(futureTask).start();
三种方式比较:
- Thread: 继承方式, 不建议使用, 因为Java是单继承的,继承了Thread就没办法继承其它类了,不够灵活。它只是用来启动线程而已,执行代码体一般不放这里。
- Runnable: 实现接口,比Thread类更加灵活,没有单继承的限制
- Callable: Thread和Runnable都是重写的run()方法并且没有返回值,Callable是重写的call()方法并且有返回值并可以借助FutureTask类来判断线程是否已经执行完毕或者取消线程执行。
- Thread类是实现Runnable,Callable封装成FutureTask,FutureTask实现RunnableFuture,RunnableFuture继承Runnable,所以Callable也算是一种Runnable,所以三种实现方式本质上都是Runnable实现
总结
本篇博文主要讲解Thread的创建、使用及常用属性方法,适合新手学习阅读。对于对线程学习有更高要求的同学请继续阅读Thread系列中的其他文章。
相关链接:
- Thread线程从零认识到深层理解——初识
- Thread线程从零认识到深层理解——六大状态
- Thread线程从零认识到深层理解——wait()与notify()
- Thread线程从零认识到深层理解——线程安全
- 线程池从零认识到深层理解——初识
- 线程池从零认识到深层理解——进阶
扩展链接:
博客书写不易,您的点赞收藏是我前进的动力,千万别忘记点赞、 收藏 ^ _ ^ !