多线程(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

相关文章

Python实现无痛修改第三方库源码的方法详解

《Python实现无痛修改第三方库源码的方法详解》很多时候,我们下载的第三方库是不会有需求不满足的情况,但也有极少的情况,第三方库没有兼顾到需求,本文将介绍几个修改源码的操作,大家可以根据需求进行选择... 目录需求不符合模拟示例 1. 修改源文件2. 继承修改3. 猴子补丁4. 追踪局部变量需求不符合很

Spring事务中@Transactional注解不生效的原因分析与解决

《Spring事务中@Transactional注解不生效的原因分析与解决》在Spring框架中,@Transactional注解是管理数据库事务的核心方式,本文将深入分析事务自调用的底层原理,解释为... 目录1. 引言2. 事务自调用问题重现2.1 示例代码2.2 问题现象3. 为什么事务自调用会失效3

找不到Anaconda prompt终端的原因分析及解决方案

《找不到Anacondaprompt终端的原因分析及解决方案》因为anaconda还没有初始化,在安装anaconda的过程中,有一行是否要添加anaconda到菜单目录中,由于没有勾选,导致没有菜... 目录问题原因问http://www.chinasem.cn题解决安装了 Anaconda 却找不到 An

Spring定时任务只执行一次的原因分析与解决方案

《Spring定时任务只执行一次的原因分析与解决方案》在使用Spring的@Scheduled定时任务时,你是否遇到过任务只执行一次,后续不再触发的情况?这种情况可能由多种原因导致,如未启用调度、线程... 目录1. 问题背景2. Spring定时任务的基本用法3. 为什么定时任务只执行一次?3.1 未启用

C++ 各种map特点对比分析

《C++各种map特点对比分析》文章比较了C++中不同类型的map(如std::map,std::unordered_map,std::multimap,std::unordered_multima... 目录特点比较C++ 示例代码 ​​​​​​代码解释特点比较1. std::map底层实现:基于红黑

Java使用多线程处理未知任务数的方案介绍

《Java使用多线程处理未知任务数的方案介绍》这篇文章主要为大家详细介绍了Java如何使用多线程实现处理未知任务数,文中的示例代码讲解详细,感兴趣的小伙伴可以跟随小编一起学习一下... 知道任务个数,你可以定义好线程数规则,生成线程数去跑代码说明:1.虚拟线程池:使用 Executors.newVir

Spring、Spring Boot、Spring Cloud 的区别与联系分析

《Spring、SpringBoot、SpringCloud的区别与联系分析》Spring、SpringBoot和SpringCloud是Java开发中常用的框架,分别针对企业级应用开发、快速开... 目录1. Spring 框架2. Spring Boot3. Spring Cloud总结1. Sprin

Spring 中 BeanFactoryPostProcessor 的作用和示例源码分析

《Spring中BeanFactoryPostProcessor的作用和示例源码分析》Spring的BeanFactoryPostProcessor是容器初始化的扩展接口,允许在Bean实例化前... 目录一、概览1. 核心定位2. 核心功能详解3. 关键特性二、Spring 内置的 BeanFactory

JAVA封装多线程实现的方式及原理

《JAVA封装多线程实现的方式及原理》:本文主要介绍Java中封装多线程的原理和常见方式,通过封装可以简化多线程的使用,提高安全性,并增强代码的可维护性和可扩展性,需要的朋友可以参考下... 目录前言一、封装的目标二、常见的封装方式及原理总结前言在 Java 中,封装多线程的原理主要围绕着将多线程相关的操

MyBatis-Plus中Service接口的lambdaUpdate用法及实例分析

《MyBatis-Plus中Service接口的lambdaUpdate用法及实例分析》本文将详细讲解MyBatis-Plus中的lambdaUpdate用法,并提供丰富的案例来帮助读者更好地理解和应... 目录深入探索MyBATis-Plus中Service接口的lambdaUpdate用法及示例案例背景