旨在介绍线程的基本知识,这里基本不涉及多线程相关
目录:
一、什么是线程
二、线程的作用
三、线程分类
四、线程优先级
五、线程的生命周期
六、线程的使用(线程的创建、线程常用方法介绍、线程的停止、线程属性)
一、什么是线程
线程是操作系统能够进行运算调度的最小单位,它被包含在进程之中,是进程中的实际运作单位。
二、线程的作用
减少程序在并发执行时所付出的时空开销,提高操作系统的并发性能。
三、线程分类
一种是守护线程,一种是(非守护线程)用户线程
守护线程:守护线程是用来服务用户线程的,Java垃圾回收就是一个典型的守护线程。通过在start()方法前调用 thread.setDaemon(true)可以把一个用户线程变成一个守护线程。
用户线程:主要包括主线程和普通线程。
四、线程优先级
线程优先级分为10个级别,分别用Thread类常量表示。
// 譬如:
Thread.MIN_PRIORITY // 优先级1
Thread.MAX_PRIORITY // 优先级10
通过方法setPriority(int grade)进行优先级设置
默认线程优先级是5,即 Thread.NORM_PRIORITY
Java线程中的优先级并不可靠,优先级高并不一定先执行,只是优先执行的几率大些,他只是给操作系统一个优先级的建议,真正具体怎么个顺序执行,还是由线程的调度算法决定的。一个线程必须 存在ThreadGroup中,而ThreadGroup也有优先级。当线程优先级和所属的ThreadGroup的优先级不一致时,线程的优先级会自动设置为ThreadGroup的优先级。详细请看以下两篇文章:
线程–线程组和线程的优先级
线程组和线程的优先级
五、线程的生命周期
线程的生命周期包含5个阶段,包括:新建、就绪、运行、阻塞、死亡(销毁)。
新建:线程对象对创建后,即进入了新建状态,就是刚使用new方法,new出来的线程;
就绪:当调用线程对象的start()方法后线程即进入就绪状态。处于就绪状态的线程,只是说明此线程已经做好了准备,随时等待CPU调度执行,并不是说执行了t.start()此线程立即就会执行;
运行:当就绪的线程被调度并获得CPU资源时,便进入运行状态,run方法定义了线程的操作和功能;
注:就绪状态是进入到运行状态的唯一入口,也就是说,线程要想进入运行状态执行,首先必须处于就绪状态中;
阻塞:在运行状态的时候,可能因为某些原因导致运行状态的线程变成了阻塞状态,比如sleep()、wait()之后线程就处于了阻塞状态,这个时候需要其他机制将处于阻塞状态的线程唤醒,比如调用notify或者notifyAll()方法。唤醒的线程不会立刻执行run方法,它们要再次等待CPU分配资源进入运行状态;根据阻塞产生的原因不同,阻塞状态又可以分为三种:
-
等待阻塞:运行状态中的线程执行wait()方法,使本线程进入到等待阻塞状态;
-
同步阻塞 — 线程在获取synchronized同步锁失败(因为锁被其它线程所占用),它会进入同步阻塞状态;
-
其他阻塞 — 通过调用线程的sleep()或join()或发出了I/O请求时,线程会进入到阻塞状态。当sleep()状态超时、join()等待线程终止或者超时、或者I/O处理完毕时,线程重新转入就绪状态。
死亡:如果线程正常执行完毕后或线程被提前强制性的终止或出现异常导致结束,那么线程就要被销毁,释放资源,该线程结束生命周期。

六、线程的使用
线程的创建:
创建线程的方式有三种:
- 继承Thread类,并重写run方法,在run方法内工作。
- new线程时传入Runnable实例,在Runnable的run方法内工作;
- 结合Callable接口 与 FutureTask 类来创建线程
1.继承Thread类
package com.mumuxi.testapplication;
/**
* @author mumuxi
* @date 2020/1/19
*/
public class Test {
public static final String TAG = Test.class.getSimpleName();
public static void main(String[] args) throws Exception {
Thread thread = new MyThread();
thread.start();
}
static class MyThread extends Thread {
@Override
public void run() {
System.out.println("thread start work");
super.run();
}
}
}
2. new线程时传入Runnable实例,在Runable的run方法内工作
package com.mumuxi.testapplication;
/**
* @author mumuxi
* @date 2020/1/19
*/
public class Test {
public static final String TAG = Test.class.getSimpleName();
public static void main(String[] args) throws Exception {
Runnable runnable = new Runnable() {
@Override
public void run() {
System.out.println("thread start work");
}
};
Thread thread = new Thread(runnable);
thread.start();
}
}
自然传入的Runnable 实例也是可以通过创建一个类实现Runable接口,重写其run()方法。如下所示:
package com.mumuxi.testapplication;
/**
* @author mumuxi
* @date 2020/1/19
*/
public class Test {
public static final String TAG = Test.class.getSimpleName();
public static void main(String[] args) throws Exception {
MyRunnable runnable = new MyRunnable();
Thread thread = new Thread(runnable);
thread.start();
}
static class MyRunnable implements Runnable {
@Override
public void run() {
System.out.println("thread start work");
}
}
}
3、结合Callable接口 与 FutureTask 类来创建线程
实际上这种方法就是第二种的方式,因为FutureTask 从源码去看就是实现了Runnable 接口的类,也会在实例化线程时作为参数传入,但由于这是Java已经给我们封装好的一种使用方式,使用方式跟第二种略有不同,所以这里把这种方式归为第三种创建线程的方式,看一下下面例子,比较一下跟传入跟普通的Runnable 的接口有什么区别
package com.mumuxi.testapplication;
import android.util.Log;
import java.util.concurrent.Callable;
import java.util.concurrent.FutureTask;
/**
* @author mumuxi
* @date 2020/1/19
*/
public class Test {
public static final String TAG = Test.class.getSimpleName();
public static void main(String[] args) throws Exception {
/**
* 1】创建Callable接口的实现类,并实现call()方法,然后创建该实现类的实例(从java8开始可以直接使用Lambda表达式创建Callable对象)。
*
* 2】使用FutureTask类来包装Callable对象,该FutureTask对象封装了Callable对象的call()方法的返回值
*
* 3】使用FutureTask对象作为Thread对象的target创建并启动线程(因为FutureTask实现了Runnable接口)
*
* 4】调用FutureTask对象的get()方法来获得子线程执行结束后的返回值
*/
//1.创建Callable 实例
Callable callable = new Callable() {
@Override
public String call() throws Exception {
System.out.println("callable start");
Thread.sleep(5000);
System.out.println("callable end");
return "test";
}
};
//2.使用FutureTask类来包装Callable对象,该FutureTask对象封装了Callable对象的call()方法的返回值
FutureTask futureTask = new FutureTask(callable);
//3.使用FutureTask对象作为Thread对象的target创建并启动线程(因为FutureTask实现了Runnable接口)
Thread thread = new Thread(futureTask);
System.out.println("start thread");
//调用start之后,当前获得资源,callable内的程序就会开始执行。
thread.start();
//4.调用FutureTask对象的get()方法来获得子线程执行结束后的返回值
System.out.println("start futureTask");
//futureTask.get()方法会阻塞,如果callable的call方法没有结果的话,
// 调用futureTask.get()的方法的线程会被阻塞在这里。
System.out.println(futureTask.get());
System.out.println("futureTask.get() end");
}
}
————————————–三种创建线程方法对比————————————–
实现Runnable和实现Callable接口的方式基本相同,不过是后者执行call()方法有返回值,后者线程执行体run()方法无返回值,因此可以把这两种方式归为一种这种方式与继承Thread类的方法之间的差别如下:
1、线程只是实现Runnable或实现Callable接口,还可以继承其他类。
2、这种方式下,多个线程可以共享一个target对象,非常适合多线程处理同一份资源的情形。
3、但是编程稍微复杂,如果需要访问当前线程,必须调用Thread.currentThread()方法。
4、继承Thread类的线程类不能再继承其他父类(Java单继承决定)。
注:一般推荐采用实现接口的方式来创建多线程,更加灵活方便
线程常用方法介绍
- start() : 启动线程
- setPriority() : 设置线程优先级
- getPriority() : 获取线程优先级
- Thread.currentThread() : 线程类的静态方法,用来获取当前线程
- Thread.sleep() : 线程类的静态方法,线程休眠,让当前线程进入睡眠阻塞状态
- setDaemon() : 将该线程标记为守护线程或用户线程
- interrupt() : interrupt() 方法只是改变中断状态而已,它不会中断一个正在运行的线程。如果线程被Object.wait, Thread.join和Thread.sleep三种方法之一阻塞,此时调用该线程的interrupt()方法,那么该线程将抛出一个 InterruptedException中断异常。
- isAlive() : 判断线程是否处于活动状态。
- join() :等待线程执行指定时间或执行完后再继续执行
- yield 方法:放弃当前的 CPU 资源,让其他的任务去占用 CPU 执行时间。但放弃的时间不确定,有可能刚刚放弃,马上又获得时间片
- stop():停止该线程。这一方法已过时
- suspend():暂停这一线程的执行 已过时
- resume() :恢复线程,和suspend一起使用,也过时
package com.mumuxi.testapplication;
/**
* @author mumuxi
* @date 2020/1/19
*/
public class Test {
public static final String TAG = Test.class.getSimpleName();
public static void main(String[] args) throws Exception {
/**
* 1、start() 启动线程
* 2、setPriority() 设置线程优先级
* 3、getPriority() 获取线程优先级
* 4、Thread.currentThread() 线程类的静态方法,用来获取当前线程
* 5、Thread.sleep() 线程类的静态方法,线程休眠,让当前线程进入睡眠阻塞状态
* 6、setDaemon() 将该线程标记为守护线程或用户线程
* 7、interrupt() interrupt() 方法只是改变中断状态而已,它不会中断一个正在运行的线程。如果线程被Object.wait, Thread
* .join和Thread.sleep三种方法之一阻塞,此时调用该线程的interrupt()方法,那么该线程将抛出一个 InterruptedException中断异常。
* 8、isAlive 判断线程是否处于活动状态。
* 9、join() 等待线程执行指定时间或执行完后再继续执行
*/
Thread thread = new MyThread();
Thread thread1 = new MyThread("你好");
//调用start()方法启动线程,多次调用会抛出 IllegalThreadStateException 异常
thread.start();
//通过setPriority方法设置线程优先级
thread.setPriority(4);
//通过getPriority方法获取线程优先级
System.out.println(thread.getPriority());
//Thread类的静态方法,用来获取当前线程
Thread.currentThread();
//该方法判断当前线程是否处于活动状态。
System.out.println("thread isAlive = " + thread.isAlive());
//获取线程唯一标识,这个ID是同步递增的
System.out.println(thread.getId());
System.out.println(thread1.getId());
//getName()获取线程名称
System.out.println(thread.getName());
System.out.println(thread1.getName());
//getState()获取线程的状态
System.out.println(thread.getState());
System.out.println(thread1.getState());
//等待该线程终止的时间最长为 millis 毫秒,不指定时间的话,会一直等待改线程结束后才继续执行
thread.join(2000);
System.out.println("join end");
//打断线程的sleep状态
thread.interrupt();
}
static class MyThread extends Thread {
public MyThread() {
super();
}
public MyThread(String name) {
super(name);
}
@Override
public void run() {
System.out.println("thread start work");
try {
Thread.sleep(5000);
} catch (InterruptedException e) {
System.out.println("throw InterruptedException");
e.printStackTrace();
}
}
}
}
线程的停止
线程的停止,核心就是想办法让线程的run方法执行完毕,线程也就会停止了。可以参考这篇文章Java结束线程的三种方法
线程属性
线程有很多属性,常常关心只有几种,线程名称,线程优先级,守护线程,处理未捕获异常的处理器。
线程名称:执行重要任务的线程初始化时最好设置好线程名称,方便后续问题分析;
线程优先级;注意一点就行,程序功能的正确性不能依赖于优先级,优先级并不可靠,优先级高并不一定先执行,只是优先执行的几率大些;
守护线程:守护线程唯一的用途就是为其他线程服务,当只剩下守护线程的时候虚拟机就会退出,所以守护线程永远不要去访问资源,如文件,数据库,因为他可能在任何时候中断。函数void setDaemon(boolean isDaemon)
未捕获异常处理器:这是因为run方法不能抛出任何被检测的异常,但是被检测的异常被抛出就会终止线程;有几种方法处理,一是自 setUncaughtExceptionHandler;第二种是try catch
参考:
Java 线程的基本使用
Android多线程:你必须要了解的多线程基础知识汇总