多线程(3)-源码分析(ReentrantLock-AQS,ThreaLocal)、强软弱虚引用

本文主要是介绍多线程(3)-源码分析(ReentrantLock-AQS,ThreaLocal)、强软弱虚引用,希望对大家解决编程问题提供一定的参考价值,需要的开发者们随着小编来一起学习吧!

1. synchronized

public  void hello(){synchronized (this){System.out.println("a");}
}

经过编译的字节码

 0 aload_01 dup2 astore_13 monitorenter4 getstatic #2 <java/lang/System.out : Ljava/io/PrintStream;>7 ldc #3 <a>9 invokevirtual #4 <java/io/PrintStream.println : (Ljava/lang/String;)V>
12 aload_1
13 monitorexit
14 goto 22 (+8)
17 astore_2
18 aload_1
19 monitorexit
20 aload_2
21 athrow
22 return

monitorenter:进入对象监视器
monitorexit:退出监视器,释放锁。两个monitorexit的原因是1. 当正常执行完结束时,monitorexit退出。2. 当执行报错时,自动monitorexit,自动退出。

synchronized实现的原理:
每个对象都与一个监视器相关联。当且仅当监视器有所有者时,监视器才被锁定。执行monitorenter的线程 尝试获得与对象关联的监视器的所有权,如下所示:
如果与对象关联的监视器的条目计数 为零,则线程进入监视器并将其条目计数设置为 1。该线程然后是监视器的所有者。
如果线程已经拥有与对象关联的监视器 ,它会重新进入监视器,增加其条目计数。
如果另一个线程已经拥有与对象关联的监视器 ,线程会阻塞,直到监视器的条目计数为零,然后再次尝试获得所有权。

  1. 多线程同时调用synchronized块时,其中一个线程进入对象监视器,将条目计数为1,该线程然后是监视器的所有者,持有对象锁。
  2. 其他的线程进入监视器失败,会进入阻塞队列中
  3. 当持有锁的对象释放锁,监视器条目计数-1,当为0时,通知队阻塞队列中的线程尝试进入监视器,如果进入成功,该线程就持有锁。
  4. 如果持有锁的线程又一次去进入监视器,那么条目计数加1. synchronized重入。
    在这里插入图片描述

2. ReenTrantLock-AQS

  • volatile int state
  • cas
  • 原理
    https://blog.csdn.net/fuyuwei2015/article/details/83719444
  • lock() 源码
  • unlock() 源码

3. ThreadLocal

  • ThreadLocal 称为ThreadLocal变量,线程局部变量。一个ThreadLocal在一个线程中是共享的,在不同线程之间又是隔离的(每个线程都只能看到自己线程的值)。也就是说,同一个ThreadLocal对象,在不同的线程中是不一样的。
  • API
    set方法,向ThreadLocal中设置当前线程的的值。
    get方法,获取当前线程在ThreadLocal中设置的值。
  • 为什么叫局部变量,可以把这个对象当成一个局部变量。变量person 等同于 threadLocal。使用时直接threadLocal.get();
    每一个threadLocal中都可以保存一个值。
Person person = new Person();
//相当于
ThreadLocal<Person> threadLocal = new ThreadLocal<Person>();
threadLocal.set(new Person());
  • 上代码
package com.tzw.juc.threadLocal;public class C11_threadLocal {private static ThreadLocal<Person> threadLocal = new ThreadLocal<Person>();public static void main(String[] args) {//主线程// 1. 在同一线程中,threadLocal相当于一个局部变量,可以进行赋值操作。// 2. 同一个线程中,threadLocal可以修改这个局部变量的值。C11_threadLocal c11_threadLocal = new C11_threadLocal();threadLocal.set(c11_threadLocal.new Person("张三"));threadLocal.set(c11_threadLocal.new Person("王武"));Person person = threadLocal.get();System.out.println(person);//线程1//1. 输出结果为null,说明ThreadLocal对象虽然是同一个,但是不同的线程中threadLocal对象保存的值不一样,//线程不共享。new Thread(()->{Person person1 = threadLocal.get();System.out.println(person1);}).start();//线程2// 输出结果为:李四。说明同一个threadLocal对象,不同线程中ThreadLocal保存的值是不一样的。new Thread(()->{threadLocal.set(c11_threadLocal.new Person("李四"));Person person2 = threadLocal.get();System.out.println(person2);}).start();}public class Person{String name;public Person(String name){this.name=name;}@Overridepublic String toString() {return "Person{" +"name='" + name + '\'' +'}';}}
}

输出结果:

Person{name=‘王武’}
null
Person{name=‘李四’}

  • 原理:源码

看一个set方法的源码

public void set(T value) {//1. 获取当前线程Thread t = Thread.currentThread();//2. 获取当前线程对象中的mapThreadLocal.ThreadLocalMap map = getMap(t);//map不为空,设置值if (map != null)map.set(this, value);//map为空,创建一个mapelsecreateMap(t, value);}//从当前线程对象中获取threadLocals属性。ThreadLocal.ThreadLocalMap getMap(Thread t) {return t.threadLocals;}void createMap(Thread t, T firstValue) {//给t线程对象的threadLocals属性赋值一个ThreadLocalMapt.threadLocals = new ThreadLocal.ThreadLocalMap(this, firstValue);}

总结:

  1. set值时实际上是set到map中。
  2. 而这个map是维护在Thread类中的。所以也就是说每个一个Thread对象都会维护一个自己的Map。通过ThreadLocal设的值(set)其实是存放在当前线程的map属性中的。这也就解释了同一个threadLocal对象设置的值在不同的线程中是不共享的。每个线程都会维护一个map来存放。
  3. ThreadLocal的应用场景:声明式事物。

一个事物方法,方法中有对数据库增删改等操作,操作需要获取connect连接,这一个事物中的连接必须都一样,所以在ThreadLocal中设置一个connect,每一个线程调用这个方法的时候都有自己维护的connect。

4.强软弱虚引用

4.1 强引用

一般情况下普通的引用都是强引用。
eg:

/*** 强引用* 在强引用消失,在垃圾回收的时候会被回收。*/
public class NormalReference {public static void main(String[] args) {A a = new A();a=null;System.gc();while(true){}}
}

类似于这种的都是强引用。
当强引用消失的时候才能够通过System.gc()进行回收垃圾,将person对象回收。

重写Object的finalize()方法。当该对象被回收时会调用finalize方法。我们可以通过观察finalize方法的调用判断A对象是否被回收。


/*** 重写Object类的finalize方法* 该方法在进行回收本类对象的时候会调用。* 回收A对象时会被调用*/
public class A {@Overridepublic void finalize(){System.out.println("进行垃圾回收");}
}

4.2 软引用

  1. 软引用在内存足够的时候发生gc不会被回收,只有在jvm内存不够使用的时候进行回收。
  2. 软引用适用于做缓存。
package com.tzw.juc.reference;import java.lang.ref.SoftReference;import static java.lang.Thread.sleep;/**** 1. softReference---》new SoftReference() 强引用*    SoftReference()  -----> new byte[1024*1024*10] 软引用* 2. 设置软引用分配 10M内存* 3. 调用System.gc(),进行垃圾回收* 4. 回收后重新看软引用是否被回收* 5. 在分配15M内存空间* 6. 查看软引用是否被回收** 设置vm参数,-Xms20M -Xmx20M* 执行结果:** [B@28d93b30* [B@28d93b30* null** 总结:软引用内存够大时,gc不会回收软引用,* 当内存不足时,gc会回收软引用。* 当强引用消失时, new SoftReference()和 软引用都会被回收。**/
public class C_SoftReference {public static void main(String[] args) {SoftReference<byte[]> softReference = new SoftReference<>(new byte[1024*1024*10]);System.out.println(softReference.get());System.gc();try {sleep(1000);} catch (InterruptedException e) {e.printStackTrace();}System.out.println(softReference.get());byte[] b = new byte[1024*1024*15];System.out.println(softReference.get());}
}

4.3 弱引用

  1. 只要发生gc就会回收弱引用,适用于ThreadLocal
package com.tzw.juc.reference;import java.lang.ref.WeakReference;public class C_WeakReference {public static void main(String[] args) {WeakReference<A> weakReference = new WeakReference<>(new A());System.out.println(weakReference.get());System.gc();//执行为null,弱引用在gc时一定会被回收。System.out.println(weakReference.get());//使用弱引用场景,ThreadLocalThreadLocal threadLocal = new ThreadLocal();threadLocal.set(new A());threadLocal.remove();// 可以当作// map中为 map(WeakReference(ThreadLoack),v);// threadLocal --->new ThreadLocal()强引用,threadLocal强引用指向ThreadLocal对象// map 中的key---》new WeakReference()---->ThreadLocal(弱引用),map中key弱引用指向ThreadLocal对象//分析:如果是强引用的话,如果threadLocal=null时,// 当gc的时候,由于key强引用指向ThreadLocal对象那么,ThreadLocal就永远不会被回收。造成内存泄露。//但是弱引用时,虽然gc会回收弱引用,但是相当于key=null,value值访问不到,那么也会造成内存泄露。//所以使用ThreadLocal时,如果使用完后,需要将remove方法。}}

4.4 虚引用

  1. 只要发生gc就会回收弱引用。
  2. 适用于堆外内存回收。由于堆外引用不在jvm内存中,所以无法直接控制,只能同过虚引用控制,当发现队列中有对象时,就去回收堆外内存。
package com.tzw.juc.reference;import java.lang.ref.PhantomReference;
import java.lang.ref.Reference;
import java.lang.ref.ReferenceQueue;
import java.util.ArrayList;
import java.util.List;/*** 虚引用时指向直接内存(堆外内存的)不在jvm堆中* 1 虚引用对象不能同个get获取到虚引用的对象* 2 虚引用对象在gc时一定会被回收* 3 虚引用对象被回收之后会放入到引用队列中。就是说引用队列中的对象已经是被回收过的。*/
public class C_PhantomReference {//创建一个引用队列private static final ReferenceQueue<A> QUEUE = new ReferenceQueue<>();public static void main(String[] args) {PhantomReference<A> phantomReference = new PhantomReference(new A(), QUEUE);// 获取A对象System.out.println(phantomReference.get());new Thread(() -> {while (true) {Reference<? extends A> poll = QUEUE.poll();if (poll != null) {System.out.println("--- 虚引用对象被jvm回收了 ---- " + poll);}}}).start();List<byte[]> list = new ArrayList<>();while(true){list.add(new byte[1024*1024]);}}
}

这篇关于多线程(3)-源码分析(ReentrantLock-AQS,ThreaLocal)、强软弱虚引用的文章就介绍到这儿,希望我们推荐的文章对编程师们有所帮助!



http://www.chinasem.cn/article/953901

相关文章

Springboot中分析SQL性能的两种方式详解

《Springboot中分析SQL性能的两种方式详解》文章介绍了SQL性能分析的两种方式:MyBatis-Plus性能分析插件和p6spy框架,MyBatis-Plus插件配置简单,适用于开发和测试环... 目录SQL性能分析的两种方式:功能介绍实现方式:实现步骤:SQL性能分析的两种方式:功能介绍记录

SpringBoot中使用 ThreadLocal 进行多线程上下文管理及注意事项小结

《SpringBoot中使用ThreadLocal进行多线程上下文管理及注意事项小结》本文详细介绍了ThreadLocal的原理、使用场景和示例代码,并在SpringBoot中使用ThreadLo... 目录前言技术积累1.什么是 ThreadLocal2. ThreadLocal 的原理2.1 线程隔离2

最长公共子序列问题的深度分析与Java实现方式

《最长公共子序列问题的深度分析与Java实现方式》本文详细介绍了最长公共子序列(LCS)问题,包括其概念、暴力解法、动态规划解法,并提供了Java代码实现,暴力解法虽然简单,但在大数据处理中效率较低,... 目录最长公共子序列问题概述问题理解与示例分析暴力解法思路与示例代码动态规划解法DP 表的构建与意义动

Java多线程父线程向子线程传值问题及解决

《Java多线程父线程向子线程传值问题及解决》文章总结了5种解决父子之间数据传递困扰的解决方案,包括ThreadLocal+TaskDecorator、UserUtils、CustomTaskDeco... 目录1 背景2 ThreadLocal+TaskDecorator3 RequestContextH

C#使用DeepSeek API实现自然语言处理,文本分类和情感分析

《C#使用DeepSeekAPI实现自然语言处理,文本分类和情感分析》在C#中使用DeepSeekAPI可以实现多种功能,例如自然语言处理、文本分类、情感分析等,本文主要为大家介绍了具体实现步骤,... 目录准备工作文本生成文本分类问答系统代码生成翻译功能文本摘要文本校对图像描述生成总结在C#中使用Deep

C#多线程编程中导致死锁的常见陷阱和避免方法

《C#多线程编程中导致死锁的常见陷阱和避免方法》在C#多线程编程中,死锁(Deadlock)是一种常见的、令人头疼的错误,死锁通常发生在多个线程试图获取多个资源的锁时,导致相互等待对方释放资源,最终形... 目录引言1. 什么是死锁?死锁的典型条件:2. 导致死锁的常见原因2.1 锁的顺序问题错误示例:不同

浅析Rust多线程中如何安全的使用变量

《浅析Rust多线程中如何安全的使用变量》这篇文章主要为大家详细介绍了Rust如何在线程的闭包中安全的使用变量,包括共享变量和修改变量,文中的示例代码讲解详细,有需要的小伙伴可以参考下... 目录1. 向线程传递变量2. 多线程共享变量引用3. 多线程中修改变量4. 总结在Rust语言中,一个既引人入胜又可

Go中sync.Once源码的深度讲解

《Go中sync.Once源码的深度讲解》sync.Once是Go语言标准库中的一个同步原语,用于确保某个操作只执行一次,本文将从源码出发为大家详细介绍一下sync.Once的具体使用,x希望对大家有... 目录概念简单示例源码解读总结概念sync.Once是Go语言标准库中的一个同步原语,用于确保某个操

SpringBoot项目中Maven剔除无用Jar引用的最佳实践

《SpringBoot项目中Maven剔除无用Jar引用的最佳实践》在SpringBoot项目开发中,Maven是最常用的构建工具之一,通过Maven,我们可以轻松地管理项目所需的依赖,而,... 目录1、引言2、Maven 依赖管理的基础概念2.1 什么是 Maven 依赖2.2 Maven 的依赖传递机

Redis主从/哨兵机制原理分析

《Redis主从/哨兵机制原理分析》本文介绍了Redis的主从复制和哨兵机制,主从复制实现了数据的热备份和负载均衡,而哨兵机制可以监控Redis集群,实现自动故障转移,哨兵机制通过监控、下线、选举和故... 目录一、主从复制1.1 什么是主从复制1.2 主从复制的作用1.3 主从复制原理1.3.1 全量复制