java-Thread

名词

线程和进程

进程是程序资源管理的最小单位 ,同一进程中的多条线程共享该进程中的全部系统资源,如虚拟地址空间,文件描述符文件描述符和信号处理等等。但同一进程中的多个线程有各自的调用栈、寄存器环境、线程本地存储等信息。

进程和线程最大的区别就是操作系统给进程分配了虚拟内存空间 .

    进程是程序资源管理的最小单位
    线程是资源调度的最小单位。

并发与并行

并行:同时执行多个任务,在多核心CPU架构中,一个CPU核心运行一个线程,那么4核心CPU,可以同时执行4个线程
并发:同处理多个任务的能力,通常我们会通过TPS或者QPS来表示某某系统支持的并发数是多少。

img

线程状态转化图

下图的方法写过java 都很熟了 , 可能比较疑惑就是几个中间状态了

img

BLOCKEDWAITING , TIMED_WAITING 之的区别是怎么样的 ? 哪一个是比较消耗性能的 ?

下面是来自chatGPT 的回答 :

Java中的线程状态包括:NEW、RUNNABLE、BLOCKED、WAITING、TIMED_WAITING和TERMINATED。其中,BLOCKED和WAITING状态都表示线程被阻塞了,但它们的原因和特点是不同的。

BLOCKED状态:表示线程被阻塞,因为它想要获取一个对象的锁,但该锁已经被其他线程占用了。此时,线程会进入BLOCKED状态,等待其他线程释放该锁。

WAITING状态:表示线程等待另一个线程执行特定的操作,以便该线程可以继续执行。线程进入WAITING状态的原因可能是调用了Object.wait()方法、Thread.join()方法或LockSupport.park()方法。在WAITING状态下,线程不会占用CPU资源,只有当其他线程调用了notify()或notifyAll()方法,或者等待时间到达后,线程才会被唤醒。

因此,BLOCKED状态和WAITING状态的区别有两点

  • BLOCKED状态是由于竞争锁资源而被阻塞,而WAITING状态是由于等待其他线程执行特定操作而被阻塞。
  • 此外,在BLOCKED状态下,线程会一直尝试获取锁资源(这里让我们想到了轮询),而在WAITING状态下,线程不会占用CPU资源,只有等待被唤醒的时候才会继续执行。

线程中断

Thead.interrupt

使得线程中断 ,用法如下 :

// java.util.concurrent.locks.AbstractQueuedSynchronizer

static void selfInterrupt() {
	Thread.currentThread().interrupt();
}

线程有个中断的状态 , Thread 提供了一些方法来获取和设置这个中断状态 ,

// Thread 类 
   /**
   Interrupts this thread.
   
   Unless the current thread is interrupting itself, which is always permitted, the checkAccess method of this thread is invoked, which may cause a SecurityException to be thrown.

	If this thread is blocked in an invocation of the wait(), wait(long), or wait(long, int) methods of the Object class, or of the join(), join(long), join(long, int), sleep(long), or sleep(long, int), methods of this class, then its interrupt status will be cleared and it will receive an InterruptedException.

	If this thread is blocked in an I/O operation upon an InterruptibleChannel then the channel will be closed, the thread's interrupt status will be set, and the thread will receive a java.nio.channels.ClosedByInterruptException.
	
   **/
   public void interrupt() {
    ...
   }

   public static boolean interrupted() {
        return currentThread().isInterrupted(true);
   }

    public boolean isInterrupted() {
        return isInterrupted(false);
    }


    private native boolean isInterrupted(boolean ClearInterrupted);

如果一个线程被 Object.wait() , Object.wait(long) 或是 Thread.join() , Thread.sleep() , 调用后 , 改线程肯定处于 WAIT 或是 TIME_WAITED 状态 , 假如此时调用该线程的 interrupt 方法 , 该线程会收到一个 InterruptedException ,并且中断标志将会被重置 .

具体我们看一下下面的两个例子 , 这两个例子我在 java-ReentrantLock 也放出来过.

ReentrantLock 过程中的中断是不会抛异常的

    public void testInterrupted3(){
        ReentrantLock reentrantLock = new ReentrantLock();
        Thread thread = new Thread(() -> {
            reentrantLock.lock();
            System.out.println("thread execute");
            /*try {
                reentrantLock.lockInterruptibly();
            } catch (InterruptedException e) {
                e.printStackTrace();
            }*/
            boolean flag = true;
            while (flag) {

            }
            System.out.println("thread unlock ");
        });
        thread.start();
        Thread thread1 = new Thread(() -> {
            reentrantLock.lock();
            System.out.println("thread execute 1");
           /* try {
                reentrantLock.lockInterruptibly();
            } catch (InterruptedException e) {
                e.printStackTrace();
            }*/
            boolean flag = true;
            while (flag) {

            }
            System.out.println("thread unlock 1");
        });
        thread1.start();
        try {
            Thread.sleep(5000);
        } catch (InterruptedException e) {
            e.printStackTrace();
        }
        System.out.println("Sleep over");
        thread.interrupt();
        thread1.interrupt();
        System.out.println(thread.isInterrupted());
        try {
            Thread.sleep(5000);
        } catch (InterruptedException e) {
            e.printStackTrace();
        }
    }

执行这个方法 ,是不会抛出异常的 , 而下面这个则会

    public static void threadStopTest2() {

        /**
         * 当外部调用对应线程进行中断的信令时,如果此时该执行线程处于被阻塞状态,如;Thread.sleep(),Object.wait(),BlockingQueue#put、BlockingQueue#take 等
         * 那么此时通过调用当前线程对象的interrupt方法触发这些函数抛出InterruptedException异常。
         * 当一个函数抛出InterruptedException异常时,表示这个方法阻塞的时间太久了,外部应用不想等它执行结束了。
         * 当你的捕获到一个InterruptedException异常后,亦可以处理它,或者向上抛出。
         *
         * 抛出时要注意???:当你捕获到InterruptedException异常后,当前线程的中断状态已经被修改为false;
         * 此时你若能够处理中断,正常结束线程,则不用理会该值;但如果你继续向上抛InterruptedException异常,你需要再次调用interrupt方法,将当前线程的中断状态设为true。
         *
         */

        Thread testThread = new Thread(new Runnable() {
            @Override
            public void run() {
                try {
                    //外部调用信令,要中断该线程时,如果此时线程正在休眠或者阻塞中,则将会抛出异常
                    Thread.sleep(6000);
                } catch (InterruptedException e) {

                    System.out.println("异常" + Thread.currentThread().isInterrupted());
                    //此处由于要继续执行该线程,不执行线程中断,所以重新修改中断状态为true;
                    Thread.currentThread().interrupt();
                    //此时获取结果为true;
                    System.out.println("异常" + Thread.currentThread().isInterrupted());
                }
            }
        });
        testThread.start();

        //发出线程中断信令
        testThread.interrupt();
    }

运行结果如下 :

异常false
异常true

深入Thead.interrupt

以下内容来自 并发编程 (重要 , 要看一下) , 非原创

线程的终止有主动和被动之分,被动表示线程出现异常退出或者run方法执行完毕,线程会自动终止。主动的方式是 Thread.stop()来实现线程的终止,但是stop()方法是一个过期的方法,官方是不建议使用,理由很简单,stop()方法在中介一个线程时不会保证线程的资源正常释放,也就是不会给线程完成资源释放工作的机会,相当于我们在linux上通过kill -9强制结束一个进程。

那么如何安全的终止一个线程呢?

我们先看一下下面的代码,代码演示了一个正确终止线程的方法,至于它的实现原理,稍后我们再分析

img

代码中有两处需要注意,在main线程中,调用了线程的interrupt()方法、在run方法中,while循环中通过 Thread.currentThread().isInterrupted()来判断线程中断的标识。所以我们在这里猜想一下,应该是在线程中维护了一个中断标识,通过 thread.interrupt()方法去改变了中断标识的值使得run方法中while循环的判断不成立而跳出循环,因此run方法执行完毕以后线程就终止了。

img

这个方法里面,调用了interrupt0(),这个方法在前面分析start方法的时候见过,是一个native方法,这里就不再重复贴代码了,同样,我们找到jvm.cpp文件,找到JVM_Interrupt的定义

img

这个方法比较简单,直接调用了 Thread::interrupt(thr)这个方法,这个方法的定义在Thread.cpp文件中,代码如下

img

Thread::interrupt方法调用了os::interrupt方法,这个是调用平台的interrupt方法,这个方法的实现是在 os_*.cpp文件中,其中星号代表的是不同平台,因为jvm是跨平台的,所以对于不同的操作平台,线程的调度方式都是不一样的。我们以os_linux.cpp文件为例 , (java 就是个中间层 ,为各种平台做了实现)

img

通过上面的代码分析可以知道,thread.interrupt()方法实际就是设置一个interrupted状态标识为true、并且通过ParkEvent的unpark方法来唤醒线程。

  • 对于synchronized阻塞的线程,被唤醒以后会继续尝试获取锁,如果失败仍然可能被park (这里就验证了上一节 线程状态 BLOCK 和 WAIT 的区别 )
  • 在调用ParkEvent的park方法之前,会先判断线程的中断状态,如果为true,会清除当前线程的中断标识
  • Object.wait、Thread.sleep、Thread.join会抛出InterruptedException

这里给大家普及一个知识点,为什么Object.wait、Thread.sleep和Thread.join都会抛出InterruptedException?

首先,这个异常的意思是表示一个阻塞被其他线程中断了。然后,由于线程调用了interrupt()中断方法,那么Object.wait、Thread.sleep等被阻塞的线程被唤醒以后会通过is_interrupted方法判断中断标识的状态变化,如果发现中断标识为true,则先清除中断标识,然后抛出InterruptedException

需要注意的是,InterruptedException异常的抛出并不意味着线程必须终止,而是提醒当前线程有中断的操作发生,至于接下来怎么处理取决于线程本身,比如

  • 直接捕获异常不做任何处理
  • 将异常往外抛出
  • 停止当前线程,并打印异常信息

为了让大家能够更好的理解上面这段话,我们以Thread.sleep为例直接从jdk的源码中找到中断标识的清除以及异常抛出的方法代码

找到 is_interrupted()方法,linux平台中的实现在os_linux.cpp文件中,代码如下

img

找到Thread.sleep这个操作在jdk中的源码体现,怎么找?相信如果前面大家有认真看的话,应该能很快找到,代码在jvm.cpp文件中

img

注意上面加了中文注释的地方的代码,先判断is_interrupted的状态,然后抛出一个InterruptedException异常。到此为止,我们就已经分析清楚了中断的整个流程。

线程中断标识有两种方式复位,

  • 第一种是前面提到过的InterruptedException;
  • 另一种是通过Thread.interrupted()对当前线程的中断标识进行复位。

Thread.sleep

    public static native void sleep(long millis) throws InterruptedException;

在Java中,线程的调度是由JVM进行管理的。当一个线程调用Thread.sleep()方法时,它会进入阻塞状态,并将CPU时间片让给其他线程。在指定的时间到达后,线程会被JVM唤醒,并重新进入就绪状态,等待CPU分配时间片。

具体来说,当一个线程调用Thread.sleep()方法时,JVM会将该线程的状态设置为TIMED_WAITING,并将该线程从就绪队列中移除。然后,JVM会调用操作系统提供的定时器来实现线程的阻塞。在指定的时间到达后,定时器会向JVM发送一个信号,JVM会将该线程重新加入就绪队列中,等待CPU分配时间片。

需要注意的是,Thread.sleep()方法并不是精确的时间控制方式。在不同的操作系统中,定时器的精度和精确度可能会有所不同,因此线程可能会被阻塞的时间比指定的时间长一些。此外,线程的阻塞时间还受到系统负载、CPU时间片分配等因素的影响,因此Thread.sleep()方法不能保证在指定的时间内精确地唤醒线程。

Thread.join

Thread.join的作用和原理

这一篇写的非常好了, 可以看一下

简述一下就是 , 利用 wait 方法进行阻塞

img

那么什么时候进行释放呢 ? 等上一个线程线程结束的时候 , 进行唤醒 , 唤醒的逻辑在 JVM 的实现中

img

img

总结,Thread.join其实底层是通过wait/notifyall来实现线程的通信达到线程阻塞的目的;当线程执行结束以后,会触发两个事情,第一个是设置native线程对象为null、第二个是通过notifyall方法,让等待在previousThread对象锁上的wait方法被唤醒。

参考资料

posted @ 2023-06-25 10:36  float123  阅读(12)  评论(0编辑  收藏  举报