当前位置:网站首页>ThreadLocal comprehensive analysis (1)

ThreadLocal comprehensive analysis (1)

2022-08-10 22:03:00 InfoQ

前言

文本已收录至我的GitHub仓库,欢迎Star:https://github.com/bin392328206/six-finger
种一棵树最好的时间是十年前,其次是现在
我知道很多人不玩
qq
了,但是怀旧一下,欢迎加入六脉神剑Java菜鸟学习群,群聊号码:
549684836
 鼓励大家在技术的路上写博客

絮叨

I stumbled across an article today aboutThreadLocal的文章,然后就去学习了一下,But after reading the article,Xiao Liuliu also feels a little imperfect,So keep looking information study,终于把ThreadLocalMost of the knowledge has some basic understanding.,Write an article to record.

ThreadLocal基础之Java的引用

在 JDK1.2 之前,Java中的定义很传统:如果 reference 类型的数据中存储的数值代表的是另外一块内存的起始地址,就称为这块内存代表着一个引用.Java 中的垃圾回收机制在判断是否回收某个对象的时候,都需要依据“引用”这个概念.在不同垃圾回收算法中,对引用的判断方式有所不同:

  • 引用计数法:为每个对象添加一个引用计数器,每当有一个引用指向它时,计数器就加1,当引用失效时,计数器就减1,当计数器为0时,则认为该对象可以被回收(目前在Java中已经弃用这种方式了).
  • 可达性分析算法:从一个被称为 GC Roots 的对象开始向下搜索,如果一个对象到GC Roots没有任何引用链相连时,则说明此对象不可用.
  • JDK1.2 之前,一个对象只有“已被引用”和"未被引用"两种状态,这将无法描述某些特殊情况下的对象,比如,当内存充足时需要保留,而内存紧张时才需要被抛弃的一类对象.

四种引用类型

所以在 JDK.1.2 之后,Java 对引用的概念进行了扩充,将引用分为了:强引用(Strong Reference)、软引用(Soft Reference)、弱引用(Weak Reference)、虚引用(Phantom Reference)4 种,这 4 种引用的强度依次减弱.

强引用

Java中默认声明的就是强引用,比如:

Object obj = new Object(); //只要obj还指向Object对象,Object对象就不会被回收
obj = null; //手动置null

只要强引用存在,垃圾回收器将永远不会回收被引用的对象,哪怕内存不足时,JVM也会直接抛出OutOfMemoryError,不会去回收.如果想中断强引用与对象之间的联系,可以显示的将强引用赋值为null,这样一来,JVM就可以适时的回收对象了

软引用

Soft references is a slightly weak than strong reference life cycle of a reference type.在JVM内存充足的情况下,Soft references are not collected by the garbage collector,只有在JVM内存不足的情况下,To be reclaimed by the garbage collector.So this feature of soft references,Generally used to implement some memory-sensitive caches,只要内存空间足够,对象就会保持不被回收掉,比如网页缓存、图片缓存等.

SoftReference<String> softReference = new SoftReference<String>(new String(&quot;小六六&quot;));
System.out.println(softReference.get());

弱引用

弱引用的引用强度比软引用要更弱一些,无论内存是否足够,只要 JVM 开始进行垃圾回收,那些被弱引用关联的对象都会被回收.在 JDK1.2 之后,用 java.lang.ref.WeakReference 来表示弱引用.
 这个对ThreadLocal有用,大家先记住

WeakReference<String> weakReference = new WeakReference<String>(new String(&quot;小六六&quot;));
System.gc();
if(weakReference.get() == null) {
 System.out.println(&quot;weakReference已经被GC回收&quot;);
}

输出结果:

weakReference已经被GC回收

虚引用(PhantomReference)

虚引用是最弱的一种引用关系,如果一个对象仅持有虚引用,那么它就和没有任何引用一样,它随时可能会被回收,在 JDK1.2 之后,用 PhantomReference 类来表示,通过查看这个类的源码,发现它只有一个构造函数和一个 get() 方法,而且它的 get() 方法仅仅是返回一个null,也就是说将永远无法通过虚引用来获取对象,虚引用必须要和 ReferenceQueue 引用队列一起使用.

PhantomReference<String> phantomReference = new PhantomReference<String>(new String(&quot;小六六&quot;), new ReferenceQueue<String>());
System.out.println(phantomReference.get());

运行后,find the result is alwaysnull,Citation is as good as not holding.

简单总结下

  • 强引用 一直存活,除非GC Roots不可达 所有程序的场景,基本对象,自定义对象等.
  • 软引用 内存不足时会被回收 - 一般用在对内存非常敏感的资源上,用作缓存的场景比较多,例如:网页缓存、图片缓存
  • 弱引用 只能存活到下一次GC前 生命周期很短的对象,例如ThreadLocal中的Key.
  • 虚引用 随时会被回收, 创建了可能很快就会被回收 业界暂无使用场景, - 可能被JVM团队内部用来跟踪JVM的垃圾回收活动

ThreadLocal基础之Java中的值传递和地址传递

首先我们来看看代码

public class Test {
 public static void main(String[] args) {
 String str = &quot;123&quot;;
 System.out.println(str);
 change(str);
 System.out.println(str);
 }
 public static void change(String str){
 str = &quot;小六六&quot;;
 }
}

So how much do you think the output will be??At least I used to think it is:

123
小六六

但是,正确答案是:

123
123

这是为什么呢?I believe that most of the students who answered incorrectly received some”java教材“的影响–javaThere are two kinds of parameter passing:

  • 值传递,传递值,Changes in the formal parameters of the function do not affect the actual parameters.
  • 引用传递,传递对象引用,Changes to formal parameters in a function affect actual parameters.

然而,实际上javaThere is only one case for parameter passing,那就是值传递.所不同的是,一般说的&quot;引用传递&quot;,What is actually passed is the address value of the reference object 值传递传递的是真实内容的一个副本,对副本的操作不影响原内容,也就是形参怎么变化,不会影响实参对应的内容.

在解释上述代码前,first add some knowledge:

String a = new String(&quot;小六六&quot;);
String b;
b= new String(&quot;小六六&quot;);

The result of which is formed by the two forms of code is completely consistent,The latter is easier to understandjavaThe specific meaning of references and objects in.先声明一个String对象的引用,再new一个“小六六”对象,Finally assign this object(等号=)give that reference.

  • b:对象的引用
  • “小六六”:实际对象

好了,Let's take an example to explain pass-by-value and address-by-value references..

先定义一个对象:

 public class Person {
 private String name;
 private int age;
 
 public String getName() {
 return name;
 }
 public void setName(String name) {
 this.name = name;
 }
 public int getAge() {
 return age;
 }
 public void setAge(int age) {
 this.age = age;
 }
}

我们写个函数测试一下:

 public static void PersonCrossTest(Person person){
 System.out.println(&quot;传入的person的name:&quot;+person.getName());
 person.setName(&quot;我是张小龙&quot;);
 System.out.println(&quot;方法内重新赋值后的name:&quot;+person.getName());
 }
 //测试
 public static void main(String[] args) {
 Person p=new Person();
 p.setName(&quot;我是马化腾&quot;);
 p.setAge(45);
 PersonCrossTest(p);
 System.out.println(&quot;方法执行后的name:&quot;+p.getName());
}

结果

1传入的person的name:我是马化腾
2方法内重新赋值后的name:我是张小龙
3方法执行后的name:我是张小龙

可以看出,person经过personCrossTest()方法的执行之后,内容发生了改变,这印证了上面所说的“引用传递”,对形参的操作,改变了实际对象的内容.

那么,到这里就结题了吗?不是的,没那么简单,You can see the desired effect because you just chose the right example.!!!

下面我们对上面的例子稍作修改,加上一行代码,

public static void PersonCrossTest(Person person){
 System.out.println(&quot;传入的person的name:&quot;+person.getName());
 person=new Person();//加多此行代码
 person.setName(&quot;我是张小龙&quot;);
 System.out.println(&quot;方法内重新赋值后的name:&quot;+person.getName());
 }

传入的person的name:我是马化腾
方法内重新赋值后的name:我是张小龙
方法执行后的name:我是马化腾

为什么这次的输出和上次的不一样了呢?看出什么问题了吗?

按照JVM内存模型可以知道,对象和数组是存储在Java堆区的,而且堆区是共享的,因此程序执行到main()方法中的下列代码时

Person p=new Person();
 p.setName(&quot;我是马化腾&quot;);
 p.setAge(45);
 PersonCrossTest(p);

JVM会在堆内开辟一块内存,用来存储p对象的所有内容,同时在main()方法所在线程的栈区中创建一个引用p存储堆区中p对象的真实地址,

当执行到PersonCrossTest()方法时,因为方法内有这么一行代码:

person=new Person();

JVM需要在堆内另外开辟一块内存来存储new Person(),假如地址为“xo3333”,那此时形参person指向了这个地址,假如真的是引用传递,那么由上面讲到:引用传递中形参实参指向同一个对象,形参的操作会改变实参对象的改变.

可以推出:实参也应该指向了新创建的person对象的地址,所以在执行PersonCrossTest()结束之后,最终输出的应该是后面创建的对象内容.

然而实际上,最终的输出结果却跟我们推测的不一样,最终输出的仍然是一开始创建的对象的内容.

由此可见:引用传递,在Java中并不存在.

但是有人会疑问:为什么第一个例子中,在方法内修改了形参的内容,会导致原始对象的内容发生改变呢?这是因为:无论是基本类型和是引用类型,在实参传入形参时,都是值传递,也就是说传递的都是一个副本,而不是内容本身.可以看出,方法内的形参person和实参p并无实质关联,它只是由p处copy了一份指向对象的地址,此时:p和person都是指向同一个对象.因此在第一个例子中,对形参p的操作,会影响到实参对应的对象内容.而在第二个例子中,当执行到new Person()之后,JVM在堆内开辟一块空间存储新对象,并且把person改成指向新对象的地址,此时:p依旧是指向旧的对象,person指向新对象的地址.所以此时对person的操作,实际上是对新对象的操作,于实参p中对应的对象毫无关系.

Also summarize

  • 在Java中所有的参数传递,不管基本类型还是引用类型,都是值传递,或者说是副本传递.只是在传递过程中:
  • 如果是对基本数据类型的数据进行操作,由于原始内容和副本都是存储实际值,并且是在不同的栈区,因此形参的操作,不影响原始内容.
  • 如果是对引用类型的数据进行操作,分两种情况,一种是形参和实参保持指向同一个对象地址,则形参的操作,会影响实参指向的对象的内容.一种是形参被改动指向新的对象地址(如重新赋值引用),则形参的操作,不会影响实参指向的对象的内容.

ThreadLocal基础之this关键字

this关键字的作用

Person

String name;//定义当前Person名字

//参数nameis the target name
//成员变量nameown name
public void sayHello(String name) {
 System.out.println(name+&quot;,你好.我是&quot;+name);
}

Demo01Person

public static void main(String[] args) {
 Person person = new Person();
 //设置当前person名字
 person.name = &quot;王健林&quot;;
 //调用sayHello方法
 person.sayHello(&quot;王思聪&quot;);
}

运行结果:王思聪,你好.I'm wang jianlin was summarized:当我们使用this.member variable name,You can access the member variables of the class.由此我们可以总结出:使用thisKeywords can accurately mark attributes

this关键字的原理

String name;//定义当前Person名字

//参数nameis the target name
//成员变量nameown name
public void sayHello(String name) {
 System.out.println(this);
}

public static void main(String[] args) {
 Person person = new Person();
 //设置当前person名字
 person.name = &quot;王健林&quot;;
 //调用sayHello方法
 person.sayHello(&quot;王思聪&quot;);
 System.out.println(person);
}

打印结果:[email protected] [email protected]从结果我们可以看出,personand methodthis的地址是一样的.由此我们可以总结出:通过谁调用的方法,谁就是this

结尾

今天给大家把ThreadLocalLearning the basic knowledge to be used,Getting ready for the next chapter.

null

日常求赞

好了各位,以上就是这篇文章的全部内容了,能看到这里的人呀,都是
真粉
.

创作不易,各位的支持和认可,就是我创作的最大动力,我们下篇文章见

六脉神剑 | 文 【原创】如果本篇博客有任何错误,请批评指教,不胜感激 !
原网站

版权声明
本文为[InfoQ]所创,转载请带上原文链接,感谢
https://yzsam.com/2022/222/202208102111154903.html