目录
一、Thread类的介绍
1、定义
Thread类是JVM 用来管理线程的一个类,每个线程都有一个唯一的 Thread 对象与之关联。每个执行流,都需要有一个对象来描述,而 Thread 类的对象就是用来描述线程执行流的,JVM 会将这些 Thread 对象组织起来,用于线程调度,线程管理。
2、构造方法
方法 | 说明 | 举例 |
Thread() | 创建线程对象 | Thread t1 = new Thread(); |
Thread(Runnable target) | 使用Runnable对象创建线程对象 | Thread t2 = new Thread(new MyRunnable()); |
Thread(String name) | 创建线程对象,并命名 | Thread t3 = new Thread("线程1"); |
Thread(Runnable target,String name) | 使用Runnable对象创建线程对象,并命名 | Thread t4 = new Thread(new MyRunnable(), "线程2"); |
Thread(ThreadGroup group,Runnable target) | 线程可以被用来分组管理,分好的组即为线程组 | \ |
3、常见属性
属性 | 获取方法 | 说明 |
ID | getId() | ID 是线程的唯一标识,不同线程不会重复 |
名称 | getName() | 名称是各种调试工具用到的 |
状态 | getState() | 状态表示线程当前所处的一个情况 |
优先级 | getPriority() | 优先级高的线程理论上来说更容易被调度到 |
是否后台线程 | isDaemon() | JVM会在一个进程的所有非后台线程结束后才会结束运行。 |
是否存活 | isAlive() | run 方法是否运行结束。 |
是否中断 | isInterrupted() | \ |
二、线程创建
1、继承Thread类
- 自定义一个类继承Thread类,并在该类中重写run方法。
- 实例化该类,调用start()方法,创建线程。
class MyThread extends Thread{
@Override
public void run(){
while (true){
System.out.println("线程1");
}
}
}
public class Demo01 {
public static void main(String[] args) {
MyThread t=new MyThread();
t.start();
}
}
2、实现Runnable接口
- 自定义一个类实现Runnable接口,并在类中实现抽象方法run()。
- 创建实该类后再将实例化对象作为参数传递到Thread类的构造器中,实例化Thread类的对象,通过Thread类的对象调用start()来创建线程。
class MyRunnable implements Runnable{
@Override
public void run(){
while (true){
System.out.println("线程2");
}
}
}
public class Demo02 {
public static void main(String[] args) {
MyRunnable runnable=new MyRunnable();
Thread t=new Thread(runnable);
t.start();
}
}
注:相比于继承 Thread 类,可以直接使用 this 表示当前线程对象的引用。实现 Runnable 接口,this 表示的是 MyRunnable 的引用,需要使用 Thread.currentThread()。
3、匿名内部类
(1)、匿名内部类创建Thread子类对象
- 创建匿名内部类
- 调用start方法创建线程
public class Demo03 {
public static void main(String[] args) {
Thread t=new Thread(){
@Override
public void run(){
while (true){
System.out.println("线程3");
}
};
t.start();
while (true){
System.out.println("main方法");
}
}
}
(2)、匿名内部类创建Runnable子类对象
- 创建匿名内部类
- 调用start()方法创建线程
public class demo04 {
public static void main(String[] args) {
Thread t=new Thread(new Runnable() {
@Override
public void run() {
while (true){
System.out.println("线程4");
}
}
});
t.start();
while (true){
System.out.println("main方法");
}
}
}
4、使用lambda表达式
public class Demo05 {
public static void main(String[] args) {
Thread t=new Thread(()->{
while (true){
System.out.println("线程5");
}
});
t.start();
while (true){
System.out.println("main方法");
}
}
}
注:
上面我们通过覆写 run 方法创建一个线程对象,但线程对象被创建出来并不意味着线程就开始运行了。覆写 run 方法是提供给线程要做的事情的指令清单,run不会创建新的线程,其是在main线程中执行的。调用 start 方法, 才真的在操作系统的底层创建出一个线程。
三、线程中断
1、手动设置标志位
使用自定义的变量来作为标志位。
public class demo01 {
public static boolean isQuit=false;
public static void main(String[] args) {
Thread t=new Thread(()->{
while (!isQuit){
System.out.println("hello 线程1");
try {
Thread.sleep(1000);
} catch (InterruptedException e) {
throw new RuntimeException(e);
}
}
System.out.println("线程1终止");
});
t.start();
try {
Thread.sleep(3000);
} catch (InterruptedException e) {
throw new RuntimeException(e);
}
//修改
isQuit=true;
}
}
注:当把isQuit从成员变量改成局部变量时无法正常发挥作用。Java要求变量捕获,捕获的变量必须是final或“实际final”。
2、调用 interrupt() 方法
Thread内部包含一个boolean类型的变量作为线程是否被中断的标记。
可以使用Thread.interrupted()或Thread.currentThread().isInterrupted()代替自定义标志位。
方法 | 说明 |
public void interrupt() | 中断对象关联的线程,如果线程正在阻塞,则以异常方式通知, 否则设置标志位 |
public static boolean interrupted() | 判断当前线程的中断标志位是否设置,调用后清除标志位 |
public boolean isInterrupted() | 判断对象关联的线程的标志位是否设置,调用后不清除标志位 |
例:
public class demo02 {
public static void main(String[] args) {
Thread t=new Thread(()->{
//currentThread是获取到当前线程实例(t)
//isInterrupted是t对象里自带的一个标志位
while (!Thread.currentThread().isInterrupted()){
System.out.println("hello 线程2");
try {
Thread.sleep(1000);
} catch (InterruptedException e) {
try {
Thread.sleep(2000);
} catch (InterruptedException ex) {
throw new RuntimeException(ex);
}
break;
}
}
});
t.start();
try {
Thread.sleep(3000);
} catch (InterruptedException e) {
throw new RuntimeException(e);
}
//将t内部的标志设置成true
t.interrupt();
}
}
interrupt()方法的作用:
- 设置标志位为true
- 如果该线程正在阻塞(sleep/join/wait)中,此时会将阻塞状态唤醒并通过抛出异常的方式让sleep立即结束。当sleep被唤醒时,sleep会自动把isInterrupted标志位给清空(true->false),这就导致下次循环仍然可以继续执行。可以在catch语句中增加break来结束循环。
四、线程等待
线程之间是并发执行的,操作系统对于线程的调度是无序的,无法判断两个线程的执行先后顺序。有些时候,我们需要能控制线程之间的顺序,线程等待就是其中一种控制线程执行顺序的手段,此处的等待主要是控制线程结束的先后顺序。
方法 | 说明 |
public void join() | 等待线程结束 |
public void join(long millis) | 等待线程结束,最多等 millis 毫秒 |
public void join(long millis, int nanos) | 同理,但可以更高精度 |
public class demo03 {
public static void main(String[] args) throws InterruptedException {
Thread t=new Thread(()->{
System.out.println("hello 线程3");
});
t.start();
//在main方法中等待t执行完后再执行main方法
t.join();
System.out.println("hello main");
}
}
上图中我们通过在main方法中调用join()方法让main线程阻塞从而使“线程3”先执行,直到“线程3”执行完毕后main线程才从阻塞中解除继续执行。因此想让哪个线程阻塞,就要在哪个线程里调用join()方法。但当main线程调用join()方法时“线程3”已经结束,此时不再阻塞会立即往下执行。
join()方法分类:
- 无参join()方法:“死等”直到程序结束。
- 有参join()方法:指定最大超时时间,如果达到时间上限就不再等待。
五、线程休眠
当代码中的某个线程调用sleep()方法时,这个线程就从就绪队列移至阻塞队列暂时不参与调度等待sleep()时间结束,而当到达sleep()的时间则又会被调度到就绪队列。
方法 | 说明 |
public static void sleep(long millis) throws InterruptedException | 休眠当前线程 millis毫秒 |
public static void sleep(long millis, int nanos) throws InterruptedException | 可以更高精度的休眠 |
public class Demo06 {
public static void main(String[] args){
Thread t=new Thread(()->{
while (true){
System.out.println("线程6");
try {
Thread.sleep(1000);
} catch (InterruptedException e) {
throw new RuntimeException(e);
}
}
});
t.start();
}
}
注:因为线程的调度是不可控的,所以sleep()方法只能保证实际休眠时间大于等于参数设置的休眠时间的。
六、获取当前线程实例
可以通过Thread.currentThread()的方式来调用,返回的是线程对象的引用,有时候也可以用this替代。