【Java核心基础知识】03 - 多线程并发(2)

多线程知识点目录

多线程并发(1)- https://www.jianshu.com/p/8fcfcac74033
多线程并发(2)-https://www.jianshu.com/p/a0c5095ad103
多线程并发(3)-https://www.jianshu.com/p/c5c3bbd42c35
多线程并发(4)-https://www.jianshu.com/p/e45807a9853e
多线程并发(5)-https://www.jianshu.com/p/5217588d82ba
多线程并发(6)-https://www.jianshu.com/p/d7c888a9c03c

四、终止线程的4种方式

4.1 正常运行结束

程序运行结束,线程自动结束。

4.2 使用退出标志退出线程

通过一个变量来控制循环,例如:最直接的方法就是设一个boolean类型的标志,并通过设置这个标志为true或false来控制while循环是否退出。

class ThreadSafe1 extends Thread{
    // 退出标识(volatile关键字保证同一时刻只能由一个线程来修改其值)
    public volatile boolean exitFlag = false;
    // 当 exit 为 true 时,while 循环退出
    public void run(){
        while (!exitFlag){
            System.out.println("Thread running ...");
        }
    }
}

4.3 Interrupt方法结束线程

使用interrupt()方法来中断线程有两种情况:

  • 线程处于阻塞状态
    如使用了sleep、同步锁的wait、socket中的receiver、accept等方法时,会使线程处于阻塞状态。当调用线程的interrupt()方法时,会抛出InterruptExcption异常。阻塞中的那个方法抛出这个异常,通过代码捕获该异常,然后break跳出循环状态,从而让我们有机会结束这个线程的执行。
    通常认为只要调用interrupt方法线程就会结束,实际上是错的,一定要先捕获InterruptException异常之后,通过break来跳出循环,才能正常结束run方法。

  • 线程未处于阻塞状态
    使用isInterrupted()判断线程的中断标志来退出循环。当使用interrupt()方法时,中断标志会置true,和使用自定义的标志来控制循环是一样的道理。

class ThreadSafe2 extends Thread {
    public void run() {
        // 非阻塞状态通过终端标志来退出
        while (!isInterrupted()) {
            try {
                Thread.sleep(5000);
                // 阻塞过程捕获中断异常来退出
            } catch (InterruptedException e) {
                e.printStackTrace();
                // 捕获异常后,执行break跳出循环
                break;
            }
        }
    }
}

4.4 Stop方法终止线程(线程不安全)

Thread.stop()的工作原理:这个方法会强制终止线程,就像突然关闭计算机电源一样,而不是像正常程序那样进行有序的关机。当 Thread.stop() 被调用时,它会立即终止线程,不论该线程是否处于执行状态。
然而,这种立即的终止可能会导致一些问题。比如,如果线程正在执行一个包含加锁的代码块(通常是为了保护数据的一致性),然后这个线程被 Thread.stop() 强制终止,那么这个锁会被立即释放。这可能导致其他线程在访问这些被保护的数据时,数据已经处于不一致的状态,从而引发应用程序错误。
此外,当一个线程被 Thread.stop() 终止时,创建这个线程的线程会抛出 ThreadDeath 错误。这可能会给开发者带来困扰,因为他们可能需要在捕获和处理这种错误的情况下,重新设计他们的应用程序。
因此,不推荐使用stop方法来终止线程。
一般推荐使用更安全的方法来结束线程。例如,你可以设置一个标志位来让线程自行决定何时结束执行,而不是由外部强制终止。这样,线程就可以在适当的时间和地点释放资源并结束执行,同时不会破坏数据的一致性。

class ThreadSafe3 {
    public static void main(String[] args) {
        Thread thread = new Thread(new Runnable() {
            @Override
            public void run() {
                while (!Thread.currentThread().isInterrupted()) {
                    // 模拟线程执行任务
                    try {
                        System.out.println("Thread running ... ");
                        Thread.sleep(1000);
                    } catch (InterruptedException e) {
                        System.out.println("catch InterruptedException ... ");
                        // 恢复中断状态
                        Thread.currentThread().interrupt();
                        break;
                    }
                }
            }
        });
        thread.start();
        // 等待一段时间后终止线程
        try {
            Thread.sleep(3000);
        } catch (InterruptedException e) {
            e.printStackTrace();
        }
        // 中断线程
        System.out.println("Begin thread.stop() ... ");
        try {
            thread.stop();
        } catch (ThreadDeath e) {
            // 捕获并处理ThreadDeath异常
            e.printStackTrace(); 
        }
        System.out.println("End thread.stop() ... ");
    }
}
运行结果

五、Sleep和Wait的区别

  • Sleep 方法是在Thread类中定义的,它会导致当前线程暂停执行指定的时间。这个方法是public的,可以被任何线程调用。sleep方法通常用于让线程暂停执行一段时间,以实现某些时间延迟或定时操作。
Thread.sleep(1000);  // 暂停当前线程1秒钟
  • Wait方法是在Object类中定义的,它是synchronized方法,通常用于线程间的协作。当一个线程调用对象的wait方法时,它会释放该对象的锁,使得其他线程可以获取该对象的锁并访问它。然后,该线程会进入等待状态,直到其他线程调用同一个对象的notify或notifyAll方法来唤醒它。
synchronized (someObject) {  
    while () {  
        someObject.wait();  
    }  
}
  • 主要区别
    • sleep方法会导致线程进入阻塞状态,不能响应任何中断或异常,直到指定的时间过去后才会重新进入就绪状态。它不会释放任何锁。

    • wait方法会导致线程进入等待状态,并且会释放对象的锁,使得其他线程可以访问该对象。它可以被中断或超时异常打断,并且可以响应中断。

    • sleep方法是针对当前线程的,而wait方法是针对对象本身的。在多线程环境中,wait方法常用于实现线程间的协作和同步。

    • 通常,在编写多线程程序时,使用wait和notify方法进行线程间的通信和同步比使用sleep方法更安全和可控。

六、Start和Run的区别

  1. 位置:run方法属于Thread类中的一个普通方法,而start方法是Thread类中的一个启动方法。
  2. 执行方式:直接调用run方法,会像普通方法一样在当前线程中顺序执行run方法的内容。而调用start方法会创建一个新的线程,并在新的线程中并行执行run方法的内容。
  3. 线程状态:当我们调用start()方法启动一个新线程时,该线程会进入就绪状态,等待JVM调度它和其他线程的执行顺序。而当我们直接调用run()方法时,则会在当前线程中执行,不会产生新的线程。
  4. 作用:run方法的作用是存放任务代码的,而start的方法的作用是启动线程,线程启动以后它会自动去执行run方法。
  5. 线程数量:run方法在执行过程中不会产生新的线程,而start方法在执行过程中会产生一个新线程。

你可能感兴趣的:(【Java核心基础知识】03 - 多线程并发(2))