提要:
1.都知道ThreadLocal都是线程局部变量,可以比作是个Thread->T的MAP,那么有个问题了,如果一个类维护了一个TL的局部变量,随着不同的线程访问,这个TL会变得很大么?我们需要在线程结束前调用TL.remove来删除TL变量么,如果不删除会不会空间无法释放导致OOM呢?
2.在写某些会被多线程访问的代码时,某些实例变量需要做成线程私有,那么就会出现在使用这些变量时都使用threadLocal.get(),这样的junk code,有好的代码结构可以优化他么?
============================================================================
解答:
1.其实ThreadLocal并不把变量保存在自己里,而是保存到线程t里,
摘自ThreadLocal
public void set(T value) { Thread t = Thread.currentThread(); ThreadLocalMap map = getMap(t); if (map != null) map.set(this, value); else createMap(t, value); } ThreadLocalMap getMap(Thread t) { return t.threadLocals; }
摘自Thread
ThreadLocal.ThreadLocalMap threadLocals = null;
因此,TheadLocal本身并不保存变量,而是委托线程去保存。而这里又有个问题,这样做可以保证ThreadLocal不会受到线程的生命周期影响,我们也不需要显示remove。那么这里又有个问题了,线程Map里始终维护了TheadLocal->T的变量,如果维护ThreadLocal的对象被GC掉,线程本地变量里的ThreadLocal变量却依然被引用,并不会被gc。这样会不会有内存泄露呢?
实际上是不会的,这个归功于WeakReference的使用
WeakReference是一种引用容器,他虽然会维持R的引用,但是如果除了WeakReference外没有其他Object引用R,那么weakreference会在R被GC时,删除他。
所以回到ThreadLocal,如果已经没有对象引用ThreadLocal,那么线程中的ThreadLocaMap就会踢掉这个被回收的ThreadLocal。
详见Thread.ThreadLocalMap
static class ThreadLocalMap { /** * The entries in this hash map extend WeakReference, using * its main ref field as the key (which is always a * ThreadLocal object). Note that null keys (i.e. entry.get() * == null) mean that the key is no longer referenced, so the * entry can be expunged from table. Such entries are referred to * as "stale entries" in the code that follows. */ static class Entry extends WeakReference<ThreadLocal> { /** The value associated with this ThreadLocal. */ Object value; Entry(ThreadLocal k, Object v) { super(k); value = v; } } ..... /** * The table, resized as necessary. * table.length MUST always be a power of two. */ private Entry[] table; }
可以看到Entry是一个TheadLocal的弱引用,其成员变量保存了映射的value,Entry[]是线程的所有ThreadLocal变量。类似一个LinkedHashMap。
2.如何避免使用ThreadLocal的线程安全类满屏的t.get()这样的junkcode
在Ibatis中sqlMapClient配置为一个bean,但是每个线程在使用smc时总有自己的局部变量,例如Transaction,这样就跟我们的场景一样。但是我们并没有看到sqlMapClient使用t.get()这样的代码。他是怎么做到的呢:
如图,sqlMapClient是全局共享的,他的queryForObject其实是委托一个sqlMapSessionImpl实现的,
sqlMapSessionImpl是线程成私有的,保存在smc的TheadLocal变量里的。
摘自SqlMapClient
protected SqlMapSessionImpl getLocalSqlMapSession() { SqlMapSessionImpl sqlMapSession = (SqlMapSessionImpl) localSqlMapSession.get(); if (sqlMapSession == null || sqlMapSession.isClosed()) { sqlMapSession = new SqlMapSessionImpl(this); localSqlMapSession.set(sqlMapSession); } return sqlMapSession; }
而sqlMapSessionImpl作为一个实例变量,他是不可能完成数据库操作的,他是委托了SqlMapExecutorDelegate的方法,SMED是在sqlMapClient里的变量,可以理解为是变相的回调操作。而SMSI里维护了一个SessionScope,这个是一个线程上下文里的变量,
摘自SqlMapSessionImpl
public class SqlMapSessionImpl implements SqlMapSession { protected SqlMapExecutorDelegate delegate; protected SessionScope sessionScope; protected boolean closed; ... }
摘自SessionScope
public class SessionScope { private static long nextId; private long id; // Used by Any private SqlMapClient sqlMapClient; private SqlMapExecutor sqlMapExecutor; private SqlMapTransactionManager sqlMapTxMgr; private int requestStackDepth; // Used by TransactionManager private Transaction transaction; private TransactionState transactionState; ... }SqlMapExecutorDelegate才是数据库执行的真正地方,那么既然要实现线程安全的操作,势必有个SqlMapExecutorDelegate不能再维护TheadLocal变量了,因此SqlMapExecutorDelegate的操作都带有SessionScope这个入参。
摘自SqlMapExecutorEelegate
public Object queryForObject(SessionScope sessionScope, String id, Object paramObject) throws SQLException { return queryForObject(sessionScope, id, paramObject, null); }因此通过这样的回调,巧妙的解决了junkcode,让代码更加清晰。
================================================================================
模仿一下
我们需要抽象一个数据源DataProvider,DataProvider可以有很多实现类,例如FileDataProvider,MysqlDataProvider,这个DataProvider数据的获取方式变成Iterator的方式。
public abstract class DataProvider implements Iterator<Row> { public abstract Set<String> listFieldsName(); public abstract Long size(); public abstract void setPath(String path); public String getName() { return this.getClass().toString(); } public abstract String getDesc(); public void remove() { throw new UnsupportedOperationException(); } }
那么junk-code的写法(代码只截取部分,有个意思)
import java.io.BufferedReader; import java.io.File; import java.io.FileReader; import java.io.IOException; import java.util.*; @Component public class FileDataProvider extends DataProvider { private ThreadLocal<ReaderInfo> readerInfo = new ThreadLocal<ReaderInfo>(); class ReaderInfo { public String csvFullPath = null; public BufferedReader reader = null; public File file = null; public boolean hasMore = false; public boolean isColumn = true; public List<String> columnNameList = null; } public boolean hasNext() { try { if (readerInfo.get().file == null) readerInfo.get().file = new File(readerInfo.get().csvFullPath); if (readerInfo.get().reader == null) readerInfo.get().reader = new BufferedReader(new FileReader(readerInfo.get().file)); if (readerInfo.get().reader.ready()) { readerInfo.get().hasMore = true; if (readerInfo.get().isColumn) { readerInfo.get().isColumn = false; readerInfo.get().columnNameList = new ArrayList<String>(); String line = readerInfo.get().reader.readLine(); StringTokenizer st = new StringTokenizer(line, ","); while (st.hasMoreTokens()) { readerInfo.get().columnNameList.add(st.nextToken()); } } return true; } else { readerInfo.get().hasMore = false; readerInfo.get().reader.close(); readerInfo.get().reader = null; return false; } } catch (IOException e) { throw new RuntimeException(e); } } ... }
在hashNext中需要访问readerInfo里的path和file都用到了readerInfo.get()。
-------------以下是模仿sqlMapClient做的改造--------------------
1.把readerfino独立出来(变个名字sessionScope)
public class SessionScope { public String csvFullPath = null; public BufferedReader reader = null; public File file = null; public boolean hasMore = false; public boolean isColumn = true; public List<String> columnNameList = null; }
2.编写一个使用sessionScope作为入参的读取器FileProviderExecutor
import com.alibaba.cainiao.hellomaven.impl.Field; import com.alibaba.cainiao.hellomaven.impl.Row; import java.io.BufferedReader; import java.io.File; import java.io.FileReader; import java.io.IOException; import java.util.ArrayList; import java.util.HashSet; import java.util.Set; import java.util.StringTokenizer; public class FileProviderExecutor { public boolean hasNext(SessionScope sessionScope) { try { if (sessionScope.file == null) sessionScope.file = new File(sessionScope.csvFullPath); if (sessionScope.reader == null) sessionScope.reader = new BufferedReader(new FileReader(sessionScope.file)); if (sessionScope.reader.ready()) { sessionScope.hasMore = true; if (sessionScope.isColumn) { sessionScope.isColumn = false; sessionScope.columnNameList = new ArrayList<String>(); String line = sessionScope.reader.readLine(); StringTokenizer st = new StringTokenizer(line, ","); while (st.hasMoreTokens()) { sessionScope.columnNameList.add(st.nextToken()); } } return true; } else { sessionScope.hasMore = false; sessionScope.reader.close(); sessionScope.reader = null; return false; } } catch (IOException e) { throw new RuntimeException(e); } } ... }
3.写一个FileDataSessionProvider,维护sessionScope,和FileProviderExecutor,FPE使用构造函数传递进来(当然也可以吧FPE换成FileDataProvider)
public class FileDataSessionProvider { private SessionScope sessionScope; private FileProviderExecutor fileProviderExecutor = null; public FileDataSessionProvider(FileProviderExecutor executor) { this.fileProviderExecutor = executor ; sessionScope = new SessionScope(); } public Set<String> listFieldsName() { return fileProviderExecutor.listFieldsName(sessionScope); } public Long size() { return fileProviderExecutor.size(sessionScope); } public void setPath(String path) { this.sessionScope.csvFullPath = path; } public boolean hasNext() { return fileProviderExecutor.hasNext(sessionScope); } public Row next() { return fileProviderExecutor.next(sessionScope); } }
所有的函数委托Executor执行,传入参数sessionScope
4.最后,封装FileDataProvider,维护一个TheadLocal变量,里面存放FileDataSessionProvider,实现DataProvider,获取TheadLocal变量进行调用。
public class FileDataProvider extends DataProvider { FileProviderExecutor fileProviderExecutor = new FileProviderExecutor(); private ThreadLocal<FileDataSessionProvider> fileDataSessionProviderThreadLocal = new ThreadLocal<FileDataSessionProvider>(); private FileDataSessionProvider getLocalSessionProvider() { FileDataSessionProvider fileDataSessionProvider = fileDataSessionProviderThreadLocal.get(); if (fileDataSessionProvider == null) { fileDataSessionProvider = new FileDataSessionProvider(this.fileProviderExecutor); fileDataSessionProviderThreadLocal.set(fileDataSessionProvider); } return fileDataSessionProvider; } @Override public Set<String> listFieldsName() { return this.getLocalSessionProvider().listFieldsName(); } @Override public Long size() { return this.getLocalSessionProvider().size(); } @Override public void setPath(String path) { this.getLocalSessionProvider().setPath(path); } @Override public String getDesc() { return "测试下"; } public boolean hasNext() { return this.getLocalSessionProvider().hasNext(); } public Row next() { return this.getLocalSessionProvider().next(); } public FileProviderExecutor getFileProviderExecutor() { return fileProviderExecutor; } public void setFileProviderExecutor(FileProviderExecutor fileProviderExecutor) { this.fileProviderExecutor = fileProviderExecutor; } }
在getLocalSessionProvider()中,如果该线程没有访问过就创建sessionProvider,传入Executor。这样就完成了封装,绕过了对ThreadLocal变量内部成员变量的反复读取。这样的设计模式让代码层次更清晰,但是呢缺增加了代码量和理解难度,所以可以看情况选择使用。
不过这样的写法在设计模式里能找到对应的类型么?
FINISH
相关推荐
Java多线程设计模式是Java开发中不可或缺的一部分,它涉及到如何在并发环境下高效、安全地组织程序执行...在提供的"java多线程设计模式.pdf"文件中,应该会涵盖更多细节和示例,有助于进一步理解这些模式的实现和应用。
在软件开发中,尤其是在基于数据库的应用程序中,数据访问层(Data Access Object, DAO)设计模式是一种常用的模式,用于简化和标准化对数据库的操作。该模式的主要目的是将业务逻辑与数据访问逻辑分离,使得应用...
创建型模式是设计模式中的一个重要类别,它关注于对象的创建过程,帮助开发者封装创建细节,使系统独立于具体对象的创建方式。 1. **Singleton(单例模式)** 单例模式确保一个类只有一个实例,并提供全局访问点...
最后,Druid的源码中还体现了多种语言特性,比如Interface和Abstract的使用,FunctionP的实现,ClassLoader的运用,以及ThreadExecutes和ThreadLocal的应用。这些语言特性的应用有助于构建出高效和线程安全的代码。 ...
5. 设计模式:(框架中使用,是程序设计的高级思想) 1. 单例模式:(重点) 1. 概念:设计一个类,这个类只能创建一个对象。(限制创建对象对象数量) 2. 怎么设计? 控制创建对象的数量 => 创建...
适配器模式、装饰器模式和代理模式都是常见的设计模式。适配器模式用于桥接两个接口,而装饰器模式用于在不修改类的情况下给类增加新的功能。代理模式则是增加一个额外的中间层,以便支持分配、控制或智能访问。 11...
了解常见的设计模式(如单例、工厂、观察者等)并能在实际项目中灵活应用,能够提高代码的可重用性和可扩展性。 10. 性能优化: Java程序员应关注JVM的性能调优,包括堆内存大小设置、垃圾回收参数调整、方法区和...
阿里巴巴面试经验分享,经历七轮面试,最终定级 P6 ...本篇文章涵盖了基础知识、框架和设计模式、项目经验和解决方案、分布式系统和消息队列、系统设计和优化等多方面的知识点,对阿里巴巴的面试经历进行了总结和解释。
Spring框架作为一款优秀的Java企业级应用开发框架,其内部集成了多种设计模式,不仅有助于提高系统的可维护性和扩展性,还能够让开发者更好地理解和掌握面向对象设计原则。下面我们将详细介绍Spring中常用的设计模式...
设计模式是解决常见问题的模板,是经验的总结,对于写出可维护和扩展的代码非常重要。 总之,"S1_Java内测题目"涵盖了从基础到高级的Java编程知识,包括但不限于语法、面向对象、集合框架、异常处理、JVM、并发编程...
设计模式是软件工程的基石,面试中常出现的有单例、工厂、观察者、装饰者、适配器等23种设计模式,理解其应用场景和优缺点非常重要。 最后,面试者还应关注一些实际项目经验,如如何处理高并发场景,数据库优化,...
6. **设计模式**:在实现多线程数据通信程序时,设计模式如生产者-消费者模型、读者-写者模型、工作窃取等能有效解决并发问题。例如,使用`BlockingQueue`实现生产者-消费者模型,可以高效地协调数据生产与消费的...
9. **设计模式**:设计模式是解决常见编程问题的模板,如单例、工厂、观察者等。学习并应用设计模式能够提升代码的可读性和可维护性,初学者往往忽视了这一点。 10. **JVM内部机制**:了解JVM的工作原理,包括类...
设计模式是提高代码复用性和扩展性的关键。手册列举了常见的设计模式,并提供了在实际开发中的应用示例,如单例模式、工厂模式、观察者模式等,帮助开发者提升设计能力。 七、单元测试 单元测试被视为保证代码质量...
- 避免滥用设计模式,每个模式都有其适用场景。 9. **JVM优化** - 了解JVM的工作原理,包括类加载、内存模型、垃圾回收策略等,有助于进行性能调优。 - 调整JVM参数,如堆大小、新生代和老年代比例,以及GC策略...
例如,通过使用Spring拦截器进行租户识别,并通过ThreadLocal传递给后端,使得数据库和缓存层对应用层透明。 2. **数据隔离**:为了保护各租户之间的数据安全,SaaS系统需要在数据库访问层对SQL语句进行改写,以...
Java面试题是每个Java开发者在求职过程中都会遇到的挑战,涵盖范围广泛,包括语言基础、集合框架、多线程、JVM、设计模式等多个方面。本文将基于标题"Java Interview Questions/Java面试题"和提供的标签"源码"、...
在软件开发中,单例模式是一种常用的面向对象设计模式,其主要目的是确保一个类只有一个实例,并提供一个全局访问点。这种模式在处理资源管理、控制并发以及提高性能等方面有着广泛的应用。当涉及到线程相关的问题时...
6. **设计模式**:理解常见的23种设计模式,如工厂模式、单例模式、建造者模式、适配器模式等,并能灵活应用于实际项目中。 7. **JVM内存模型**:理解堆、栈、方法区、本地方法栈的概念,垃圾回收机制,以及性能...
求职者需要理解每种设计模式的使用场景和优缺点,并能够在项目中灵活运用。 书中还提到了Java并发编程中的重要概念,如ThreadLocal的基本原理、Java内存泄露的原因、GC如何判断对象失去引用等。Java的并发机制是一...