Java多线程编程核心技术读书笔记二
条评论这篇读书笔记是《Java多线程编程核心技术》图书的最后一篇。由于写笔记的时候已经读完了整本书,留下了一个大致的印象——适合做多线程的入门读物,所以在读完后感觉缺点什么东西,顺手就打开了另一本《Java并发编程的艺术》,瞬间感觉当年书买得十分的合理,《Java并发编程的艺术》虽然没有开始读,但是大致看了几页,感觉这本书还是要深入一些,更加贴近原理,知识的密度要高很多。
第3章 线程间通信
这一章基本围绕“wait等待/notify通知” 机制来描述线程之间通信的方式。
wait/notify
其核心思想是处理好锁的获取,锁的释放,唤醒(本质是改变线程自己的状态)的关系。
每个锁都有两个队列,一个就绪,一个阻塞。就绪是要需要锁的(可参与锁竞争),阻塞是不需要锁的(被其他事情中断而不能参与锁的争夺,notify就是改变这个队列中的线程的状态,使其进入就绪状态)
某线程调用的lock对象的wait()方法,线程释放锁,进入waiting状态(线程的等待队列);
其他线程的lock对象调用notify()或notifyall()方法,则随机或全部讲waiting该锁的线程设置回runnable可执行状态,同时线程会在完成自己代码的执行后,释放自己占用的lock(注意不是notify动作的时候释放)。等待队列中的线程则被cpu随机调用执行。
这里要题外话,书上描述“如果线程抢占到cpu资源…………”,这个描述很容易让新手误解线程是主动的,实际上并不是,是线程自己的状态配置被其他获取到cpu资源的线程改变了,然后被cpu调用到了才对。
wait(long)可以加时间参数,等带long时间看看有没有被唤醒,超过long就自动唤醒。
使用wait/notify,需要避免混乱:如过早通知、等待条件发生改变
生产/消费者模式
生产者和消费者数量在{1,N}的笛卡尔积的组合,如何处理好同步。主要是利用wait/notify的技巧。
还介绍了以下管道进行线程间的通信。这个之前了解比较少就专门记录一下。
- PipedInputStream 和 PipedOutputStream,通过各自对象的connect(stream)方法建立进和出通道的联系
- PipedReader 和PipedWriter,同上
线程的join方法
线程若需要其他线程等待自己线程结束,可以使用的方法。为什么要等?比如等子线程执行后返回值。是被等待的线程调用join()方法来阻塞等待线程。join()具有使线程排队运行的作用,与synchronized的区别:join内部使用的wait()方法进行等待,而synchronized是使用的对象监视器做同步。
join(long) 同wait(long)。
join(long) 和sleep(long) 区别。锁释放阻塞和和锁持有等待(阻塞)。
类ThreadLocal
主要解决每个线程绑定自己线程的值。教程向我们展示了如何使用ThreadLocal,我这里做下变通自己写了测试代码。
1 | public class ThreadLocalDemo { |
通过查看源码, ThreadLocal
第4章 Lock的使用
这个章节主要在聊ReentrantLock和ReentrantReadWriteLock。
jdk1.5增加的锁,也能实现线程之间的同步和互斥,而且具备了嗅探锁定、多路分支通知等特性。
ReentrantLock
Lock是接口,ReentrantLock实现了这个接口、围绕ReentrantLock讲故事,故事的内容纷繁,我就截个图说明了。
lock的方法:
condition的方法:
最简单的情节,加锁,办事,解锁。
复杂一点的,等待condition,等别人在此confdition上面调用notify或notifyall。不同的condition可以精准唤醒。
然后教程展示了使用这套机制实现生产者\消费者模式
ReenTrantLock的方法
公平锁和非公平锁
公平锁,表示线程获取锁的顺序是按照线程加锁的顺序来分配,FIFO形式。反之就是非公平,非公平是随机的。
ReentrantReadWriteLock
更加细粒度的锁。
规则:读线程之间不互斥;读和写互斥,写和写互斥。
1 | ReentrantReadWriteLock lock= new ReentrantReadWriteLock(); |
使用起来大同小易。
第5章 Timer
这个章节主要介绍了定时/计划功能,它在移动端开发使用较多,其内部是使用多线程方式来处理的。细节也不纠结,比较简单。
第6章 单例模式和多线程
本章开篇,提及一个观点,多线程模式下,很多不良设计会给商业应用带来灾难。这是一个值得警醒的问题。
单例模式有饿汉和懒汉模式。
本质区别是单例对象的生成是在类加载时还是在单例方法被调用时。
出现线程安全问题是懒汉模式。
这里不断的优化单例模式。给方法加synchronized、给代码块加synchronized。文章说synchronized慢,至于为什么慢没有说,我姑且猜测是synchronized包裹的代码行越多,就导致多线程能并发执行的越少。
然后给出了DCL机制,double check lock。
1 | public class AloneInstance { |
其他使用静态代码块、静态内之类实现的单利模式,本质其实还是提前生成了单例对象。
还有一种天然线程安全的enum枚举实现单例模式。这块我之前有了解,但是枚举为什么是线程安全的继续深入了解了一下——编译后反编译的代码显示,它是静态的,恩静态的。毋庸置疑了。
最后,序列化的单例模式如何保证线程安全?反序列化类生成是通过反射机制来实现的,所以会导致单利模式的结构被破坏。要保证线程安全,就要使用静态方式、并增加一个获取实例的非静态方法。
1 | public class Singleton implements Serializable { |