ThreadLocal是什么?能干嘛?
ThreadLocal提供线程局部变量,这些变量与正常的变量不同。因为每个线程在访问ThreadLocal实例的时候(通过其get()和set()),都有自己的、独立初始化变量副本。ThreadLocal实例通常是类的私有静态字段,使用它的目的是希望将状态(用户id或事务id)和线程关联起来。
常见api
代码案例
某销售公司需要知道子公司销售了多少房子,子公司共有5个销售,便于计算子公司的奖金分成。
代码如下:
package multiThreads;
import java.util.Random;
import java.util.concurrent.CountDownLatch;
public class ThreadLocalTest {
public static void main(String[] args) throws InterruptedException {
SaleCount saleCount = new SaleCount();
CountDownLatch countDownLatch = new CountDownLatch(5);
for (int i = 0; i < 5; i++) {
new Thread(() -> {
try {
int count = new Random().nextInt(5) + 1;
System.out.println("count:" + count);
saleCount.sale(count);
} finally {
countDownLatch.countDown();
}
}).start();
}
countDownLatch.await();
System.out.println("最终卖出"+ saleCount.getCount() +"套房");
}
}
class SaleCount {
private int count;
public synchronized void sale(int saleCount) {
count += saleCount;
}
public SaleCount() {
}
public SaleCount(int count) {
this.count = count;
}
public int getCount() {
return count;
}
public void setCount(int count) {
this.count = count;
}
@Override
public String toString() {
return "SaleCount{" +
"count=" + count +
'}';
}
}
运行结果如下:
现在需求变了,子公司的销售不想吃大锅饭,希望根据每个人的销售额来分奖金,于是需要统计每个人的销售额。
代码如下:
package multiThreads;
import java.util.Random;
import java.util.concurrent.CountDownLatch;
public class ThreadLocalTest {
public static void main(String[] args) throws InterruptedException {
SaleCount saleCount = new SaleCount();
CountDownLatch countDownLatch = new CountDownLatch(5);
for (int i = 0; i < 5; i++) {
new Thread(() -> {
try {
int count = new Random().nextInt(5) + 1;
System.out.println(Thread.currentThread().getName() + ",count:" + count);
saleCount.sale(count);
saleCount.saleByThreadLocal(count);
System.out.println(Thread.currentThread().getName() + "卖出" + saleCount.saleCount());
} finally {
//ThreadLocal中的变量用完一定要记得remove,否则在线程复用的情况下,会有脏数据导致业务数据异常
saleCount.clearSaleCount();
countDownLatch.countDown();
}
}).start();
}
countDownLatch.await();
System.out.println("最终卖出"+ saleCount.getCount() +"套房");
}
}
class SaleCount {
private int count;
private static final ThreadLocal<Integer> SALE_COUNT = ThreadLocal.withInitial(() -> 0);
public SaleCount() {
}
public SaleCount(int count) {
this.count = count;
}
public int getCount() {
return count;
}
public void setCount(int count) {
this.count = count;
}
public synchronized void sale(int count) {
this.count += count;
}
public void saleByThreadLocal(int count) {
SALE_COUNT.set(count + SALE_COUNT.get());
}
public void clearSaleCount() {
SALE_COUNT.remove();
}
public Integer saleCount() {
return SALE_COUNT.get();
}
@Override
public String toString() {
return "SalesMan{" +
"count=" + count +
"threadLocalCount=" + SALE_COUNT.get() +
'}';
}
}
运行结果:
ThreadLocal中的变量用完不清楚的错误代码示例:
package multiThreads;
import java.util.Random;
import java.util.concurrent.CountDownLatch;
import java.util.concurrent.ExecutorService;
import java.util.concurrent.Executors;
public class ThreadLocalTest {
public static void main(String[] args) throws InterruptedException {
ExecutorService threadPool = Executors.newFixedThreadPool(2);
try {
SaleCount saleCount = new SaleCount();
CountDownLatch countDownLatch = new CountDownLatch(5);
for (int i = 0; i < 5; i++) {
Thread thread = new Thread(() -> {
try {
int count = new Random().nextInt(5) + 1;
System.out.println(Thread.currentThread().getName() + ",count:" + count);
saleCount.sale(count);
saleCount.saleByThreadLocal(count);
System.out.println(Thread.currentThread().getName() + "卖出" + saleCount.saleCount());
} finally {
//ThreadLocal中的变量用完一定要记得remove,否则在线程复用的情况下,会有脏数据导致业务数据异常
// saleCount.clearSaleCount();
countDownLatch.countDown();
}
});
threadPool.submit(thread);
}
countDownLatch.await();
System.out.println("最终卖出"+ saleCount.getCount() +"套房");
} catch (Exception e) {
e.printStackTrace();
} finally {
threadPool.shutdown();
}
}
}
class SaleCount {
private int count;
private static final ThreadLocal<Integer> SALE_COUNT = ThreadLocal.withInitial(() -> 0);
public SaleCount() {
}
public SaleCount(int count) {
this.count = count;
}
public int getCount() {
return count;
}
public void setCount(int count) {
this.count = count;
}
public synchronized void sale(int count) {
this.count += count;
}
public void saleByThreadLocal(int count) {
SALE_COUNT.set(count + SALE_COUNT.get());
}
public void clearSaleCount() {
SALE_COUNT.remove();
}
public Integer saleCount() {
return SALE_COUNT.get();
}
@Override
public String toString() {
return "SalesMan{" +
"count=" + count +
"threadLocalCount=" + SALE_COUNT.get() +
'}';
}
}
运行结果:
ThreadLocal源码分析
ThreadLocal的内存泄露问题
Java中的四种引用
1.强引用(Reference)
强引用:强引用是我们最常见的对象引用,只要还有强引用指向一个对象,那么就意味着该对象还活着,那么GC就不会回收此对象。Object obj = new Object();
中的obj就是一个强引用,当一个对象被强引用所引用时,那么此对象是可达的,无法被GC回收,即使该对象后面永远不会被用到,都不会被回收,因此强引用是造成Java内存泄露的主要原因之一。对于一个普通对象,如果没有其他引用关系的情况下,只要超过了引用的作用域或者显式地将引用设置为null,那么该对象都是可以被GC回收的。
代码示例:
package multiThreads;
import java.util.concurrent.TimeUnit;
public class MyObjectTest {
public static void main(String[] args) throws InterruptedException {
MyObject myObject = new MyObject();
System.out.println("before myObject + " + myObject);
myObject = null;
System.gc();
TimeUnit.MILLISECONDS.sleep(500L);
System.out.println("after myObject + " + myObject);
}
}
class MyObject {
@Override
protected void finalize() throws Throwable {
System.out.println("gc invoke finalize()----");
}
}
运行结果:
2.软引用(SoftReference)
软引用:相对强引用有一些弱化,需要使用java.lang.ref.SoftReference来实现。软引用在内存充足时不会被GC回收,但是在内存不足时会被GC回收。软应用通常使用在对内存比较敏感的程序中,比如高速缓存中就有用到软引用,内存足够就保留,内存不足就回收。
内存充足时,不会被GC回收,代码如下:
package multiThreads;
import java.lang.ref.SoftReference;
import java.util.concurrent.TimeUnit;
public class MyObjectTest {
public static void main(String[] args) throws InterruptedException {
//strongReference();
SoftReference<MyObject> myObjectSoftReference = new SoftReference<>(new MyObject());
System.gc();
TimeUnit.SECONDS.sleep(1L);
System.out.println("内存够用时:" + myObjectSoftReference.get());
try {
byte[] bytes = new byte[20 * 1024 * 1024];//设置虚拟机内存大小为10m,new 20m的byte数组,造成内存不足
} catch (Exception e) {
e.printStackTrace();
} finally {
System.out.println("内存不够时:" + myObjectSoftReference.get());
}
}
private static void strongReference() throws InterruptedException {
MyObject myObject = new MyObject();
System.out.println("before myObject + " + myObject);
myObject = null;
System.gc();
TimeUnit.MILLISECONDS.sleep(500L);
System.out.println("after myObject + " + myObject);
}
}
class MyObject {
@Override
protected void finalize() throws Throwable {
System.out.println("gc invoke finalize()----");
}
}
运行结果:
3.弱引用(WeakReference)
弱引用:相较于软引用来说,弱引用的对象生命周期更短。只要GC启动,不论内存是否充足,弱引用的对象都会回收。
代码示例:
package multiThreads;
import java.lang.ref.SoftReference;
import java.lang.ref.WeakReference;
import java.util.concurrent.TimeUnit;
public class MyObjectTest {
public static void main(String[] args) throws InterruptedException {
WeakReference<MyObject> myObjectWeakReference = new WeakReference<>(new MyObject());
System.out.println("gc之前:" + myObjectWeakReference.get());
System.gc();
System.out.println("gc之后:" + myObjectWeakReference.get());
}
}
class MyObject {
@Override
protected void finalize() throws Throwable {
System.out.println("gc invoke finalize()----");
}
}
运行结果:
4.虚引用(PhantomReference)
虚引用:
1.虚引用必须和引用队列ReferenceQueue联合使用。
虚引用需要使用java.lang.ref.PhantomReference类来实现,顾名思义,就是形同虚设。与其他三种引用不同,虚引用并不会决定对象的生命周期。如果一个对象仅持有虚引用,那么该对象就和没有任何引用一样,在任何时候都可能被GC回收,它不能单独使用,也无法通过它访问对象,且必须和引用队列(ReferenceQueue)联合使用。
2.PhantomReference的get()总是返回null
虚引用的主要作用是跟踪对象的垃圾回收状态。仅仅是提供了一种确保了对象被finalize后,再做某些事的通知机制。PhantomReference的get()总是返回null,因此无法通过虚引用来访问引用的对象。
3.处理监控通知使用
换句话说,设置虚引用的唯一目的,是在这个对象被垃圾回收的时候收到一个系统通知或者后续添加进一步的处理,用于实现比finalize()更灵活的回收操作。
代码示例:
package multiThreads;
import java.lang.ref.PhantomReference;
import java.lang.ref.ReferenceQueue;
import java.util.ArrayList;
import java.util.List;
import java.util.concurrent.TimeUnit;
public class MyObjectTest {
public static void main(String[] args) throws InterruptedException {
//
// WeakReference<MyObject> myObjectWeakReference = new WeakReference<>(new MyObject());
// System.out.println("gc之前:" + myObjectWeakReference.get());
// System.gc();
// System.out.println("gc之后:" + myObjectWeakReference.get());
MyObject myObject = new MyObject();
ReferenceQueue<MyObject> objectReferenceQueue = new ReferenceQueue<>();
PhantomReference<MyObject> myObjectPhantomReference = new PhantomReference<>(myObject, objectReferenceQueue);
System.out.println(myObjectPhantomReference.get());//null 虚引用的get()永远返回null,因此无法通过虚引用获取对象
List<byte[]> list = new ArrayList<>();
new Thread(() -> {
while (true) {
list.add(new byte[1024 * 1024]);
try {
TimeUnit.MILLISECONDS.sleep(500L);
} catch (InterruptedException e) {
e.printStackTrace();
}
System.out.println("list add ok");
}
}, "a").start();
new Thread(() -> {
while (true) {
if (null != objectReferenceQueue.poll()) {
System.out.println("虚引用对象被回收,加入引用队列");
break;
}
}
}, "b").start();
}
}
class MyObject {
@Override
protected void finalize() throws Throwable {
System.out.println("gc invoke finalize()----");
}
}
运行结果:
各种引用适用场景
Thread、ThreadLocal、T对象三者的关系
Entry为什么使用弱引用?