Skip to content

Latest commit

 

History

History
125 lines (74 loc) · 6.51 KB

01线程相关的基础知识.md

File metadata and controls

125 lines (74 loc) · 6.51 KB

来源:https://gitbook.cn/books/5aa7d7c824f0ce2205cbab78/index.html

线程的创建与运行

创建线程的三种方法:

  1. 实现 Runnable 接口
  2. 继承 Thread 类
  3. 实现 Callable 接口

线程通知与等待

void wait() 方法

当一个线程调用了一个共享对象的 wait() 方法时,调用线程会被阻塞挂起,发生下面几种情况之一会返回:

  1. 其它线程调用了该共享对象的 notify() 或者 nogityAll() 方法。
  2. 其它线程调用了该线程的 interrupt() 方法

如果调用 wait() 方法的线程没有事先获取到该对象的监视器锁,则调用 wait() 方法时将会抛出 IllegalMonitorStateExcepiotn

线程获取一个共享变量的监视器:

  1. 执行使用 synchronized 同步代码块,使用该共享变量作为参数。
  2. 调用该共享变量的方法,并且该方法使用 synchronized 修饰。

虚假唤醒:当一个线程从挂起状态变为可以运行转态(也就是被唤醒),即使该线程没有被其它的线程调用 notify()notifyAll() 进行通知,或者被中断,或者等待超时。

参考:对条件变量的讨论
简单点说就是:底层wait方法没有保证每次唤醒都是由notify触发,而是交给了上层应用去处理

虚假唤醒在实践当中虽然很少发生,但是还是要防范于未然。做法就是不同的去测试该线程被唤醒的条件是否满足。如下为经典的调用共享变量 wait() 的示例:

synchronized (obj) {
    while (条件不满足) {
        obj.wait();
    }
}

当一个线程调用了共享变量的 wait() 方法后该线程会被挂起,同时该线程会暂时释放对该共享变量监视器的持有,直到另外一个线程调用了共享变量的 notify 或者 notifyAll() 方法才有可能会重新获取到该共享变量监视器的持有权。

多个线程都调用了 wait() 方法,那么多个线程会竞争该共享变量的监视器 只会释放当前共享变量上的锁,如果当前线程还持有其他共享变量的锁,则这些锁是不会被释放的

void wait(long timeout)

如果没有在指定的 timeout 毫秒内,被其它线程调用该共享变量的 notify() 或者 notifyAll() 方法唤醒,那么该函数会因为超时而返回。

如果 timeout 为负数,将会抛出 IllegalArgumentException 异常

void notify()

一个线程调用共享对象的 notify() 方法后,会唤醒一个在该共享变量上调用 wait 方法后被挂起的线程。一个共享变量上可能会有多个线程在等待,具体唤醒哪一个等待的线程是随机的。

被唤醒的线程不能马上从 wait 返回继续执行,它必须获取了共享对象的监视器之后才会返回。但是被唤醒的线程也不一定可以获取到共享对象的监视器,因为它要跟其他线程竞争。

类似 wait,只有当前线程已经获取到该共享变量的监视器之后,才可以调用该共享变量的 notify() 方法,否则会抛出 IllegalMonitorStateException 异常。

void notifyAll()

唤醒所有在该共享变量上由于调用 wait 方法而被挂起的线程。

等待线程执行终止的 join 方法

等待某几件事情完成后才能继续往下执行 。比如多个线程去加载资源,当多个线程全部加载完毕后在汇总处理。

// 启动线程
threadOne.start();
threadTwo.start();

System.out.println("wait all child thread over!");

// 阻塞,等待线程执行完毕,再继续往下执行
threadOne.join();
threadTwo.join();

System.out.println("all child thread over!");

让线程睡眠的 sleep 方法

当一个执行中的线程调用了 Thread 类的 sleep 静态方法后,调用线程会暂时让出指定时间的执行权,也就是这期间不参与 CPU 的调度,但是该线程所拥有的监视器资源,比如锁还是持有不让出的。

当指定的睡眠时间到了该函数会正常返回,线程就处于就绪状态,然后参与 CPU 的调度,当获取到了 CPU 资源就可以继续运行了。

如果在睡眠期间其它线程调用了该线程的 interrupt() 方法中断了该线程,该线程会在调用 sleep 的地方抛出 InterruptedException 异常返回。

让出CPU 执行权的yield 方法

当一个线程调用yield方法时, 当前线程会让出CPU使用权,然后处于就绪状态,线程调度器会从线程就绪队列里面获取一个线程优先级最高的线程,当然也有可能会调度到刚刚让出CPU的那个线程来获取CPU执行权。

线程中断

线程中断是一种线程间协作模式,通过设置线程的中断标志并不能直接终止该线程的执行,而是需要被中断的线程根据中断状态自行处理。

  • void interrupt() 方法

当线程 A 运行时,线程 B 可以调用线程 A 的 interrupt() 方法来设置线程 A 的中断标志为 true 并立即返回。

设置标志仅仅是设置标志,线程 A 并没有实际被中断,会继续往下执行的。

如果线程 A 因为调用了 wait 系列函数或者 join 方法或者 sleep 函数而被阻塞挂起,这时候线程 B 调用了线程 A 的 interrupt() 方法,线程 A 会在调用这些方法的地方抛出 InterruptedException 异常而返回。

  • boolean isInterrupted()

检测当前线程是否被中断,如果是返回 true,否者返回 false

  • boolean interrupted()

检测当前线程是否被中断,如果是返回 true,否者返回 false,与 isInterrupted 不同的是该方法如果发现当前线程被中断后会清除中断标志。

守护线程与用户线程

Java 中的线程分为两类,分别为daemon线程(守护线程)和user线程(用户线程)。

当最后一个非守护线程结束时,JVM会正常退出,而不管当前是否有守护线程,也就是说守护线程是否结束并不影响JVM的退出。言外之意,只要有一个用户线程还没结束,正常情况下JVM就不会退出。

设置daemon线程:thread.setDaemon(true)

当父线程结束后,子线程还是可以继续存在的,也就是子线程的生命周期并不受父线程的影响。这也说明了在用户线程还存在的情况下JVM进程并不会终止

如果当前进程中不存在用户线程,但是还存在正在执行任务的守护线程,则JVM不等守护线程运行完毕就会结束JVM进程