一、ThreadLocal简介
1、ThreadLocal介绍
在JDK 1.2的版本中就提供java.lang.ThreadLocal,ThreadLocal为解决多线程程序的并发问题提供了一种新的思路。
当使用ThreadLocal维护变量时,ThreadLocal为每个使用该变量的线程提供独立的变量副本,所以每一个线程都可以独立地改变自己的副本,而不会影响其它线程所对应的副本。从线程的角度看,目标变量就象是线程的本地变量,这也是类名中“Local”所要表达的意思。
2、ThreadLocal的接口方法
- void set(Object value)设置当前线程的线程局部变量的值。
- public Object get()该方法返回当前线程所对应的线程局部变量。
- public void remove()将当前线程局部变量的值删除,目的是为了减少内存的占用,该方法是JDK 5.0新增的方法。需要指出的是,当线程结束后,对应该线程的局部变量将自动被垃圾回收,所以显式调用该方法清除线程的局部变量并不是必须的操作,但它可以加快内存回收的速度。
- protected Object initialValue()返回该线程局部变量的初始值,该方法是一个protected的方法,显然是为了让子类覆盖而设计的。这个方法是一个延迟调用方法,在线程第1次调用get()或set(Object)时才执行,并且仅执行1次。ThreadLocal中的缺省实现直接返回一个null。
在JDK5.0中,ThreadLocal支持泛型,该类的类名已经变为ThreadLocal。API方法也相应进行了调整,新版本的API方法分别是void set(T value)、T get()以及T initialValue()。
3、ThreadLocal是如何做到为每一个线程维护变量的副本的呢?
其实实现的思路很简单:在ThreadLocal类中有一个Map,用于存储每一个线程的变量副本,Map中元素的键为当前ThrealLocal对象,而值对应线程的变量副本。
4、threadLocal的作用
提供线程内的局部变量,不同 的线程之间不会相互干扰,这种变量在线程的生命周期内起作用,减少同一个线程内多个函数或组件之间一些公共变量传递的复杂度
1、线程并发:多线程环境下
2、传递数据:通过THreadLocal在同一线程,不同组件中传递公共变量
3、线程隔离:每个线程变量都是独立的,不会相互影响
二、java四种引用类型
/**
* Author: 徐志
* Date: 2020/8/9 10:25
*/
public class M {
//重写finalize方法,当系统调用finalize()方法时,并不一定执行了垃圾回收,具体还的看JVM
@Override
protected void finalize() throws Throwable {
System.out.println("finalize");
}
}
1、强引用
Object o=new Object();
强引用指向的对象不会被垃圾回收器回收。宁肯溢出
/**
* Author: 徐志
* Date: 2020/8/9 10:27
* 强引用
*/
import java.io.IOException;
public class NormalReference {
public static void main(String[] args) throws IOException {
M m=new M(); //强引用
m=null;
System.gc(); //手动调用GC,真正执行还是得看垃圾回收器
//main线程走完,垃圾回收线程也会退出,就可能出现垃圾会收还没执行运行就已经结束了
System.in.read();//阻塞main线程,给垃圾回收线程时间执行
}
}
2、软引用
SR是一个软引用对象,存放在堆上。只不过这个对象有些特殊, 它又指向一个数组。
当内存不够时,软引用指向的对象会被回收;内存够的时候,就不会回收。运行以下代码需要将堆内存调小一点
-Xmx20M //设置堆内存
import java.lang.ref.SoftReference;
/**
* Author: 徐志
* Date: 2020/8/9 10:35
* 软引用
*/
public class SoftReferenceTest {
public static void main(String[] args) {
SoftReference<byte[]> m=new SoftReference<>(new byte[1024*1024*10]);
System.out.println(m.get());
System.gc();
try {
Thread.sleep(500);
} catch (InterruptedException e) {
e.printStackTrace();
}
System.out.println(m.get());
//再分配一个数组,这是会堆装不下,系统会执行垃圾回收
byte[] b=new byte[1024*1024*15];
System.out.println(m.get());
}
}
//软引用适合做缓存
3、弱引用
弱引用指向的对象,只要执行垃圾回收,就会被回收掉
import java.lang.ref.WeakReference;
/**
* Author: 徐志
* Date: 2020/8/9 10:52
* 弱引用
*/
public class WeakReferenceTest {
public static void main(String[] args) {
WeakReference<M> m=new WeakReference<>(new M());
System.out.println(m.get());
System.gc();
System.out.println(m.get());
}
}
4、虚引用
一般用来管理内存,使用虚引用指向的对象拿不到值。虚引用仅仅只是在回收的时候发出一个信号
/**
* Author: 徐志
* Date: 2020/8/9 10:59
*/
import java.lang.ref.PhantomReference;
import java.lang.ref.Reference;
import java.lang.ref.ReferenceQueue;
import java.util.LinkedList;
import java.util.List;
/**
* 虚引用,用来管理直接内存,凡是虚引用指向的对象,在垃圾回收的时候会先将对象
* 放到队列里
*/
public class PhantomReferenceTest {
private static final List<Object> LIST=new LinkedList<>();
private static final ReferenceQueue<M> QUEUE=new ReferenceQueue<>();
//运行前指定堆内存为20M
public static void main(String[] args) {
PhantomReference<M> phantomReference=new PhantomReference<>(new M(),QUEUE);
System.out.println(phantomReference.get());
new Thread(()->{
while (true){
//往List里存值,死循环,总会将内存占满
LIST.add(new byte[1024*1024]);
try {
Thread.sleep(1000);
} catch (InterruptedException e) {
e.printStackTrace();
Thread.currentThread().interrupt();
}
System.out.println(phantomReference.get());
}
}).start();
new Thread(()->{
Reference<? extends M> poll=QUEUE.poll();
if (poll!=null){
System.out.println("虚引用对象被JVM回收了"+poll);
}
}).start();
try {
Thread.sleep(500);
} catch (InterruptedException e) {
e.printStackTrace();
}
}
}
ThreadLocal用法
1、传统的多线程
多个线程操作,线程与线程之间不隔离,有可能出现的结果是
线程2–>线程1的数据
/**
* Author: 徐志
* Date: 2020/8/9 14:59
* 线程不隔离
*/
public class MyThreadDemo {
private String content;
public String getContent() {
return content;
}
public void setContent(String content) {
this.content = content;
}
public static void main(String[] args) {
MyThreadDemo demo=new MyThreadDemo();
for (int i = 0; i < 5; i++) {
Thread thread=new Thread(new Runnable() {
@Override
public void run() {
demo.setContent(Thread.currentThread().getName()+"的数据");
System.out.println("===============");
System.out.println(Thread.currentThread().getName()+"-->"+demo.getContent());
}
});
thread.setName("线程"+i);
thread.start();
}
}
}
传统的解决方法,使用synchronized关键字
/**
* Author: 徐志
* Date: 2020/8/9 14:59
*/
/**
* 使用synchronized关键字解决
*/
public class MyThreadDemo2 {
private String content;
public String getContent() {
return content;
}
public void setContent(String content) {
this.content = content;
}
public static void main(String[] args) {
MyThreadDemo2 demo=new MyThreadDemo2();
for (int i = 0; i < 5; i++) {
Thread thread=new Thread(new Runnable() {
@Override
public void run() {
//性能降低,程序失去并发性,串行执行
synchronized (MyThreadDemo2.class){
demo.setContent(Thread.currentThread().getName()+"的数据");
System.out.println("===============");
System.out.println(Thread.currentThread().getName()+"-->"+demo.getContent());
}}
});
thread.setName("线程"+i);
thread.start();
}
}
}
2、ThreadLocal方式
比使用synchronized关键字的效率高
/**
* Author: 徐志
* Date: 2020/8/9 14:59
*/
public class ThreadLocalDemo {
ThreadLocal<String> tl=new ThreadLocal<>();
public String getContent() {
return tl.get();
}
public void setContent(String content) {
tl.set(content);
}
public static void main(String[] args) {
ThreadLocalDemo demo=new ThreadLocalDemo();
for (int i = 0; i < 5; i++) {
Thread thread=new Thread(new Runnable() {
@Override
public void run() {
demo.setContent(Thread.currentThread().getName()+"的数据");
System.out.println("===============");
System.out.println(Thread.currentThread().getName()+"-->"+demo.getContent());
}
});
thread.setName("线程"+i);
thread.start();
}
}
}
/**
* Author: 徐志
* Date: 2020/8/9 11:35
*/
public class ThreadLocalTest2 {
static ThreadLocal<Person> t1=new ThreadLocal<>();
public static void main(String[] args) {
new Thread(()->{
try {
TimeUnit.SECONDS.sleep(2);
} catch (InterruptedException e) {
e.printStackTrace();
}
System.out.println(t1.get());
}).start();
new Thread(()->{
try {
TimeUnit.SECONDS.sleep(1);
} catch (InterruptedException e) {
e.printStackTrace();
}
//每个线程对应一个map,线程与线程之间互不影响
//以t1为key,person为值,放到一个map里
t1.set(new Person());
}).start();
}
static class Person{
String name="zhangsna";
}
}
三、JVM调优小案例
小米真实案例
场景:频繁Full GC,频繁OOM。
原因:一批C++程序员转行java程序员之后重写了finalize方法
1、为什么C++程序员会重写finalize方法
因为C++需要程序员手动释放内存,不需要垃圾回收器
2、为什么重写finalize方法会造成Full GC
将很多耗时的操作放在finalize方法里了,耗时操作会影响对象的回收
四、ThreadLocal内存泄漏的原因
使用ThreadLocal线程与线程隔离,一个线程对应一个Map