Thread


一个线程就是一个执行流程. 每个线程都可以按照顺序执行自己的代码, 而多个线程可以同时执行多份代码

1.生命周期

在这里插入图片描述
Thread有六个生命周期

  1. 新建(NEW)
    当一个Thread对象被创建时,它处于新建状态。在这个阶段,线程还没有启动。
public static void main(String[] args) throws Exception{
    System.out.println("Thread State is:"+new Thread().getState());
}
  1. 就绪(Runnable)
    当调用线程对象的start()方法后,线程进入就绪状态。在就绪状态下,线程已经准备好运行,但还没有得到 CPU 时间片。
public static void main(String[] args) {
   new Thread(() -> {
        System.out.println("Thread State is:"+Thread.currentThread().getState());
    }).start();
}
  1. 运行(Running)
    线程获得CPU时间片后,进入运行状态。在运行状态下,线程执行具体的任务代码。
  2. 阻塞(Blocked)
    线程在某些情况下会由于某些原因放弃CPU时间片,进入阻塞状态。例如,线程等待某个资源的释放,或者调用了sleep()方法。
public class ThreadTest {

    public static void main(String[] args) throws InterruptedException {
        Object lock = new Object();//锁
        BlockThread t1 = new BlockThread(lock,"T1");
        BlockThread t2 = new BlockThread(lock,"T2");
        t1.start(); //线程 T1开始运行
        t2.start(); //线程 T2开始运行
        Thread.sleep(100);  //阻塞主线程,等待T1,T2抢锁
        System.out.println("Thread T1 State is " + t1.getState());  //获取T1线程状态
        System.out.println("Thread T2 State is " + t2.getState());  //获取T2线程状态
    }
}

class BlockThread extends Thread {
    private String name;    //当前线程名称
    private Object lock;    //锁

    public BlockThread(Object lock, String name) {
        this.lock = lock;
        this.name = name;
    }

    @Override
    public void run() {
        System.out.println("Thread " + name + " State is " + Thread.currentThread().getState());
        synchronized (lock) {
            System.out.println("Thread " + name + " hold the lock");
            try {
                System.out.println("Thread " + name + " State is " + Thread.currentThread().getState());
                Thread.sleep(1000 * 10);    //抢到锁的线程执行逻辑,这里用睡眠模拟
            } catch (InterruptedException e) {
                e.printStackTrace();
            }
            System.out.println("Thread " + name + " release the lock");
        }
    }
}
  1. 等待(Waiting)
    线程在等待某个条件的触发时进入等待状态。例如,调用Object.wait()方法或者 Thread.join()方法。
public class ThreadTest {

    public static void main(String[] args) throws InterruptedException {
        Object lock = new Object();
        WaitingThread t = new WaitingThread("T", lock);
        t.start();
        Thread.sleep(1000);
        System.out.println("Thread T State is " + t.getState());
        System.out.println("Thread "+Thread.currentThread().getName()+" State is " + Thread.currentThread().getState());
    }
}

class WaitingThread extends Thread {
    private Object lock;
    public WaitingThread(String name, Object lock) {
        super(name);
        this.lock = lock;
    }

    @Override
    public void run() {
        System.out.println("Thread " + Thread.currentThread().getName()+" try to wait");
        synchronized (lock) {
            try {
                lock.wait();
            } catch (InterruptedException e) {
                e.printStackTrace();
            }
        }
    }
}
  1. 超时等待(Timed Waiting): 类似于等待状态,但有一个超时时间。例如,调用 Thread.sleep() 方法或者带有超时参数的 Object.wait() 方法。
  2. 终止(Terminated)
    线程执行完毕或者因异常退出时,进入终止状态。线程一旦终止,就不能再进入其他状态。

阻塞和等待的区别

阻塞状态等待状态
原因当线程试图获得一个对象的锁, 但该锁已经被其他线程持有时, 线程会进入阻塞状态当线程在某个对象上调用Object.wait()方法时, 它会进入等待状态, 等待期间会释放对象的锁
解除当持有锁的线程释放锁时, 被阻塞的线程有机会重新竞争锁, 并进入就绪状态线程可以通过调用Object.notify()或Object.notifyAll()来被唤醒,也可以在等待期间等待超时
是否会释放持有的锁在阻塞状态下, 线程仍然保持对锁的所有权在等待状态下, 线程会释放锁, 让其他线程有机会获得锁

阻塞状态结束后, 线程重新进入就绪状态,
在等待状态中, 线程可以被唤醒, 被唤醒的线程将重新进入就绪状态, 并尝试获取对象的锁

2.构造方法

Thread() :创建一个默认设置的线程对象实例
Thread(Runnable target) :创建一个包含可执行对象的线程实例
Thread(Runnable target, String name) :创建一个包含可执行对象,指定名称的线程对象
Thread(String name):创建一个指定名称的线程对象
Thread(ThreadGroup group, Runnable target) :创建一个指定线程组,包含可执行对象的线程对象实例
Thread(ThreadGroup group, Runnable target, String name) :创建一个指定线程组,包含可执行对象,指定线程名称的线程对象实例
Thread(ThreadGroup group, Runnable target, String name, long stackSize) :创建一个指定线程组、包含可执行对象、指定名称以及堆栈大小的线程对象实例
Thread(ThreadGroup group, String name):创建一个指定线程组,线程名称的线程实例

3.创建线程的方式

1.继承Thread类

public class ThreadTest {

    public static void main(String[] args) throws InterruptedException {
        // 继承Thread
        MyThread thread1 = new MyThread();
        thread1.start();

        // 匿名内部类
        Thread thread2 =new Thread() {
            @Override
            public void run() {
                System.out.println("Hello World 2");
            }
        };
        thread2.start();
    }
}

class MyThread extends Thread {
    @Override
    public void run() {
        System.out.println("Hello World");
    }
}

2.实现Runnable接口

public class ThreadTest {

    public static void main(String[] args) throws InterruptedException {
        // 继承Thread
        Thread thread1 = new Thread(new MyRunnable());
        thread1.start();

        // 匿名内部类
        Thread thread2 = new Thread(new Runnable() {
            @Override
            public void run() {
                System.out.println("Hello World 2");
            }
        });
        thread2.start();
    }
}
class MyRunnable implements Runnable {
    @Override
    public void run() {
        System.out.println("Hello World");
    }
}

3.实现Callable接口
FutureTask 是一个类,它实现了 Runnable 和 Future 接口

Callable不能直接交给Thread来运行, 我们可以使用FutureTask包装Callable, 让它变成一个Runnable

public class FutureTaskThreadDemo {
    public static void main(String[] args) {
        // 创建一个实现 Callable 接口的匿名类实例,该实例在 call() 方法中返回一个字符串
        Callable<String> callableTask = new Callable<String>() {
            @Override
            public String call() throws Exception {
                // 在这里执行你的任务,并返回结果
                String result = "Hello from Callable!";
                return result;
            }
        };
 
        // 使用 Callable 任务创建一个 FutureTask 实例
        FutureTask<String> futureTask = new FutureTask<>(callableTask);
 
        // 创建一个新线程,并将 FutureTask 作为参数传递给它
        Thread taskThread = new Thread(futureTask);
 
        // 启动新线程,这将执行 FutureTask 中的 Callable 任务
        taskThread.start();
 
        try {
            // 从 FutureTask 对象中获取计算结果,如果任务尚未完成,此方法会阻塞等待
            String result = futureTask.get();
            System.out.println("Result: " + result);
        } catch (InterruptedException e) {
            // 当前线程在等待过程中被中断时抛出 InterruptedException
            e.printStackTrace();
        } catch (ExecutionException e) {
            // 计算任务抛出异常时抛出 ExecutionException
            e.printStackTrace();
        }
    }
}

4.常用的方法

getId() 获取线程的ID
getName() 获取线程的名称
getState() 获取线程的状态
getPriority() 获取线程的优先级
isDaemon() 是否为后台线程
isAlive() 判断线程是否存活
interrupt() 中断线程
isInterrupted() 判断线程是否被中断
join() 让一个线程等待另一个线程执行结束
public static void main(String[] args) throws InterruptedException {
    Thread thread = new Thread(() -> {
        int count = 5;
        while (count-- != 0) {
            System.out.println("子线程");
            try {
                Thread.sleep(1000);
            } catch (InterruptedException e) {
                throw new RuntimeException(e);
            }
        }

    }, "thread-0");

    //设置线程为后台线程
    thread.setDaemon(true);
    thread.start();

    System.out.println("线程的ID(JVM):"+thread.getId());
    System.out.println("线程的名字:"+thread.getName());

    //TimeUnit.SECONDS.sleep(1);
    System.out.println("线程的状态:"+thread.getState());
    System.out.println("线程的优先级:"+thread.getPriority());

    System.out.println("线程是否为后台线程:"+thread.isDaemon());

    //thread.interrupt();
    System.out.println("线程是否被打断:"+thread.isInterrupted());


    System.out.println("线程是否存活:"+thread.isAlive());
    thread.join();
    System.out.println("线程是否存活:"+thread.isAlive());
}

什么是后台线程?

前台线程后台线程
作用通常用于执行与客户端的交互任务, 前台线程可以影响用户体验,因此它们的响应速度较高用于执行不需要用户直接参与或感知的任务, 通常用于处理一些较为耗时的操作,如数据加载、文件处理等
是否阻塞通常用于执行与客户端的交互任务, 前台线程可以影响用户体验,因此它们的响应速度较高用于执行不需要用户直接参与或感知的任务, 通常用于处理一些较为耗时的操作,如数据加载、文件处理等
生命周期启动和结束通常与应用程序的启动和结束相一致生命周期通常独立于应用程序的生命周期

线程优先级有什么用?
用于告诉操作系统调度器在多个线程可运行时, 应该选择哪个先执行

具有较高优先级的线程在竞争CPU时更有可能被调度执行。高优先级的线程更有可能更早地得到执行,因此可以更快地响应某些事件。这对于实时系统和需要低延迟响应的应用程序非常重要

什么情况下会使用interrupt方法

  • 优雅的终止线程
    通过调用 interrupt() 方法,可以通知目标线程应该中断并终止执行。线程在检测到中断状态后可以采取适当的措施,例如释放资源、完成未完成的任务等。
  • 中断阻塞操作
    如果线程在执行阻塞操作(如 sleep()、wait()、join() 等)时,可以通过调用 interrupt() 来中断线程,以提前结束阻塞状态,使线程能够响应中断并采取相应措施。
  • 线程间通信
    interrupt() 方法可以用作线程间的一种通信机制。当一个线程希望通知其他线程进行某种操作时,可以通过中断其他线程来引起其注意。
  • 检查中断状态
    线程可以通过调用 isInterrupted() 或 Thread.interrupted() 来检查自己是否被中断,并相应地进行处理。这可以帮助线程在合适的时候做出决策,例如提前结束执行或执行一些清理工作。
  • 线程池关联
    在使用线程池时,interrupt() 方法可以用于中断某个任务的执行。线程池可以通过 shutdownNow() 方法中断所有线程池中的任务,这将调用每个任务的 interrupt() 方法。

interrupt()和stop()方法的区别
interrupt和stop方法都是用来中断线程的方法, 但它们在实现上有很大的不同, 在java中建议使用interrupt()而不是stop, 两者的区别如下

interrupt()stop()
作用Thread类的实例方法, 用于中断目标线程的执行Thread类的实例方法, 用于立即停止目标线程的执行
调用将线程的中断状态设置为true会导致线程立即终止,不会有机会去执行清理工作或完成未完成的任务

interrupt()是否会中断目标线程的执行

  • 如果线程处于阻塞状态(如 sleep()、wait()、join() 等)
    它会抛出InterruptedException, 并清除中断状态
  • 如果线程处于非阻塞状态
    仅仅只是将线程的中断状态设置为true

interrupt() 方法主要是一种协作机制,通知线程应该中断并终止执行

Java:关于Java中的线程中断的几种方法

join()方法的作用
作用是让一个线程等待另一个线程执行结束

如果线程A中调用了线程B的join()方法, 那么线程A将会等待线程B执行完成, 然后再继续执行

join方法有以下几个特点:

  1. 等待: 调用join的线程将阻塞, 直到被调用的线程执行完成
  2. 顺序执行: 如果在主线程中调用多个线程的join方法, 那么这些线程将按照调用join的顺序依次执行
public class ThreadTest {

    public static void main(String[] args) throws InterruptedException {
        Thread thread1 = new Thread(new MyRunnable(), "Thread-1");
        Thread thread2 = new Thread(new MyRunnable(), "Thread-2");

        // 启动线程1
        thread1.start();
        thread1.join();

        // 启动线程2
        thread2.start();

    }

}

class MyRunnable implements Runnable {
    public void run() {
        for (int i = 1; i <= 5; i++) {
            System.out.println(Thread.currentThread().getName() + ": " + i);
            try {
                Thread.sleep(100); // 模拟一些耗时操作
            } catch (InterruptedException e) {
                e.printStackTrace();
            }
        }
    }
}