【昕宝爸爸小模块】守护线程、普通线程、两者之间的区别-LMLPHP



一、✅典型解析


在Java中有两类线程: User Thread(用户线程)Daemon Thread(守护线程)。用户线程一般用于执行用户级任务,而守护线程也就是 “ 后台线程 ”,一般用来执行后台任务,守护线程最典型的应用就是GC(垃圾回收器)。



1.1 ✅什么是守护线程(概念)


守护线程(Daemon Thread)是一种在后台提供服务的线程,它在程序运行时在后台提供一种通用服务。守护线程并不属于程序中不可或缺的部分,它的生死与整个进程的运行无关。


守护线程的主要作用是为其他线程提供服务,例如垃圾回收线程就是典型的守护线程。当所有的用户线程都已退出运行时,如果还有守护线程存在,JVM(Java虚拟机)会继续运行直到所有的守护线程也结束。当所有非守护线程结束时,如果没有守护线程存在,程序就会终止。


用户可以通过调用Thread类的 setDaemon(true) 方法将线程设置为守护线程模式,这样该线程就会在后台运行并一直服务其他线程。需要注意的是,守护线程不应该执行重要的操作,因为它的终止是不可控制的。


1.2 ✅守护线程会阻塞其他线程吗



当我们在Java中创建一个线程时,我们可以通过调用Thread类setDaemon(true)方法将其设置为守护线程。守护线程是在后台运行的,当没有用户线程在运行时,守护线程会自动终止。下面是一个示例。



/**
* @author xinbaobaba
* 如何创建一个守护线程
*/
public class DaemonThreadExample {
    public static void main(String[] args) {
        // 创建一个守护线程
        Thread daemonThread = new Thread(() -> {
            while (true) {
                // 守护线程的逻辑
                System.out.println("Daemon thread is running.");
                try {
                    Thread.sleep(1000);
                } catch (InterruptedException e) {
                    e.printStackTrace();
                }
            }
        });
        daemonThread.setDaemon(true); // 设置守护线程
        daemonThread.start(); // 启动守护线程

        // 主线程逻辑
        System.out.println("Main thread is running.");
        try {
            Thread.sleep(5000);
        } catch (InterruptedException e) {
            e.printStackTrace();
        }
        System.out.println("Main thread is exiting.");
    }
}


1.3 ✅守护线程有哪些优缺点


守护线程的优点:

  1. 守护线程在后台提供通用服务,可以减轻主线程的负担,使其专注于执行主要任务。
  2. 守护线程的创建和销毁成本相对较低,可以提高程序的启动速度和执行效率。
  3. 守护线程可以自动终止,不需要手动管理,减少了程序退出时的资源清理工作。

守护线程缺点:

  1. 守护线程的生命周期依赖于用户线程,如果用户线程全部退出,守护线程也会被终止,可能导致一些资源无法得到正确的清理。
  2. 守护线程不控制资源,这可能导致一些重要的资源在程序退出时仍被占用,影响程序的正确关闭。
  3. 守护线程的执行优先级较低,可能会影响程序的实时性和响应速度。

因此,在使用守护线程时需要注意权衡利弊,根据具体需求选择是否使用以及如何使用


1.4 ✅ 哪些场景下需要使用守护线程


守护线程适用于以下场景



注意:守护线程并不适合执行需要持续运行的任务,因为它们会在所有用户线程结束后自动退出。在实际开发中,我们可以根据具体需求来合理使用守护线程,提高程序的性能和可靠性


二、✅普通线程


2.1 ✅什么是普通线程(概念)



普通线程与守护线程不同,它在程序运行过程中一直存在,直到程序结束或手动终止。普通线程可以与其他线程并发执行,并在其生命周期内完成特定的任务。


在Java中,可以通过继承Thread类或实现Runnable接口来创建普通线程。普通线程的创建和管理需要更多的资源,因此需要注意线程的启动、同步、通信和异常处理等问题。


总结起来的话,普通线程是用户创建的常规线程,需要手动管理其生命周期,常用于执行需要持续运行的任务或并发操作


2.2 ✅如何创建线程


创建线程的方法主要有两种:继承Thread类和实现Runnable接口。


  1. 继承Thread类:创建一个线程类,继承Thread类,并重写run()方法。在run()方法中编写线程要执行的代码。创建线程对象时调用其构造函数,启动线程时调用start()方法。例如:


/**
* @author xinbaobaba
*/
public class MyThread extends Thread {
    public void run() {
        // 线程执行的代码
    }
}

MyThread thread = new MyThread();
thread.start();

  1. 实现Runnable接口:创建一个实现了Runnable接口的类,实现run()方法,在run()方法中编写线程要执行的代码。创建一个Thread对象,将实现Runnable接口的类的对象作为参数传递给Thread构造函数,调用Thread对象的start()方法启动线程。例如:


/**
* @author xinbaobaba
*/
public class MyRunnable implements Runnable {
    public void run() {
        // 线程执行的代码
    }
}

Thread thread = new Thread(new MyRunnable());
thread.start();

无论使用哪种方法创建线程,都需要调用start()方法启动线程,而不是直接调用run()方法。因为run()方法是普通方法,直接调用会立即执行,不会创建新的线程。而start()方法会启动一个新的线程来执行run()方法中的代码。


2.3 ✅如何开启线程


要开启线程,需要创建一个线程对象并调用其start()方法。


在Java中,可以通过继承Thread类或实现Runnable接口来创建线程。无论使用哪种方法,都需要实现run()方法,并在其中编写线程要执行的代码。


创建线程对象时,可以直接使用Thread类或通过实现Runnable接口的类来创建。然后,调用线程对象的start()方法来启动线程。


例如,以下是一个简单的Java代码示例,演示如何开启线程:



/**
* @author xinbaobaba
*/
public class MyThread extends Thread {
    public void run() {
        // 线程执行的代码
        System.out.println("Thread is running.");
    }
}

public class Main {
    public static void main(String[] args) {
        MyThread thread = new MyThread();
        thread.start(); // 启动线程
    }
}


2.4 ✅如何终止线程


终止线程的方法主要有三种

  1. 使用退出标志:在run()方法中设置一个boolean类型的标志,并通过设置这个标志为true或false来控制while循环是否退出。
  2. 使用stop方法:强制终止线程,但这种方法不推荐使用,因为stop和suspend、resume一样,也可能发生不可预料的结果。
  3. 使用interrupt方法:中断线程。可以通过捕获InterruptedException异常来控制线程的退出。

注意,终止线程时应该谨慎处理,避免资源泄漏和数据不一致等问题。在实际开发中,应该根据具体情况选择合适的方法来管理线程的生命周期


在Java中,可以通过设置一个boolean类型的变量作为线程的退出标志,在线程的while循环中不断检查这个标志,如果标志为true,则退出循环并结束线程。以下是一个示例代码:



/**
* @author xinbaobaba
*/
public class MyThread extends Thread {
    private boolean isRunning = true;

    public void run() {
        while (isRunning) {
            // 线程执行的代码
            System.out.println("Thread is running.");
        }
    }

    public void stopThread() {
        isRunning = false;
    }
}

public class Main {
    public static void main(String[] args) {
        MyThread thread = new MyThread();
        thread.start(); // 启动线程

        // 等待一段时间后终止线程
        try {
            Thread.sleep(5000);
        } catch (InterruptedException e) {
            e.printStackTrace();
        }
        thread.stopThread(); // 停止线程
    }
}


问:线程的三种终止方法,哪一种最常用呢 ?


线程的三种终止方法包括使用退出标志、使用stop方法和使用interrupt方法。其中,最常用的方法是使用interrupt方法


使用 interrupt方法 来终止线程是最常用的方法,因为它提供了一种优雅的方式来通知被停止的线程停止执行。通过调用线程的interrupt方法,可以向线程发送一个中断请求,线程可以选择在适当的时候响应这个请求并停止执行。这种方式的优点在于它不会立即强制停止线程,而是给线程一个机会来清理资源、释放锁等,从而避免出现资源泄漏或其他问题。此外,interrupt方法还提供了一种通用的机制,可以用于处理其他类型的中断请求,如用户界面中的取消操作等。


相比之下,使用退出标志或stop方法来终止线程并不常用。使用退出标志需要在程序中设置一个标志变量,当需要停止线程时将标志变量设置为true,然后等待线程自行停止。这种方式需要线程自行处理停止逻辑,而且可能会造成线程阻塞或死锁等问题。使用stop方法会强制停止线程,可能会导致资源泄漏或其他问题,因此不推荐使用。


综上所述,使用interrupt方法是线程终止最常用的方法,因为它提供了一种优雅、安全的方式来停止线程执行,并可以用于处理其他类型的中断请求。


2.5 ✅线程的同步和通信是什么


线程的同步和通信是实现多线程并发执行的必要机制。


线程同步是指线程之间的协调与合作,以确保它们能够按照正确的顺序执行,避免出现数据不一致或资源冲突的问题。线程同步可以通过互斥锁、条件变量、信号量等机制实现,以确保同一时刻只有一个线程访问共享资源。


线程通信是指线程之间传递数据或消息的机制。由于不同的线程可能执行在不同的处理器或不同的内存空间上,因此需要一种机制来在不同的线程之间传递信息。线程通信可以通过共享内存、消息队列、管道等机制实现,以确保线程之间能够正确地传递数据或状态信息。


线程的同步和通信是多线程编程中的重要概念,用于协调和管理不同线程的执行,以确保程序能够正确地运行并实现预期的功能。


Demo:




/**
* @author xinbaobaba
* 演示了线程同步和通信的基本概念
*/
public class SharedResource {
    private int count = 0;
    private final Object lock = new Object();

    public void increment() {
        synchronized (lock) {
            count++;
        }
    }

    public int getCount() {
        return count;
    }
}

public class MyThread extends Thread {
    private SharedResource resource;

    public MyThread(SharedResource resource) {
        this.resource = resource;
    }

    public void run() {
        for (int i = 0; i < 10; i++) {
            resource.increment();
            System.out.println("Thread " + Thread.currentThread().getId() + " count: " + resource.getCount());
        }
    }
}

public class Main {
    public static void main(String[] args) {
        SharedResource resource = new SharedResource();
        MyThread thread1 = new MyThread(resource);
        MyThread thread2 = new MyThread(resource);
        thread1.start();
        thread2.start();
        try {
            thread1.join();
            thread2.join();
        } catch (InterruptedException e) {
            e.printStackTrace();
        }
        System.out.println("Final count: " + resource.getCount());
    }
}

在上面的代码中,我们定义了一个SharedResource类,其中包含一个共享资源count和一个同步锁lock。increment()方法用于增加count的值,并在访问count时进行同步,以确保线程安全。MyThread类继承了Thread类,并在run()方法中模拟了对共享资源的并发访问。在Main类中,我们创建了两个MyThread对象,并启动它们。使用 join() 方法等待线程执行完成,然后输出最终的count值。


这个示例演示了线程同步的基本概念,通过synchronized关键字对共享资源进行同步访问,确保同一时刻只有一个线程能够修改count 的值。同时,通过线程通信的方式,每个线程在访问完共享资源后输出count的值,以便观察线程的执行顺序和共享资源的状态变化。


01-15 13:00