1.wait() 和notify()
调用wait后做的三件事
1.释放锁
2.等待其他线程通知
3.收到通知后重新上锁,继续执行
要想实用wait和notify就得搭配synchronized 获取锁
wait哪个对象就要针对哪个对象加锁
Java中线程是随机抢占式执行的,实际上线程的执行我们一定要有一个顺序.
join可以部分解决这个问题,但是A.join是让一个线程A挤入正在运行的线程B直到A执行完毕,才执行刚才暂停的线程B.
wait和notify都是Object类的方法
调用wait就会使线程陷入阻塞状态
直到其他线程调用notify来通知这个阻塞状态的线程,它才开始执行
2.线程的状态
3.关于多线程的案例
1.实现一个线程安全版本的单例模式
单例模式:是一个设计模式之一,设计模式就是棋谱,一些固定的代码套路
常见的设计模式就是单例模式和工厂模式
单例模式:就是一个类只能有一个实例,不能有多个,在实际开发中是很常见的
单例模式Singleton分为
饿汉模式(饿指的是着急):中午吃饭用了4个碗,吃完饭以后立即把碗给洗了(很着急)
懒汉模式:中午吃饭用了4个碗,吃完饭以后,先不洗.晚上这顿只需要两个碗,然后只洗两个即可,如果明天早上再用一个碗那么只洗一个碗 (很懒,洗的碗树=剩余没洗的-这次要用的)
懒汉模式比较好,效率更高,不用重复洗碗了
懒汉模式线程不安全,因为多线程的时候,调用get方法的时候,方法内部有读和写的操作,不是原子性 .懒汉模式要加锁,但是不能每次进入这个方法就要加锁,因为线程不安全只可能发生在第一次的读写读操作中,一旦实例了,后面只有两个读,后面就线程安全了,但是每次进入还要有锁操作,代价比较大,所以需要优化着来写
这里解释下为什么两层if一样还要写两次,是因为他们的目的不同,第一次是为了判断是否要加锁的,第二次是为了判断是否要创建一个新的对象实例的. 并且最重要的是在多线程中加了锁以后可能会阻塞,10:00执行的加锁的前面的,可能10:30才执行到加锁的后面的,如果中间过程instance被修改了,那么就需要重新判断,如果不重新判断的话,就一定会创建个对象的实例,就达不到单例模式了
4.阻塞队列
首先
阻塞队列用在生产者消费者模型中,用于作为他们的交易场所,减少了耦合度
这里解释一下耦合度:如果A和B的耦合度比较高,可以认为它们有直接的联系,如果修改A就要考虑B,同样如果修改B也要考虑A,并且B或者A挂了,可能会导致A或者B挂.
使用生产者消费者模型就能降低耦合
生产者消费者模型中,使用了交易场所
A只需要关注如何与队列交互,不需要关注B
B也是
生产者消费者模型的优点
1.能够解耦合
2.能够"削峰填谷"
"削峰"就是如果请求量一下增大A的请求只是通过交易场所传给B,A不会有啥大问题,但是如果没有交易场所的话B可能处理不了大量的请求,可能会崩. 如果有交易场所,B就会保护的很好,因为阻塞满了以后不会再入队请求
"填谷"就是如果请求量回复正常的话,交易场所(阻塞队列)中的请求还在,还会给B进行处理,B还在高速的频率处理着问题
5.指令重排序
volatile 解决了指令重排序和内存可见性问题
synchronized解决了内存可见性和原子性的问题还可以保证禁止指令重排序
6.定时器Timer
实现首先是执行任务的类(Task ),然后是Timer类
Timer类底层的数据结构是堆 PriorityQueue
自定义类型的比较要继承Comparable 接口然后重写compareto
什么时候被唤醒计算出来用wait唤醒
sleep不能中途唤醒,wait能
7.线程池
线程比进程更轻量,但是如果还是存在大量的线程,创建和销毁的代价依然不小
所以我们设计了线程池,线程创建后放到线程池中,需要用的时候从线程池去拿,用完后在放到线程池中。这样快,因为一般线程创建和销毁和使用需要牵涉到“内核态”,它具有不确定性,也就创建你的线程后可能会做其他任务,而如果放入线程池中,使用线程就是“用户态”,它比较可靠,只需完成用户指定的操作即可
一般认为“用户态”比内核态更加高效(具有可控性)