Start:第三章
Page:133-199
Date:20190202
Title:多线程编程核心技术
线程间的通信
要点:
使用wait/notify实现线程间的通信
生产者/消费者模式的实现
方法join的使用
ThreadLocal类的使用
wait/notify机制
wait会在wait时立马暂停线程的运行,而notify则会运行完该同步方法后才释放锁。(看底层C++代码的时候发现,在wait的时候调用了exit方法释放同步锁,而notify则没有,则默认在方法运行完后释放。)
wait和notify的时候,要在需要调用这个方法的线程内,lock.wait/lock.notify,初步理解为,将当前线程放入lock的wait队列里,或者唤醒lock的wait队列中的一个(每个object自带一个objectMonitor,这个是C++的源码,在java只能看到关键字native修饰的方法名)。(随机唤醒)而notifyAll则是遍历唤醒所有等待线程。
发出notify时如果没有任何等待线程,则略过。如果notify的时候没有阻塞的线程,然后再阻塞,那么相当于只执行了阻塞。
多次notify可以唤醒多个不同的线程。
notifyAll后执行的可能是优先级高的,也可能是随机,取决于jvm的实现。(这里尽量少用优先级控制执行顺序,因为优先级存在一个概率性,而不是绝对性。)
每个对象锁又两个队列,一个是就绪队列(准备执行),一个是阻塞队列(进入阻塞)
wait的线程如果调用interrupt方法会报错:InterruptedException.(人家已经阻塞了你还让人家阻塞。)
Wait(long)等待到long时间,如果没有线程唤醒这个线程,到了Long毫秒后自动唤醒。(wait() == wait(0),无限等待,直到唤醒)
还有一个Wait(Long,int),如果int大于500000(纳秒)则Long+1,否则就Long毫秒
线程的状态:
new Thread >> Start >> Runnable >> 抢到资源 >> Running
生产者/消费者模式:
生产者线程和消费者线程分别用while(true)执行run
run中生产者进行生产,只生产一个产品,然后阻塞,直到消费者消费后,消费者唤醒生产者,告诉生产者:已经没有产品可以消费了,快起来生产。然后消费者阻塞。循环执行。
一对多和多对多的生产者和消费者关系:
一对多的时候,无论多的是生产者或者消费者,都有可能唤醒同类型的线程(及生产者唤醒生产者或者消费者唤醒消费者),陷入假死状态。
多对多同理。
可以用notifyAll来解决这个问题。唤醒的同类执行之后会wait,而如果是不同类则会进行,则只会产生一个产品/消费一个产品,然后唤醒异类(由于run中的方法是同步的,不存在脏读现象)。
线程间的字节流和字符流
PipedInputStream/PipedOutputStream
PipedRead/PipedWrite
和常用的IO流用法相似,这里不深度挖掘。
写入和备份的交叉运行:
这里用到的是volatile关键字,boolean类型,用来体现是写入还是存储。
而写入和存储的方法用Synchronized修饰成为同步方法。
则保证写入和备份交叉运行,且不会同时出现多个写入或者备份。
Join方法的使用
main(){ a.join()}
事实上是对a方法进行isAlive判断。如果a方法是活跃的(非阻塞),则阻塞调用线程--也就是main线程。直到a方法执行完。
join方法底层用的是wait方法实现的,和sleep看起来相同,但事实上sleep并不会释放锁,而join/wait会释放锁。
和wait相同,join(Long),join(Long,int)分别代表等待Long毫秒,或者(如果int大于500000(纳秒)则Long+1,否则就Long)毫秒
同理join() == join(0)
当三个方法同时争夺一个锁:
A,B,Main同时争夺一个锁B,且A先执行,执行时间大于main的b.join(等待)时间,会出现三种情况:A执行完,main发现自己的等待时间结束:
1.main与B争夺锁,且main胜出,先执行main
2.main与b争夺锁,且B胜出,先执行B
3.B,main同时争夺锁,不论谁胜出,最后执行输出的时候异步输出导致输出结果异常
ThreadLocal类的使用
多个线程同时调用ThreadLocal的get可以获取各自的值,互不干扰。
这里是因为get或者set的时候会把调用这个方法的线程带入。get自己ThreadLocalMap里的Entry或者set自己的ThreadLocalMap,事实上这里的Map还是Entry
(如果set的时候原来没有值则createMap,如果get的时候没有值则取到null)
为了避免get到Null可以写一个类继承ThreadLocal然后重写initValue方法,返回一个初始值即可(ThreadLocal的是返回一个null)。
InheritableThreadLocal类
继承自ThreadLocal,这个类使得子线程可以get父线程Set的值。这里将ThreadLocal中原本应该抛异常的childValue方法重写,返回的是父线程的值。
可以修改继承的值,只要写一个类继承InheritableThreadLocal类,并重写childValue方法即可。
若在子线程get的时候父线程set了,子线程取的仍然是旧值。