1.ThreadLocal简介
ThreadLocal提供线程局部变量。这些变量与正常的变量不同,因为每一个线程在访问ThreadLocal实例的时候(通过其get或set方法)都有自己的、独立初始化的变量副本。ThreadLocal实例通常是类中的私有静态字段,使用它的目的是希望将状态(例如,用户ID或事务ID)与线程关联起来。
实现每一个线程都有自己专属的本地变量副本(自己用自己的变量不麻烦别人,不和其他人共享,人人有份,人各一份),主要解决了让每个线程绑定自己的值,通过使用get() 和set() 方法,获取默认值或将其值更改为当前线程所存的副本的值从而避免了线程安全问题。
2.永远的helloworld
class House //资源类 { int saleCount = 0; public synchronized void saleHouse() { ++saleCount; } /*ThreadLocal<Integer> saleVolume = new ThreadLocal<Integer>(){ @Override protected Integer initialValue() { return 0; } };*/ ThreadLocal<Integer> saleVolume = ThreadLocal.withInitial(() -> 0); public void saleVolumeByThreadLocal() { saleVolume.set(1 + saleVolume.get()); } } /** * 需求1: 5个销售卖房子,集团高层只关心销售总量的准确统计数。 * <p> * 需求2: 5个销售卖完随机数房子,各自独立销售额度,自己业绩按提成走, */ public class ThreadLocalDemo { public static void main(String[] args) throws InterruptedException { House house = new House(); for (int i = 1; i <= 5; i++) { new Thread(() -> { int size = new Random().nextInt(5) + 1; try { for (int j = 1; j <= size; j++) { house.saleHouse(); house.saleVolumeByThreadLocal(); } System.out.println(Thread.currentThread().getName() + "\t" + "号销售卖出:" + house.saleVolume.get()); } finally { house.saleVolume.remove(); } }, String.valueOf(i)).start(); } //暂停毫秒 try { TimeUnit.MILLISECONDS.sleep(300); } catch (InterruptedException e) { e.printStackTrace(); } System.out.println(Thread.currentThread().getName() + "\t" + "共计卖出多少套: " + house.saleCount); } }
小总结:
因为每个 Thread 内有自己的实例副本且该副本只由当前线程自己使用
既然其它 Thread 不可访问,那就不存在多线程间共享的问题。
统一设置初始值,但是每个线程对这个值的修改都是各自线程互相独立的
加入synchronized或者Lock控制资源的访问顺序
人手一份,大家各自安好,没必要抢夺
阿里ThreadLocal规范开始
class MyData { ThreadLocal<Integer> threadLocalField = ThreadLocal.withInitial(() -> 0); public void add() { threadLocalField.set(1 + threadLocalField.get()); } } /** .【强制】必须回收自定义的 ThreadLocal 变量,尤其在线程池场景下,线程经常会被复用,如果不清理 自定义的 ThreadLocal 变量,可能会影响后续业务逻辑和造成内存泄露等问题。尽量在代理中使用 try-finally 块进行回收。 */ public class ThreadLocalDemo2 { public static void main(String[] args) throws InterruptedException { MyData myData = new MyData(); ExecutorService threadPool = Executors.newFixedThreadPool(3); try { for (int i = 0; i < 10; i++) { threadPool.submit(() -> { try { Integer beforeInt = myData.threadLocalField.get(); myData.add(); Integer afterInt = myData.threadLocalField.get(); System.out.println(Thread.currentThread().getName()+"\t"+"beforeInt:"+beforeInt+"\t afterInt: "+afterInt); } finally { myData.threadLocalField.remove(); //不及时清理,会导致计算结果错误 } }); } }catch (Exception e){ e.printStackTrace(); }finally { threadPool.shutdown(); } } }
3.Thread,ThreadLocal,ThreadLocalMap 关系
Thread和ThreadLocal
各自线程,人手一份
ThreadLocal和ThreadLocalMap
三者总概括
threadLocalMap实际上就是一个以threadLocal实例为key,任意对象为value的Entry对象(k,v键值对)。
当我们为threadLocal变量赋值,实际上就是以当前threadLocal实例为key,值为value的Entry往这个threadLocalMap中存放
近似的可以理解为:ThreadLocalMap从字面上就可以看出这是一个保存ThreadLocal对象的map(其实是以ThreadLocal为Key),不过是经过了两层包装的ThreadLocal对象
JVM内部维护了一个线程版的Map<Thread,T>(通过ThreadLocal对象的set方法,结果把ThreadLocal对象自己当做key,放进了ThreadLoalMap中),每个线程要用到这个T的时候,用当前的线程去Map里面获取,通过这样让每个线程都拥有了自己独立的变量,人手一份,竞争条件被彻底消除,在并发模式下是绝对安全的变量。
4.ThreadLocal内存泄露问题
1.什么是内存泄漏
不再会被使用的对象或者变量占用的内存不能被回收,就是内存泄露。
2.ThreadLocalMap与WeakReference
ThreadLocalMap 从字面上就可以看出这是一个保存ThreadLocal对象的map(其实是以它为Key),不过是经过了两层包装的ThreadLocal对象: (1)第一层包装是使用 WeakReference<ThreadLocal <?>> 将 ThreadLocal 对象变成一个弱引用的对象; (2)第二层包装是定义了一个专门的类 Entry 来扩展 WeakReference <ThreadLocal <?>>
3.强引用、软引用、弱引用、虚引用分别是什么?
关于引用的整体架构
一般new对象,就是Reference(强引用)
强引用又有三个子类:SoftReference(软),WeakReference(弱),PhantomReference(虚)
java 技术允许使用 finalize() 方法在垃圾收集器将对象从内存中清除出去之前做必要的清理工作。
1.强引用(默认支持模式)
当内存不足,JVM开始垃圾回收,对于强引用的对象,就算是出现了OOM也不会对该对象进行回收,死都不收。
强引用是我们最常见的普通对象引用,只要还有强引用指向一个对象,就能表明对象还“活着”,垃圾收集器不会碰这种对象。在 Java 中最常见的就是强引用,把一个对象赋给一个引用变量,这个引用变量就是一个强引用。当一个对象被强引用变量引用时,它处于可达状态,它是不可能被垃圾回收机制回收的,即使该对象以后永远都不会被用到,JVM也不会回收。因此强引用是造成Java内存泄漏的主要原因之一。
对于一个普通的对象,如果没有其他的引用关系,只要超过了引用的作用域或者显式地将相应 (强)引用赋值为 null,一般认为就是可以被垃圾收集的了(当然具体回收时机还是要看垃圾收集策略)。
class MyObject { //这个方法一般不用复写,我们只是为了教学给大家演示案例做说明 @Override protected void finalize() throws Throwable { // finalize的通常目的是在对象被不可撤销地丢弃之前执行清理操作。 System.out.println("-------invoke finalize method~!!!"); } } public static void strongReference() { MyObject myObject = new MyObject(); System.out.println("-----gc before: "+myObject); myObject = null; System.gc(); try { TimeUnit.SECONDS.sleep(1); } catch (InterruptedException e) { e.printStackTrace(); } System.out.println("-----gc after: "+myObject); }
2.软引用
软引用是一种相对强引用弱化了一些的引用,需要用java.lang.ref.SoftReference类来实现,可以让对象豁免一些垃圾收集。
对于只有软引用的对象来说,
当系统内存充足时它 不会 被回收,
当系统内存不足时它 会 被回收。
软引用通常用在对内存敏感的程序中,比如高速缓存就有用到软引用,内存够用的时候就保留,不够用就回收!
private static void softReference() { SoftReference<MyObject> softReference = new SoftReference<>(new MyObject()); //System.out.println("-----softReference:"+softReference.get()); System.gc(); try { TimeUnit.SECONDS.sleep(1); } catch (InterruptedException e) { e.printStackTrace(); } System.out.println("-----gc after内存够用: "+softReference.get());//输出有值 try { byte[] bytes = new byte[20 * 1024 * 1024];//设置JVM启动内存小于20M,创建20MB对象 }catch (Exception e){ e.printStackTrace(); }finally { System.out.println("-----gc after内存不够: "+softReference.get());//输出:null,内存不够才会回收 } }
3.弱引用
弱引用需要用java.lang.ref.WeakReference类来实现,它比软引用的生存期更短,对于只有弱引用的对象来说,只要垃圾回收机制一运行,不管JVM的内存空间是否足够,都会回收该对象占用的内存。
private static void weakReference() { WeakReference<MyObject> weakReference = new WeakReference<>(new MyObject()); System.out.println("-----gc before 内存够用: "+weakReference.get());//有值 System.gc(); //暂停几秒钟线程 try { TimeUnit.SECONDS.sleep(1); } catch (InterruptedException e) { e.printStackTrace(); } System.out.println("-----gc after 内存够用: "+weakReference.get());//输出:null }
软引用和弱引用的适用场景
假如有一个应用需要读取大量的本地图片:
如果每次读取图片都从硬盘读取则会严重影响性能,
如果一次性全部加载到内存中又可能造成内存溢出。
此时使用软引用可以解决这个问题。
设计思路是:用一个HashMap来保存图片的路径和相应图片对象关联的软引用之间的映射关系,在内存不足时,JVM会自动回收这些缓存图片对象所占用的空间,从而有效地避免了OOM的问题。
Map<String, SoftReference<Bitmap>> imageCache = new HashMap<String, SoftReference<Bitmap>>();
软引用:内存充足 不回收,内存不充足 回收
弱引用:只要gc执行,内存不管是否充足,都会回收