Java并发实现原理:JDK源码剖析 - (EPUB全文下载)
文件大小:0.96 mb。
文件格式:epub 格式。
书籍内容:
Java并发实现原理:JDK源码剖析
第1章 多线程基础
第2章 Atomic类
第3章 Lock与Condition
第4章 同步工具类
第5章 并发容器
第6章 线程池与Future
第7章 ForkJoinPool
第8章 CompletableFuture
好书分享
第1章 多线程基础
1.1 线程的优雅关闭
1.1.1 stop()与destory()函数
线程是“一段运行中的代码”,或者说是一个运行中的函数。既然是在运行中,就存在一个最基本的问题:运行到一半的线程能否强制杀死?
答案肯定是不能。在Java中,有stop()、destory()之类的函数,但这些函数都是官方明确不建议使用的。原因很简单,如果强制杀死线程,则线程中所使用的资源,例如文件描述符、网络连接等不能正常关闭。
因此,一个线程一旦运行起来,就不要去强行打断它,合理的关闭办法是让其运行完(也就是函数执行完毕),干净地释放掉所有资源,然后退出。如果是一个不断循环运行的线程,就需要用到线程间的通信机制,让主线程通知其退出。
1.1.2 守护线程
在下面的一段代码中:在main(..)函数中开了一个线程,不断循环打印。请问:main(..)函数退出之后,该线程是否会被强制退出?整个进程是否会强制退出?
答案是不会的。在C语言中,main(..)函数退出后,整个程序也就退出了,但在Java中并非如此。
对于上面的程序,在t1.start()前面加一行代码t1.setDaemon(true)。当main(..)函数退出后,线程t1就会退出,整个进程也会退出。
当在一个JVM进程里面开多个线程时,这些线程被分成两类:守护线程和非守护线程。默认开的都是非守护线程。在Java中有一个规定:当所有的非守护线程退出后,整个JVM进程就会退出。意思就是守护线程“不算作数”,守护线程不影响整个 JVM 进程的退出。例如,垃圾回收线程就是守护线程,它们在后台默默工作,当开发者的所有前台线程(非守护线程)都退出之后,整个JVM进程就退出了。
1.1.3 设置关闭的标志位
在上面的代码中,线程是一个死循环。但在实际工作中,开发人员通常不会这样编写,而是通过一个标志位来实现,如下面的代码所示。
代码1
但上面的代码有一个问题:如果MyThread t在while循环中阻塞在某个地方,例如里面调用了 object.wait()函数,那它可能永远没有机会再执行 while(!stopped)代码,也就一直无法退出循环。
此时,就要用到下面所讲的InterruptedException()与interrupt()函数。
1.2 InterruptedException()函数与interrupt()函数
1.2.1 什么情况下会抛出Interrupted异常
Interrupt这个词很容易让人产生误解。从字面意思来看,好像是说一个线程运行到一半,把它中断了,然后抛出了InterruptedException异常,其实并不是。仍以上面的代码为例,假设while循环中没有调用任何的阻塞函数,就是通常的算术运算,或者打印一行日志,如下所示。
这个时候,在主线程中调用一句t.interrupt(),请问该线程是否会抛出异常?答案是不会。
再举一个例子,假设这个线程阻塞在一个 synchronized 关键字的地方,正准备拿锁,如下代码所示。
这个时候,在主线程中调用一句t.interrupt(),请问该线程是否会抛出异常?答案是不会。
实际上,只有那些声明了会抛出 InterruptedException 的函数才会抛出异常,也就是下面这些常用的函数:
1.2.2 轻量级阻塞与重量级阻塞
能够被中断的阻塞称为轻量级阻塞,对应的线程状态是WAITING或者TIMED_WAITING;而像 synchronized 这种不能被中断的阻塞称为重量级阻塞,对应的状态是 BLOCKED。如图1-1所示的是在调用不同的函数之后,一个线程完整的状态迁移过程。
图1-1 线程的状态迁移过程
初始线程处于NEW状态,调用start()之后开始执行,进入RUNNING或者READY状态。如果没有调用任何的阻塞函数,线程只会在RUNNING和READY之间切换,也就是系统的时间片调度。这两种状态的切换是操作系统完成的,开发者基本没有机会介入,除了可以调用yield()函数,放弃对CPU的占用。
一旦调用了图中的任何阻塞函数,线程就会进入WAITING或者TIMED_WAITING状态,两者的区别只是前者为无限期阻塞,后者则传入了一个时间参数,阻塞一个有限的时间。如果使用了synchronized关键字或者synchronized块,则会进入BLOCKED状态。
除了常用的阻塞/唤醒函数,还有一对不太常见的阻塞/唤醒函数,LockSupport.park()/unpark()。这对函数非常关键,Concurrent包中Lock的实现即依赖这一对操作原语。
故而t.interrupted()的精确含义是“唤醒轻量级阻塞”,而不是字面意思“中断一个线程”。
1.2.3 t.isInterrupted()与Thread.interrupted()的区别
因为 t.interrupted()相当于给线程发送了一个唤醒的信号,所以如果线程此时恰好处于WAITING或者TIMED_WAITING状态,就会抛出一个InterruptedException,并且线程被唤醒。而如果线程此时并没有被阻塞,则线程什么都不会做。但在后续,线程可以判断自己是否收到过其他线程发来的中断信号,然后做一些对应的处理,这也是本节要讲的两个函数。
这两个函数都是线程用来判断自己是否收到过中断信号的,前者是非静态函数,后者是静态函数。二者的区别在于,前者只是读取中断状态,不修改状态;后者不仅读取中断状态,还会重置中断标志位。
1.3 synchronized关键字
1.3.1 锁的对象是什么
对不熟悉多线程原理的人来说,很容易误解 s ............
以上为书籍内容预览,如需阅读全文内容请下载EPUB源文件,祝您阅读愉快。
书云 Open E-Library » Java并发实现原理:JDK源码剖析 - (EPUB全文下载)