From f5f8ffd074d48d8ad8c48609cfb497598353c19a Mon Sep 17 00:00:00 2001 From: Omooo <869759698@qq.com> Date: Mon, 22 Jun 2020 19:13:43 +0800 Subject: [PATCH] Update --- blogs/Java/口水话/并发相关口水话.md | 45 +++++++++++++++++++ blogs/Java/口水话/线程池.md | 2 + 2 files changed, 47 insertions(+) create mode 100644 blogs/Java/口水话/并发相关口水话.md diff --git a/blogs/Java/口水话/并发相关口水话.md b/blogs/Java/口水话/并发相关口水话.md new file mode 100644 index 0000000..48de563 --- /dev/null +++ b/blogs/Java/口水话/并发相关口水话.md @@ -0,0 +1,45 @@ +--- +并发相关口水话 +--- + +#### 目录 + +1. 进程和线程的区别? +2. 创建线程的方式? +3. 线程的生命周期? +4. 一些 Object、Thread 方法的理解? + 1. wait、notify/notifyAll 等待通知机制 + 2. Thread 的 sleep、join、yield 和线程中断 +5. 线程死锁 + +#### 进程和线程区别? + +进程是操作系统资源分配的基本单位,而线程是 CPU 调度和分配的基本单位。进程有独立的地址空间,一个进程崩溃后在保护模式下不会对其他进程产生影响,而线程只是一个进程中的不同的执行路径,所以也可以说进程是执行的程序,线程是进程内不同的执行控制流。当然,进程是执行的程序,这是一种非正式的说法,进程不只是程序代码,进程还包括当前活动,如进程堆栈和数据段等等。 + +操作系统内的每个进程表示,采用进程控制块 PCB,它包含很多和进程相关的信息,如进程状态、CPU 寄存器、CPU 调度信息等等。而线程也有一个 TCB 线程控制块表示,它包括线程 ID、程序计数器、堆栈等等。 + +#### 创建线程的方式? + +创建线程有两种方式,一种是直接 new Thread 重写它的 run 方法,还有一种是实现 Runnable 接口传给 Thread。这两种方式都是没法获取任务执行结果的,如果需要获取任务执行结果,就需要使用到了 FutureTask。而且因为历史设计的原因,Thread 只接受 Runnable 而不接受 Callable,而 FutureTask 就是是 Runnable 和 Callable 的包装,本身是继承 Runnable 的,所以可以直接传给 Thread,调用其 get 方法就可以获取到执行结果,如果任务没有执行完,无参 get 就会一直阻塞,当然也可以使用 超时 get,超过一定时间就返回 null。 + +#### 线程的生命周期? + + + +#### 一些 Object、Thread 方法的理解? + +首先就是线程的等待通知机制,等待通知机制设计到了两个函数 wait 和 notify/notifyAll,它们就是 Object 的方法,wait 函数是当一个线程调用了共享变量的 wait 方法时,该调用线程就会被阻塞挂起,直到其他线程调用了该共享变量的 notify/notifyAll 方法唤醒。需要注意的是,调用 wait 方法的线程需要先获取该对象的监视器锁,不然就会抛出 IllegalMonitorStateException,那么如何获取一个共享变量的监视器锁呢?其实就是加 synchronzied 即可。调用 wait 方法会释放当前共享变量的监视器锁,不然就会死锁了。wait 还有一个超时重载函数,如果在指定时间没有被唤醒就直接返回了,无参 wait 调用的其实就 wait(0) 方法。调用 notify 函数是随机唤醒一个等待线程,而 notifyAll 就是唤醒所有等待线程。同 wait 一致,只有获取了共享变量的监视器锁后,才可以调用其 notify/notifyAll 方法。 + +还有等待线程执行完成的 join 方法,以及让线程休眠的 sleep 方法,sleep 方法和 wait 方法不同的是,sleep 并不会释放锁。Object#wait、Thread#join、Thread#sleep 在阻塞期间,其他线程调用了该线程的 interrupt 方法中断线程,都会抛出 InterruptedException。 + +其次就是让出 CPU 执行权的 Thread#yield 方法,让线程调度器进行下一轮的线程调度,不过需要注意的是,下一轮调度的时候也有可能会再次调度到自己。yield 方法一般是用在自旋中。 + +最后就是线程中断了,线程中断是一种线程间的协作模式,通过设置线程的中断标志并不能直接中断线程的执行,而是被中断的线程根据中断状态自行处理。现在中断包括两个函数,一个 interrupt() 方法,中断线程,还有一个是 Thread 的静态方法 interrupted 方法,前者不会清楚标志位,后者会清除标志位。 + +#### 线程死锁 + +死锁是指两个或多个线程在执行过程中,因争夺资源而造成的一直相互等待而无法继续运行下去。 + +死锁产生的条件必须具备以下四个条件:互斥条件、请求并持有、不可剥脱和环路等待。想要避免死锁,只需要破坏掉至少一个构成死锁的条件即可,但是目前只有请求并持有和环路等待条件是可以被破坏的。造成死锁的原因其实和申请资源的顺序有很大关系,使用资源申请的有序性原则就可以避免死锁。 + +然后比较滑稽的是,现代操作处理死锁的办法是直接忽视。虽然看起来这似乎不是一个解决死锁问题的可行办法,但是确实大多数操作系统所采用的,代价是一个重要的考虑因素,对于许多系统,死锁很少发生(如一年一次),发生死锁就直接人工重启了。使用频繁的死锁预防、死锁避免和死锁检测与恢复相比,这种办法更便宜。 \ No newline at end of file diff --git a/blogs/Java/口水话/线程池.md b/blogs/Java/口水话/线程池.md index d6db0f0..2ff05cd 100644 --- a/blogs/Java/口水话/线程池.md +++ b/blogs/Java/口水话/线程池.md @@ -27,4 +27,6 @@ 需要注意的是,线程池使用 FutureTask 时如果把拒绝策略设置为 DiscardPolicy 和 DiscardOldestPolicy,并且在被拒绝的任务的 Future 对象上调用了无参 get 方法,那么调用线程会一直被阻塞。所以当使用 Future 时,尽量使用带超时时间的 get 方法,这样即使使用了 DiscardPolicy 拒绝策略也不至于一直等待,超时时间到了就会自动返回。如果非要使用不带参数的 get 方法那就只能重写 DiscardPolicy 的拒绝策略了,发现是 FutureTask 就调用 cancel,并且需要在 get 方法加 try-catch。 +或者可以参考 Android 的 AsyncTask 的做法,当发生拒绝策略时,复写的拒绝策略其实是再创建一个核心线程池可回收的线程池来处理任务。 + 至此,线程池就讲完了。 \ No newline at end of file