今天的重点是ThreadLocal,我看完视频却还是觉得自己理解的不是很清楚,于是我找到了这位大神的博客,写的非常仔细,细节拉满,内容也是诙谐轻松,并且很清晰明了。看完之后我对ThreadLocal的理解一下子提升非常多。
附上链接 https://blog.csdn.net/weixin_47184173/article/details/111992933
还有一些视频中有提起的有关JVM的知识:
引用的四种类型:
强引用StrongReference:
当我们new一个对象时,比如 Object obj = new Object();在虚拟机中的堆中会创建一个Object的实例,而obj存在于栈中,obj就像一个门牌号码,他指向了堆中的Object实例。 所有的new对象的引用都称呼为强引用。 只要栈中的引用还指向着堆中的实例,那么堆中的实例就不会被回收。
软引用SoftReference:
就是说我虽然指向了实例,但是虚拟机发现快报内存溢出异常了,需要垃圾回收,会先给软引用对象一个通知,然后优先回收弱引用,如果内存还是不够,再回收软引用。
弱引用WeakReference:
一定会被垃圾回收,只要JVM需要垃圾回收,弱引用的对象实例就会被立刻回收。
虚引用PhantomReference:
最弱的引用,几乎没有什么用。
除了ThreadLocal还讲了多线程的等待和唤醒机制:
线程间的协作
生产者消费者模型:
生产者和消费者模型,就是在生产者和消费者中间加一个容器来解决两者之间的强耦合问题,通俗来讲,生产者不停的生产,消费者不停的消费,而消费者消费的东西是生产者生产的,他们之间自然需要有个中间容器。就好像一把手枪,生产者生产子弹,而消费者消耗子弹,手枪就是承载子弹的容器,如果手枪没有子弹,生产者就生产子弹装满手枪,然后通知消费者来消耗子弹并等待消费者消耗完子弹,消费者消耗完了子弹将手枪放回通知生产者生产子弹,并等待生产者装满子弹,这样不断的循环。这个过程中生产者和消费者是隔离的,所谓的“手枪“其实是一个阻塞队列,生产者生产的产品不直接给消费者消费,而是扔给阻塞队列,这个阻塞队列就是来解决生产者和消费者之间的强耦合问题的。这就是生产者消费者模型。
总结一下:生产者消费者能够解决的问题如下:
- 生产与消费的速度不匹配
- 软件开发过程中解耦
那我们看看下面的方法:
Wait()方法:
1.wait()是Object里面的方法,而不是Thread里面的,这一点很容易搞错。它的作用是将当前线程置于预执行队列,并在wait()所在的代码处停止,等待唤醒通知。
2.wait()只能在同步代码块或者同步方法中执行,如果调用wait()方法,而没有持有适当的锁,就会抛出异常。wait()方法调用后悔释放出锁,线程与其他线程竞争重新获取锁
notify()方法:
在上面的代码中我们看到wait()调用以后线程一直在等待,在实际当中我们难免不希望是这样的,那么这个时候就用到了另一个方法notify方法:
1.notify()方法也是要在同步代码块或者同步方法中调用的,它的作用是使停止的线程继续执行,调用notify()方法后,会通知那些等待当前线程对象锁的线程,并使它们重新获取该线程的对象锁,如果等待线程比较多的时候,则有线程规划器随机挑选出一个呈wait状态的线程。
2.notify()调用之后不会立即释放锁,而是当执行notify()的线程执行完成,即退出同步代码块或同步方法时,才会释放对象锁。
notifyAll()方法
和上面的唯一区别就是它可以一次性唤醒所有等待队列中的线程。
等待和通知标准范式:
wait()和notify(),notifyAll()必须要在synchronized锁中。
wait()范式:
Syn(对象){
while(xxx条件){
对象.wait(); //wait方法被调用会释放锁
}}
notify() or notifyAll()范式:
Syn(对象){
//改变条件
对象.notify/notifyAll();//不会释放锁,一般将唤醒方法放在代码的最后。
}
notify和notifyAll应该用谁?
在多个线程都等待被唤醒的情况下,为了避免你想要唤醒的线程没有被唤醒,推荐使用notifyAll()方法,可以唤醒所有的方法,避免进入一个死锁的情况。
在单个线程等待会唤醒的情况下,推荐使用notify()方法。
OOM:
回收不了的内存叫内存泄漏, 内存不够叫内存溢出,发生内存泄漏的情况很容易造成内存溢出。
总结:
今天的重点是ThreadLocal,它将变量给每个线程做了一个副本,每个线程都可以访问这个变量副本,对原变量没有任何影响。我帖的链接讲ThreadLocal非常详细,我自己在上面就不一一写了。