Thread类基本用法
线程的创建
Thread类的构造方法
继承Thread类
class ThreadTest extends Thread {
@Override
public void run() {
// 重写run方法
// 线程运行的代码
System.out.println("继承Thread类来创建一个线程类");
}
}
public class ThreadDemo {
public static void main(String[] args) {
// 创建线程实例
ThreadTest t = new ThreadTest();
// start方法启动线程
t.start();
}
}
使用匿名内部类创建 Thread 子类对象
public static void main(String[] args) {
// 创建线程实例
Thread t = new Thread() {
@Override
public void run() {
// 线程运行的代码
System.out.println("匿名内部类");
}
};
// start方法启动线程
t.start();
}
Runnable 接口
class RunnableTest implements Runnable {
@Override
public void run() {
// 线程运行的代码
System.out.println("实现Runnable接口");
}
}
public class ThreadDemo {
public static void main(String[] args) {
// 创建线程实例
Thread t = new Thread(new RunnableTest());
// start方法启动线程
t.start();
}
}
使用匿名内部类创建 Runnable 子类对象
public static void main(String[] args) {
// 创建线程实例
Thread t = new Thread(new Runnable() {
@Override
public void run() {
// 线程运行的代码
System.out.println("匿名内部类");
}
});
// start方法启动线程
t.start();
}
lambda表达式简化匿名内部类
public static void main(String[] args) {
// 创建线程实例
Thread t = new Thread(() -> {
// 线程运行的代码
System.out.println("lambada");
});
// start方法启动线程
t.start();
}
两种写法的区别
继承Thread类, this表示当前线程对象的引用
实习Runnable接口, this表示Runnable对象的引用
class ThreadTest extends Thread {
@Override
public void run() {
System.out.println(this.toString());
}
}
class RunnableTest implements Runnable {
@Override
public void run() {
System.out.println(this.toString());
}
}
public class ThreadDemo {
public static void main(String[] args) {
ThreadTest t1 = new ThreadTest();
Thread t2 = new Thread(new RunnableTest());
t1.start();
t2.start();
}
}
Thread类常见的属性和方法
常见属性
- ID是线程的唯一标识
- 名称方便我们调试,在创建线程时可以命名
- 状态标识线程当前情况
- 优先级高的线程理论上优先调度
- 所有前台线程结束后,进程才会结束,后台线程不会影响进程的结束
- 是否存活,线程的 run 方法是否执行结束,从调用 start 开始,到 run 方法执行结束,都是存活的(除了NEW 和 TERMINATED 状态)
线程的启动
我们通过重写 run 方法创建了线程对象后, 这个线程并没有开始运行,
调用 start 方法, 才真正的在操作系统底层创建了线程并运行run中的代码
重写 run 方法就相当于安排了一个任务
创建线程对象就相当于安排人来做这个任务
调用 start 这个人才真正行动起来
线程的休眠
sleep方法, 是Thread类中的静态方法
public static void main(String[] args) {
Thread t = new Thread(() -> {
while (true) {
System.out.println("hello");
try {
// 运行到这里时, 线程休眠1000ms
Thread.sleep(1000);
} catch (InterruptedException e) {
e.printStackTrace();
}
}
});
t.start();
}
线程的中断
- 使用自定义变量作为标志位
public class ThreadDemo {
// volatile 这个关键字与线程安全有关
public static volatile boolean flg = false;
// 因为我们用匿名内部类的方式创建线程对象
// 由于匿名内部类变量捕获问题(捕获的变量必须是final修饰的,或不会修改值的变量)
// 所以这个标志位必须是成员变量
public static void main(String[] args) throws InterruptedException {
Thread t = new Thread(() -> {
while (!flg) {
System.out.println("hello");
try {
Thread.sleep(1000);
} catch (InterruptedException e) {
e.printStackTrace();
}
}
System.out.println("线程运行结束");
});
// 启动线程
t.start();
Thread.sleep(3000);
// 3秒后,修改标志位,中断线程
flg = true;
}
}
- 使用Thread类中内部的方法
public static void main(String[] args) throws InterruptedException {
Thread t = new Thread(() -> {
// Thread.currentThread() 获取当前线程对象的引用
while (!Thread.currentThread().isInterrupted()) {
// while (!Thread.interrupted()) {
// 两个 while 条件都可以
System.out.println("hello");
try {
Thread.sleep(1000);
} catch (InterruptedException e) {
e.printStackTrace();
//break;
}
}
System.out.println("线程运行结束");
});
// 启动线程
t.start();
Thread.sleep(3000);
// 3秒后,设置标志位
t.interrupt();
}
我们看运行结果可以发现,这个线程抛出异常后并没有中断,这是因为线程调用 wait/join/sleep 等方法而阻塞挂起时,interrupt 方法抛出异常唤醒阻塞状态,然后这几个方法会清除标志位,所以回到 while 条件时,标志位已经被清除了,可以在 catch 中加入 break 跳出循环
这种写法即使在阻塞状态也能唤醒阻塞并中断线程,而第一种写法要等阻塞状态结束才中断线程。
线程的等待
public static void main(String[] args) throws InterruptedException {
Thread t = new Thread(() -> {
try {
Thread.sleep(2000);
} catch (InterruptedException e) {
e.printStackTrace();
}
// t线程休眠两秒
System.out.println("t线程运行结束");
});
// 启动t线程
t.start();
// t.join();
// main 线程调用 t.join
// 意思是让 main 线程等待 t 线程结束, 再往下执行
t.join();
System.out.println("main线程运行结束");
}
线程的状态
- NEW:安排了工作, 还未开始行动,创建了线程对象,还未调用start。
- RUNNABLE: 可工作的。又可以分成正在工作中和即将开始工作。
- BLOCKED:等待锁引起的阻塞状态。
- WAITING::等待唤醒,由 wait ,join等方法引起的。
- TIMED_WAITING:等待唤醒,但是设置了时限,sleep,join(有参数的)等方法引起的。
- TERMINATED:工作完成了(run 方法执行结束)。