0%

线程

这里先说明一下,进程和线程是不同的

  • 进程:程序的执行过程,是一个独立的运行环境,持有资源和线程,相当于一个应用程序,操作系统在分配资源时把资源分配给进程(堆和方法区是属于进程的)
  • 线程:进程中执行的一个任务,系统中最小的执行单元,同一进程有多个线程,线程共享进程的资源,CPU资源分配给线程(程序计数器和栈是属于线程的)

创建一个线程Thread时,JVM将分配一大块内存到专为线程保留的特殊区域上,用于提供运行任务时所需的一切,包括:

  • 程序计数器,指明要执行的下一个JVM字节码指令,由于线程是占有CPU执行的基本单位,CPU是用时间片轮转的方式,当前线程时间片用完之后需要让出CPU,而程序计数器就是记录该线程让出CPU时的执行地址,等到下次分配到时间片时线程就可以从本身私有的计数器中指定的地址继续执行

  • 用于支持Java代码执行的栈,包含有关线程已到达当时执行位置所调用方法的信息(调用栈帧)以及每个正在执行的方法的所有局部变量

  • 第二个则用于native code执行的栈

  • 线程本地变量的存储区域

  • 用于控制线程的状态管理变量

每当调用一个方法时,当前程序计数器被推到该线程的栈上,然后栈指针向下移动以足够来创建一个栈帧,其栈帧里存储该方法的所有局部变量,参数和返回值。所有基本类型变量都直接在栈上,虽然方法中创建对象的任何引用都位于栈帧中,但对象本身存于堆中。

创建线程的方式

  • 继承Thread类,并且重写run方法

  • 实现Runnable接口,使用带参的Thread构造器来创建Thread对象

  • 实现Callable接口使用FutureTask的方式,使用带参的Thread构造器来创建Thread对象,该方式可以获取到线程执行的返回结果

    1
    2
    FutureTask<Integer> futureTask = new FutureTask<>(call);
    new Thread(futureTask).start();
  • 使用线程池

调用start()方法才会调用线程,如果直接调用run()方法,和普通的方法没有区别

实现Runnable接口比继承Thread的优势

  • 代码可被多个线程共享,适合多个相同的代码去处理同一个资源

  • 可以避免java单继承的限制

  • 增加程序的健壮性,代码和任务分离

方法介绍

  • join() 线程加入进来,要等待该线程执行完在继续执行join之后的代码,可以确保两个线程的执行顺序

    1
    2
    3
    t1.start();  
    t1.join(); // 等待t1线程执行完成,再执行t2线程
    t2.start();
  • yield() 暂停当前正在执行的线程,并执行其他线程,大多数情况下yield()方法会让出CPU使用权,进入就绪状态,重新争抢cpu

  • sleep() 在指定的时间内让当前正在执行的线程休眠,暂时让出指定时间的执行权,在这期间不参与CPU的调度,但是该线程所拥有的监视器资源不会让出

  • getState() 获取线程状态

  • wait() Object的wait()方法、notify()、notifyAll()方法必须要与synchronized(obj)一起使用,只能针对已经获取到obj锁的情况,否则会抛出”java.lang.IllegalMonitorStateException“异常

  • interrupt()方法,中断线程,但是不一定会被中断,只有在阻塞时才会被中断而抛出InterruptedException异常

    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    14
    15
    16
    public void interrupt() {
    if (this != Thread.currentThread())
    checkAccess();

    synchronized (blockerLock) {
    Interruptible b = blocker;
    // 只有被阻塞才会抛出异常
    if (b != null) {
    interrupt0(); // Just to set the interrupt flag
    b.interrupt(this);
    return;
    }
    }
    // 该方法只会设置中断标志为true,实际上并没有被中断,还是会继续执行
    interrupt0();
    }
  • isInterrupted()方法,检查线程是否被中断,如果是返回true

  • interrupted()方法,检查线程是否被中断,与isInterrupted()不同的是,该方法如果发现当前线程被中断,则会清除中断标志,且该方法是静态方法,判断的是当前调用线程的中断标志而不是调用该方法的实例对象的中断标志

sleep和wait的区别

  • sleep()是Thread的静态方法,wait()是Object的方法

  • sleep()和wait()虽然都释放CPU,但是如果线程持有对象锁资源的话,sleep()在休眠时不释放锁资源,wait()在等待时会释放锁资源

  • sleep()需要捕获异常,wait()不需要

  • sleep()可以在任意位置使用,wait()必须要在synchronized同步块内使用

线程阻塞

  • 当执行Thread.sleep方法时,会一直阻塞指定时间,或者阻塞被另一个线程打断
  • 当线程执行wait方法时,会一直阻塞到接到通知(notify方法)或者被中断或者经过指定时间为止

上下文切换

一般情况下使用多线程的时候,使用的线程个数会大于CPU个数,但是每个CPU同一时刻只能被一个线程使用,CPU资源采用了时间片轮转的策略为每个线程分配一个时间片,当前线程的时间片到后,会处于就绪状态让出CPU,这就是上下文切换,而在上下文切换时需要保存当前线程的执行现场,当再次执行时根据保存的执行现场信息恢复执行现场

线程上下文切换的时机:

  • 当前线程的CPU时间片用完处于就绪状态时
  • 当前线程被其他线程中断时