标签:线程 操作 同步机制 first param 两种 use 运算 jdk
一、介绍
/** * This class provides thread-local variables. These variables differ from * their normal counterparts in that each thread that accesses one (via its * {@code get} or {@code set} method) has its own, independently initialized * copy of the variable. {@code ThreadLocal} instances are typically private * static fields in classes that wish to associate state with a thread (e.g., * a user ID or Transaction ID). * * <p>For example, the class below generates unique identifiers local to each * thread. * A thread‘s id is assigned the first time it invokes {@code ThreadId.get()} * and remains unchanged on subsequent calls. * <pre> *
import java.util.concurrent.atomic.AtomicInteger;
public class ThreadId {
// Atomic integer containing the next thread ID to be assigned
private static final AtomicInteger nextId = new AtomicInteger(0);
// Thread local variable containing each thread‘s ID
private static final ThreadLocal<Integer> threadId =
new ThreadLocal<Integer>() {
@Override protected Integer initialValue() {
return nextId.getAndIncrement();
}
};
// Returns the current thread‘s unique ID, assigning it if necessary
public static int get() {
return threadId.get();
}
}
* </pre> * <p>Each thread holds an implicit reference to its copy of a thread-local * variable as long as the thread is alive and the {@code ThreadLocal} * instance is accessible; after a thread goes away, all of its copies of * thread-local instances are subject to garbage collection (unless other * references to these copies exist). * * @author Josh Bloch and Doug Lea * @since 1.2 */
ThreadLocal类用来提供线程内部的局部变量,这种变量在多线程环境下访问(通过set和get方法访问)时能
保证各个线程的变量相对独立与其他线程内的变量。ThreadLocal实例通常来说都是private static类型的,用
于关联线程和线程上下文。
作用:提供线程内的局部变量,不同线程之间不会互相干扰,这种变量在线程的生命周期内起作用,减少同一个
线程内多个函数或组件之间一些公共变量传递的复杂度(同一线程,不同组件中传递公共变量)。
二、基本使用
2.1 常用方法
2.2 使用案例
1 public class Demo1 { 2 //变量 3 private String content; 4 5 public void setContent(String content) { 6 this.content = content; 7 } 8 9 public String getContent() { 10 return content; 11 } 12 13 }
1 public class Main { 2 public static void main(String[] args) { 3 final Demo1 demo = new Demo1(); 4 // final Demo2 demo = new Demo2(); 5 for(int i=0;i<5;i++){ 6 Thread thread = new Thread(new Runnable() { 7 @Override 8 public void run() { 9 //每个线程存入一个变量过一会取出 10 demo.setContent(Thread.currentThread().getName()+"的数据"); 11 System.out.println("--------------------------"); 12 System.out.println(Thread.currentThread().getName()+"--->"+demo.getContent()); 13 14 } 15 },"线程"+i); 16 thread.start(); 17 } 18 } 19 }
输出:
-------------------------- -------------------------- 线程3--->线程4的数据 -------------------------- 线程2--->线程4的数据 -------------------------- 线程1--->线程4的数据 -------------------------- 线程0--->线程3的数据 线程4--->线程4的数据
修改Demo1,引入ThreadLocal
1 public class Demo2 { 2 //变量 3 private String content; 4 ThreadLocal<String> threadLocal = new ThreadLocal<>(); 5 public void setContent(String content) { 6 threadLocal.set(content); 7 } 8 9 public String getContent() { 10 return threadLocal.get(); 11 } 12 13 }
输出:一一对应
-------------------------- -------------------------- 线程2--->线程2的数据 -------------------------- -------------------------- 线程4--->线程4的数据 -------------------------- 线程3--->线程3的数据 线程0--->线程0的数据 线程1--->线程1的数据
三、ThreadLocal & synchronized
3.1 使用synchronized修饰
1 public class Main { 2 public static void main(String[] args) { 3 final Demo1 demo = new Demo1(); 4 // final Demo2 demo = new Demo2(); 5 for(int i=0;i<5;i++){ 6 Thread thread = new Thread(new Runnable() { 7 @Override 8 public void run() { 9 //每个线程存入一个变量过一会取出 10 synchronized (Demo1.class){ 11 demo.setContent(Thread.currentThread().getName()+"的数据"); 12 System.out.println("--------------------------"); 13 System.out.println(Thread.currentThread().getName()+"--->"+demo.getContent()); 14 } 15 } 16 },"线程"+i); 17 thread.start(); 18 } 19 } 20 }
输出:效果相同,虽然实现了线程数据隔离的问题,但是失去了并发性。
-------------------------- 线程0--->线程0的数据 -------------------------- 线程4--->线程4的数据 -------------------------- 线程3--->线程3的数据 -------------------------- 线程2--->线程2的数据 -------------------------- 线程1--->线程1的数据
3.2 区别
synchronized:同步机制采用以时间换空间的方式,只提供了一份变量,让不同的线程排队访问。侧重多个线程之间访问资源的同步。
ThreadLocal:采用以空间换时间的方式,为每一个线程提供了一份变量的副本,从而实现同时访问而互不干扰。侧重于多个线程之间的数据隔离。
四、运用场景
4.1 转账
转入转出操作应该为原子性操作,如果之间出现异常,导致转账数据错误,例如Service代码中的ArithmeticException异常导致Jack减少100,而Rose没有增加。
1 package com.juc.transfer_accounts; 2 3 import com.mchange.v2.c3p0.ComboPooledDataSource; 4 import java.sql.Connection; 5 import java.sql.SQLException; 6 /** 7 * @author Millet 8 * @date 2020/4/30 23:17 9 */ 10 public class JdbcUtils { 11 //通过标识名来创建相应连接池 12 static ComboPooledDataSource dataSource=new ComboPooledDataSource(); 13 //从连接池中取用一个连接 14 public static Connection getConnection() throws SQLException { 15 return dataSource.getConnection(); 16 } 17 //释放连接回连接池 18 public static void release(AutoCloseable... ios){ 19 for(AutoCloseable io : ios){ 20 if(io!=null){ 21 try { 22 io.close(); 23 } catch (Exception e) { 24 e.printStackTrace(); 25 } 26 } 27 } 28 } 29 public static void commitAndClose(Connection conn){ 30 if(conn!=null){ 31 try { 32 conn.commit(); 33 } catch (SQLException e) { 34 e.printStackTrace(); 35 } 36 } 37 } 38 public static void rollbackAndClose(Connection conn){ 39 if(conn != null){ 40 try { 41 conn.rollback(); 42 conn.close(); 43 } catch (SQLException e) { 44 e.printStackTrace(); 45 } 46 } 47 } 48 49 }
1 package com.juc.transfer_accounts; 2 3 import java.sql.SQLException; 4 5 /** 6 * @author Millet 7 * @date 2020/4/30 23:17 8 */ 9 public class AccountService { 10 public boolean transfer(String outUser, String inUser, int money){ 11 AccountDao ad = new AccountDao(); 12 try { 13 ad.out(outUser,money); 14 //转入转出应该为原子操作,如果两个操作中间出现异常,导致转账数据错误,如下所示 15 int i = 1/0; 16 ad.in(inUser,money); 17 } catch (Exception e) { 18 e.printStackTrace(); 19 return false; 20 } 21 return true; 22 } 23 }
1 package com.juc.transfer_accounts; 2 3 /** 4 * @author Millet 5 * @date 2020/4/30 23:17 6 */ 7 public class AccountController { 8 public static void main(String[] args) { 9 //Jack给Rose转账100 10 String outUser = "Jack"; 11 String inUser = "Rose"; 12 AccountService as = new AccountService(); 13 boolean res = as.transfer(outUser, inUser, 100); 14 if(res){ 15 System.out.println("转账成功!"); 16 }else { 17 System.out.println("转账失败!"); 18 } 19 } 20 }
4.2 解决方案
4.2.1 事务
使用事务时需要注意:
1.service层和Dao层的连接对象Connection需要保持一致
2.每个线程的Connection对象必须前后一致,线程隔离
常见解决方案:
1.传参:将service层的connection对象直接传递到Dao层(就是在Dao层方法上增加一个连接对象参数,此处应
注意Dao层不能释放连接)
2.加锁
1 package com.juc.transfer_accounts; 2 3 import org.springframework.stereotype.Repository; 4 5 import java.sql.Connection; 6 import java.sql.PreparedStatement; 7 import java.sql.SQLException; 8 9 /** 10 * @author Millet 11 * @date 2020/4/30 23:16 12 */ 13 public class AccountDao { 14 /** 15 * 传参:将service层的connection对象直接传递到Dao层(就是在Dao层方法上增加一个连接对象参数,此处应 16 * 注意Dao层不能释放连接) 17 */ 18 //转出 19 public void out(String outUser, int money,Connection conn) throws SQLException { 20 String sql = "update account set money = money - ? where name = ?"; 21 // Connection conn = JdbcUtils.getConnection(); 22 PreparedStatement pstm = conn.prepareStatement(sql); 23 pstm.setInt(1,money); 24 pstm.setString(2,outUser); 25 // JdbcUtils.release(pstm,conn); 26 } 27 //传入 28 public void in(String inUser, int money, Connection conn) throws SQLException { 29 String sql = "update account set money = money + ? where name = ?"; 30 // Connection conn = JdbcUtils.getConnection(); 31 PreparedStatement pstm = conn.prepareStatement(sql); 32 pstm.setInt(1,money); 33 pstm.setString(2,inUser); 34 // JdbcUtils.release(pstm,conn); 35 } 36 }
1 package com.juc.transfer_accounts; 2 3 import java.sql.Connection; 4 /** 5 * @author Millet 6 * @date 2020/4/30 23:17 7 */ 8 public class AccountService { 9 public boolean transfer(String outUser, String inUser, int money){ 10 AccountDao ad = new AccountDao(); 11 Connection conn = null; 12 /** 13 * 事务的使用注意点: 14 * 1.service层和Dao层的连接对象Connection需要保持一致 15 * 2.每个线程的Connection对象必须前后一致,线程隔离 16 * 常见解决方案: 17 * 1.传参:将service层的connection对象直接传递到Dao层 18 * 2.加锁 19 * 常规解决方案弊端: 20 * 1.提高代码耦合度 Service-->Dao 21 * 2. 降低程序性能 synchronized 22 */ 23 try { 24 synchronized (AccountService.class){ 25 //1.开启事务 26 conn = JdbcUtils.getConnection(); 27 conn.setAutoCommit(false);//关闭自动提交事务 28 29 ad.out(outUser,money,conn); 30 //转入转出应该为原子操作,如果两个操作中间出现异常,导致转账数据错误,如下所示 31 int i = 1/0; 32 ad.in(inUser,money,conn); 33 34 JdbcUtils.commitAndClose(conn); 35 } 36 } catch (Exception e) { 37 e.printStackTrace(); 38 39 JdbcUtils.rollbackAndClose(conn); 40 return false; 41 } 42 return true; 43 } 44 }
常规解决方案弊端:
1.提高代码耦合度Service-->Dao
2. 降低程序性能 synchronized
4.2.2 ThreadLocal
Connection创建绑定线程,且提交事务需要解绑,防止内存泄漏。
1 package com.juc.transfer_accounts; 2 3 import com.mchange.v2.c3p0.ComboPooledDataSource; 4 import java.sql.Connection; 5 import java.sql.SQLException; 6 /** 7 * @author Millet 8 * @date 2020/4/30 23:17 9 */ 10 public class JdbcUtils { 11 //通过标识名来创建相应连接池 12 static ComboPooledDataSource dataSource=new ComboPooledDataSource(); 13 14 //从连接池中取用一个连接 15 /** 16 * 原本:直接从连接池中获取链接 17 * 现在: 18 * 1.直接获取当前线程绑定的连接对象 19 * 2.如果连接对象为空 20 * 2.1 再去连接池中获取链接 21 * 2.2 将此链接对象跟当前线程进行绑定 22 * @return 23 * @throws SQLException 24 */ 25 static ThreadLocal<Connection> tl = new ThreadLocal<>(); 26 public static Connection getConnection() throws SQLException { 27 Connection conn = tl.get(); 28 if(conn == null){ 29 conn = dataSource.getConnection(); 30 tl.set(conn); 31 } 32 return conn; 33 // return dataSource.getConnection(); 34 } 35 //释放连接回连接池 36 public static void release(AutoCloseable... ios){ 37 for(AutoCloseable io : ios){ 38 if(io!=null){ 39 try { 40 io.close(); 41 } catch (Exception e) { 42 e.printStackTrace(); 43 } 44 } 45 } 46 } 47 public static void commitAndClose(Connection conn){ 48 if(conn!=null){ 49 try { 50 conn.commit(); 51 //解绑当前线程绑定的连接对象 52 tl.remove(); 53 } catch (SQLException e) { 54 e.printStackTrace(); 55 } 56 } 57 } 58 public static void rollbackAndClose(Connection conn){ 59 if(conn != null){ 60 try { 61 conn.rollback(); 62 conn.close(); 63 //解绑当前线程绑定的连接对象 64 tl.remove(); 65 } catch (SQLException e) { 66 e.printStackTrace(); 67 } 68 } 69 } 70 71 }
Service、Dao直接使用JdbcUtils创建conn,且不使用synchronized和传参
1 package com.juc.transfer_accounts; 2 3 import org.springframework.stereotype.Repository; 4 5 import java.sql.Connection; 6 import java.sql.PreparedStatement; 7 import java.sql.SQLException; 8 9 /** 10 * @author Millet 11 * @date 2020/4/30 23:16 12 */ 13 public class AccountDao { 14 /** 15 * 传参:将service层的connection对象直接传递到Dao层(就是在Dao层方法上增加一个连接对象参数,此处应 16 * 注意Dao层不能释放连接) 17 */ 18 //转出 19 public void out(String outUser, int money) throws SQLException { 20 String sql = "update account set money = money - ? where name = ?"; 21 Connection conn = JdbcUtils.getConnection(); 22 PreparedStatement pstm = conn.prepareStatement(sql); 23 pstm.setInt(1,money); 24 pstm.setString(2,outUser); 25 JdbcUtils.release(pstm,conn); 26 } 27 //传入 28 public void in(String inUser, int money) throws SQLException { 29 String sql = "update account set money = money + ? where name = ?"; 30 Connection conn = JdbcUtils.getConnection(); 31 PreparedStatement pstm = conn.prepareStatement(sql); 32 pstm.setInt(1,money); 33 pstm.setString(2,inUser); 34 JdbcUtils.release(pstm,conn); 35 } 36 }
1 package com.juc.transfer_accounts; 2 3 import java.sql.Connection; 4 /** 5 * @author Millet 6 * @date 2020/4/30 23:17 7 */ 8 public class AccountService { 9 public boolean transfer(String outUser, String inUser, int money){ 10 AccountDao ad = new AccountDao(); 11 Connection conn = null; 12 /** 13 * 事务的使用注意点: 14 * 1.service层和Dao层的连接对象Connection需要保持一致 15 * 2.每个线程的Connection对象必须前后一致,线程隔离 16 * 常见解决方案: 17 * 1.传参:将service层的connection对象直接传递到Dao层 18 * 2.加锁 19 * 常规解决方案弊端: 20 * 1.提高代码耦合度 Service-->Dao 21 * 2. 降低程序性能 synchronized 22 */ 23 try { 24 // synchronized (AccountService.class){ 25 //1.开启事务 26 conn = JdbcUtils.getConnection(); 27 conn.setAutoCommit(false);//关闭自动提交事务 28 29 ad.out(outUser,money); 30 //转入转出应该为原子操作,如果两个操作中间出现异常,导致转账数据错误,如下所示 31 int i = 1/0; 32 ad.in(inUser,money); 33 34 JdbcUtils.commitAndClose(conn); 35 // } 36 } catch (Exception e) { 37 e.printStackTrace(); 38 39 JdbcUtils.rollbackAndClose(conn); 40 return false; 41 } 42 return true; 43 } 44 }
ThreadLocal好处:
1. 传递数据:保存每个线程绑定的数据,在需要的地方可以直接获取。
2. 线程隔离:各线程之间的数据相互隔离却又具备并发性,避免同步方式带来的性能损失。
五、ThreadLocal内部结构
5.1 JDK8设计
每个Thread维护了一个ThreadLocalMap,这个Map的Key是ThreadLocal实例本身,Value才是真正要存储的值Object,具体过程如下:
public class Thread implements Runnable {{ ... ThreadLocal.ThreadLocalMap threadLocals = null; ... }
1. 每个Thread线程内部都有一个Map(ThreadLocalMap)
2. Map里面存储ThreadLocal对象(Key)和线程的变量副本(Value)
3. Thread内部的Mao是由ThreadLocal维护的,由ThreadLocal负责向Map获取和设置线程的变量值
4. 对于不同的线程,每次获取副本值时,别的线程并不能获取当前线程的副本值,形成了副本的隔离,互
不干扰。
JDK8设计优点:
1. 每个Map存储的Entry数量减少,不再是一个Thread对应一个Entry。 2. 当Thread销毁的时候,ThreadLocalMao也随之销毁,减少内存的使用。早期ThreadLocalMao是由
ThreadLocal维护的,Thread销毁并不会使ThreadLocalMap销毁。
六、核心方法源码
6.1 set()
1 /** 2 * 设置当前线程对应的ThreadLocal的值 3 */ 4 public void set(T value) { 5 //获取当前线程对象 6 Thread t = Thread.currentThread(); 7 //获取Thread维护的ThreadLocalMap对象 8 ThreadLocalMap map = getMap(t); 9 if (map != null) 10 //map存在,则设置Entry 11 map.set(this, value); 12 else 13 //1. 当前线程Thread不存在ThreadLocalMap对象 14 //2.则调用createMap进行ThreadLocalMap初始化 15 //3.并将Thread t(当前线程)和value作为第一个entry存放之ThreadLocalMap中 16 createMap(t, value); 17 } 18 /** 19 * 获取Thread维护的ThreadLocalMap对象,返回当前线程对应维护的ThreadLocalMap 20 */ 21 ThreadLocalMap getMap(Thread t) { 22 return t.threadLocals; 23 } 24 /** 25 * 创建当前线程Thread对应维护的ThreadLocalMap 26 * @param t 当前线程 27 * @param 存放到Map的第一个Entry的值 28 */ 29 void createMap(Thread t, T firstValue) { 30 t.threadLocals = new ThreadLocalMap(this, firstValue); 31 }
6.2 get()
/** * Returns the value in the current thread‘s copy of this * thread-local variable. If the variable has no value for the * current thread, it is first initialized to the value returned * by an invocation of the {@link #initialValue} method. * * @return the current thread‘s value of this thread-local */ public T get() { Thread t = Thread.currentThread(); ThreadLocalMap map = getMap(t); if (map != null) { ThreadLocalMap.Entry e = map.getEntry(this); if (e != null) { @SuppressWarnings("unchecked") T result = (T)e.value; return result; } } /* 初始化:有两种情况执行此方法 1.map不存在,表示此线程没有维护的ThreadLocalMap对象 2.map存在,entry不存在 */ return setInitialValue(); } /** * @return the initial value */e private T setInitialValue() { //获取初始化值,此方法可被重写,如果不重写返回null T value = initialValue(); Thread t = Thread.currentThread(); ThreadLocalMap map = getMap(t); if (map != null) //如果map存在,设置此实体entry map.set(this, value); else //当前线程Thread不存在ThreadLocalMap对象 //则调用createMap进行初始化,并将当前线程和value作为第一个entry放入Map createMap(t, value); return value; }
总结:先获取当前线程的ThreadLoaclMap变量,如果存在则返回值,不存在则创建并返回初始值。
6.3 initiaValue()
protected T initialValue() { return null; }
这个方法是一个延迟调用的方法,从上面的代码可知,在set方法还未调用之前调用了get方法时才会执行此方法,且只执行一次。这个方法缺省值返回一个null。如果想要除null之外的初始值,可以重写此方法。(该方法是一个protected方法,显然是为了让子类覆盖而设计的)。
6.4 remove()
1 public void remove() { 2 ThreadLocalMap m = getMap(Thread.currentThread()); 3 if (m != null) 4 m.remove(this); 5 }
七、ThreadLocalMap源码分析
7.1 基本结构
ThreadLocalMap是ThreadLocal内部类,没有实现Map接口,用独立的方法实现了Map的功能,其内部的Entry也是独立实现。
7.1.1 默认属性
/** * 初始容量 必须是2的整次幂 */ private static final int INITIAL_CAPACITY = 16; /** * 存放数据 * 数组长度为2的整次幂 */ private Entry[] table; /** * 数组中Entry的个数,可用于判断是否需要扩容 */ private int size = 0; /** * 扩容阈值 */ private int threshold; // Default to 0
7.1.2 存储结构-Entry
/** * Entry继承WeakReference,并且用ThreadLocal作为key。 * 如果key为null(entry.get()==null),意味着key不再被引用 * 因此这时Entry也可以从table中清除 */ static class Entry extends WeakReference<ThreadLocal<?>> { /** The value associated with this ThreadLocal. */ Object value; Entry(ThreadLocal<?> k, Object v) { super(k); value = v; } }
Entry继承WeakReference,也就是key(ThreadLocal)是弱引用,其目的是将ThreadLocal对象的生命周期和线程生命周期解绑。
7.2 弱引用和内存泄漏
弱引用:只要垃圾回收器发现了只具有弱引用的对象,不管当前内存空间足够与否,都会回收他的内存。
Q1:如果Key使用强引用,那么会出现内存泄漏吗?
1. 假设在业务代码中使用完ThreadLocal, ThreadLocal ref被回收了 2. 但是因为threadLocalMap的Entry强引用了threadLocal, 造成ThreadLocal无法被回收 3. 在没有手动删除Entry以及CurrentThread依然运行的前提下, 始终有强引用链threadRef → currentThread →
entry, Entry就不会被回收( Entry中包括了ThreadLocal实例和value), 导致Entry内存泄漏 也就是说: ThreadLocalMap中的key使用了强引用, 是无法完全避免内存泄漏的
Q2:如果Key使用弱引用,那么会出现内存泄漏吗?
1. 假设在业务代码中使用完ThreadLocal, ThreadLocal ref被回收了 2. 由于threadLocalMap只持有ThreadLocal的弱引用, 没有任何强引用指向threadlocal实例, 所以threadlocal就可以顺利被gc回收
,此时Entry中的key = null 3. 在没有手动删除Entry以及CurrentThread依然运行的前提下, 也存在有强引用链threadRef → currentThread → value, value就
不会被回收, 而这块value永远不会被访问到了, 导致value内存泄漏 也就是说: ThreadLocalMap中的key使用了弱引用, 也有可能内存泄漏。
由上可知,内存泄漏的发生跟 ThreadLocalIMap
中的 key
是否使用弱引用是没有关系的。那么内存泄漏的的真正原因是什么呢?
细心的同学会发现,在以上两种内存泄漏的情况中.都有两个前提:
1 .没有手动侧除这个 Entry
2 · CurrentThread 依然运行
第一点很好理解,只要在使用完下 ThreadLocal ,调用其 remove 方法翻除对应的 Entry ,就能避免内存泄漏。
第二点稍微复杂一点,由于ThreodLocalMap 是 Threod 的一个属性,被当前线程所引用所以它的生命周期跟 Thread 一样长。那么在使用完 ThreadLocal 的使用,如果当前Thread 也随之执行结束, ThreadLocalMap 自然也会被 gc 回收,从根源上避免了内存泄漏。
综上, ThreadLocal 内存泄漏的根源是:由于ThreadLocalMap 的生命周期跟 Thread 一样长,如果没有手动删除对应 key 就会导致内存泄漏.
Q3:那为什么Key要使用弱引用呢?
为什么使用弱引用,根据刚才的分析,我们知道了:无论 ThreadLocalMap 中的 key 使用哪种类型引用都无法完全避免内存泄漏,跟使用弱引用没有关系。
? 要避免内存泄漏有两种方式:
? 1 .使用完 ThreadLocal ,调用其 remove 方法删除对应的 Entry
? 2 .使用完 ThreadLocal ,当前 Thread 也随之运行结束
? 相对第一种方式,第二种方式显然更不好控制,特别是使用线程池的时候,线程结束是不会销毁的.也就是说,只要记得在使用完ThreadLocal 及时的调用 remove ,无论 key 是强引用还是弱引用都不会有问题.
? 事实上,在 ThreadLocalMap 中的set/getEntry 方法中,会对 key 为 null (也即是 ThreadLocal 为 null )进行判断,如果为 null 的话,那么是会对 value 置为 null 的.
? 这就意味着使用完 ThreadLocal , CurrentThread 依然运行的前提下.就算忘记调用 remove 方法,弱引用比强引用可以多一层保障:弱引用的 ThreadLocal 会被回收.对应value在下一次 ThreadLocaIMap 调用 set/get/remove 中的任一方法的时候会被清除,从而避免内存泄漏。
7.3 Hash冲突的解决
7.3.1 从ThreadLocal的set()方法入手
回顾:
1. 首先获取当前线程,并根据当前线程获取一个Map 2. 如果获取的Map不为空,则将参数设置到Map中(当前ThreadLocal作为Key),这里调用了ThreadLocalMap的set()方法。 3.如果Map为空,则给该线程创建Map,并设置初始量(这里调用了ThreadLoacalMap的构造方法)
7.3.2 构造方法:ThreadLocalMap(ThreadLocal<?> firstKey, Object firstValue)
/** * firstKey:本ThreadLocal实例 * firstValue:要保存的线程本地变量 */ ThreadLocalMap(ThreadLocal<?> firstKey, Object firstValue) { //初始化table table = new Entry[INITIAL_CAPACITY]; //计算索引(★★★★★)INITiTAL_CAPACITY初始值是16 int i = firstKey.threadLocalHashCode & (INITIAL_CAPACITY - 1); //设置值 table[i] = new Entry(firstKey, firstValue); size = 1; //设置阈值,是数组容量的2/3 setThreshold(INITIAL_CAPACITY); }
分析:firstKey.threadLocalHashCode
private final int threadLocalHashCode = nextHashCode(); private static int nextHashCode() { return nextHashCode.getAndAdd(HASH_INCREMENT); } //原子Integer,适合高并发 private static AtomicInteger nextHashCode = new AtomicInteger(); //16进制 特殊的hash值 private static final int HASH_INCREMENT = 0x61c88647; public final int getAndAdd(int delta) { return unsafe.getAndAddInt(this, valueOffset, delta); }
这里定义了一个AtomicInteger类型nextHashCode ,每次获取当前值并加上HASH_INCREMENT,值为0x61c88647,这个值跟斐波那契数列有关,其主要目的是为了让哈希码能均匀地分布在2的n次方数组里,也就是table中,减小哈希冲突。原来 hash code 从 0 开始不断累加 0x61c88647 生成的.。
分析:(INITIAL_CAPACITY - 1)
计算hash的时候采用hashCode&(size-1)的算法,这相当于取模运算hashCode%size的一个更有效的实现。例如50%16 = 50&15,使用位运算比取余更快。所以size必须是2的整次幂。
7.3.3 ThreadLocalMap的set()方法
1 private void set(ThreadLocal<?> key, Object value) { 2 Entry[] tab = table; 3 int len = tab.length; 4 //计算索引 5 int i = key.threadLocalHashCode & (len-1); 6 //线性探测法查找元素 ★★★★★ 7 for (Entry e = tab[i]; 8 e != null; 9 e = tab[i = nextIndex(i, len)]) { 10 //取出Key 11 ThreadLocal<?> k = e.get(); 12 //ThreadLocal对应的值存在,则覆盖之前的值 13 if (k == key) { 14 e.value = value; 15 return; 16 } 17 //key为null,但是值不为null,说明之前的ThreadLocal对象已经被回收了 18 //当前数组中的Entry是一个陈旧的元素 19 if (k == null) { 20 //用新元素替代陈旧的元素,这个方法进行了不少垃圾清理的动作,防止内存泄漏 21 replaceStaleEntry(key, value, i); 22 return; 23 } 24 } 25 //ThreadLocal对应的Key不存在并且没有找到陈旧的值, 26 //则在空元素位置创建一个新的Entry 27 tab[i] = new Entry(key, value); 28 int sz = ++size; 29 30 /** 31 cleanSomeSlots用于清除那些e.get()==null的元素 32 这种数据key关联的对象已经被回收,所以这个Entry(table[index]) 33 可以被置为null,如果没有清除任何Entry,并且当前使用量达到了负 34 载因子所定义(长度的2/3),那么进行rehash操作(执行一次全表的扫描清理 35 工作) 36 37 */ 38 if (!cleanSomeSlots(i, sz) && sz >= threshold) 39 rehash(); 40 } 41 //获取环形数组的下一个索引 42 private static int nextIndex(int i, int len) { 43 return ((i + 1 < len) ? i + 1 : 0); 44 }
标签:线程 操作 同步机制 first param 两种 use 运算 jdk
原文地址:https://www.cnblogs.com/qmillet/p/12813385.html