Java并发28:ThreadLocal学习笔记-简介、基本方法及应用场景
本章主要对ThreadLocal进行学习。
1.初始ThreadLocal
ThreadLocal又称为线程本地变量、线程局部变量,来源于JDK1.2版本。
简单来说,每个线程都单独存放一个ThreadLocal变量的副本,线程之间互不干扰。
ThreadLocal主要区别于线程之间的共享变量。
- 线程共享变量:多个线程共同访问这个变量,存在数据争用。
- 线程本地变量:每个线程单独存放这个变量的副本,不存在数据争用。
下面,通过一段简单的代码来演示线程本地变量和线程共享变量的区别。
自定义一个同时包含两种变量的自定义类型:
/** * <p>共享变量、线程本地变量--示例</p> * * @author hanchao 2018/3/21 23:46 **/ static class MyNum { //共享变量,多个线程共享 int num; //本地变量,每个线程单独创建一个副本 ThreadLocal<Integer> threadLocalNum = new ThreadLocal<Integer>(); public MyNum(int num, Integer threadLocalNum) { this.num = num; this.threadLocalNum.set(threadLocalNum); } public int getNum() { return num; } public void setNum(int num) { this.num = num; } public ThreadLocal<Integer> getThreadLocalNum() { return threadLocalNum; } }
测试代码:
//构造 MyNum myNum = new MyNum(0, new Integer(0)); System.out.println("线程[" + Thread.currentThread().getName() + "]----num:" + myNum.getNum() + ",threadLocalNum:" + myNum.getThreadLocalNum().get().intValue() + "\n"); //多线程运行 for (int i = 0; i < 4; i++) { new Thread(() -> { //每个线程中执行加1计算 myNum.setNum(myNum.getNum() + 1); //打印结果 System.out.println("线程[" + Thread.currentThread().getName() + "]----num: " + myNum.getNum()); //ThreadLocal只能在自己的线程中设置值 if (myNum.getThreadLocalNum().get() != null) { myNum.getThreadLocalNum().set(myNum.getThreadLocalNum().get().intValue() + 1); //打印结果 System.out.println("线程[" + Thread.currentThread().getName() + "]----threadLocalNum: " + myNum.getThreadLocalNum().get().intValue()); } else { System.out.println("线程[" + Thread.currentThread().getName() + "]----threadLocalNum is null ,threadLocalNum to " + 1); myNum.getThreadLocalNum().set(1); } }).start(); Thread.sleep(100); System.out.println(); } Thread.sleep(100); System.out.println("线程[" + Thread.currentThread().getName() + "]----num:" + myNum.getNum() + ",threadLocalNum:" + myNum.getThreadLocalNum().get().intValue()); System.out.println("\n线程共享变量在多个线程中共享;线程本地变量每个线程独有一份副本,互补影响;main也是一个线程。");
运行结果:
线程[main]----num:0,threadLocalNum:0 线程[Thread-0]----num: 1 线程[Thread-0]----threadLocalNum is null ,threadLocalNum to 1 线程[Thread-1]----num: 2 线程[Thread-1]----threadLocalNum is null ,threadLocalNum to 1 线程[Thread-2]----num: 3 线程[Thread-2]----threadLocalNum is null ,threadLocalNum to 1 线程[Thread-3]----num: 4 线程[Thread-3]----threadLocalNum is null ,threadLocalNum to 1 线程[main]----num:4,threadLocalNum:0 线程共享变量在多个线程中共享;线程本地变量每个线程独有一份副本,互补影响;main也是一个线程。
从运行结果可知:
- 线程共享变量经过四个线程的+1操作最终为4。
- 线程本地变量在每个线程中的值都是独立的。
- 线程本地变量在每个线程中都需要进行初始值设置。
2.ThreadLocal的基本方法
ThreadLocal的基本方法如下:
- ThreadLocal():构造方法,初始值为null。
- get():获取当前线程中此线程本地变量的值。
- set(value):为当前线程中此线程本地变量赋值。
- remove():移除当前线程中此线程本地变量的值。
- initialValue():返回当前线程中此线程本地变量的初值。常用来Override(重写)以设置初始值。
下面通过实例代码对这些方法进行练习:
ThreadLocal<Integer> threadLocal = new ThreadLocal<Integer>(); //ThreadLocal默认值为null System.out.println("ThreadLocal默认值为null,所以需要先set()才能使用--" + threadLocal.get() + "\n"); //ThreadLocal在每个线程中都需要单独赋值 Thread.sleep(100); threadLocal.set(1); System.out.println("通过get()获取当前线程中的值,线程[" + Thread.currentThread().getName() + "] value =" + threadLocal.get()); new Thread(() -> { System.out.println("每个线程中需要单独赋值,线程[" + Thread.currentThread().getName() + "] value =" + threadLocal.get()); threadLocal.set(1); System.out.println("每个线程中需要单独赋值,线程[" + Thread.currentThread().getName() + "] value =" + threadLocal.get() + "\n"); }).start(); //通过remove()删除当前线程的值 Thread.sleep(100); threadLocal.remove(); System.out.println("通过remove()删除当前线程的值,线程[" + Thread.currentThread().getName() + "] value =" + threadLocal.get()); Thread.sleep(100); //重写protected initialValue()方法用来设置初始值 ThreadLocal<String> stringThreadLocal = new ThreadLocal<String>() { @Override protected String initialValue() { return "Hello World!"; } }; System.out.println("\n重写protected initialValue()方法用来设置初始值," + Thread.currentThread().getName() + "---" + stringThreadLocal.get());
运行结果:
ThreadLocal默认值为null,所以需要先set()才能使用--null 通过get()获取当前线程中的值,线程[main] value =1 每个线程中需要单独赋值,线程[Thread-4] value =null 每个线程中需要单独赋值,线程[Thread-4] value =1 通过remove()删除当前线程的值,线程[main] value =null 重写protected initialValue()方法用来设置初始值,main---Hello World!
3.ThreadLocal的应用场景
主要学习以下两种应用场景:
- 数据库连接管理
- 会话管理
3.1.数据库连接管理
场景:
定义一个数据库连接工具类来管理数据库连接。
分析:
- 如果使用普通的共享变量来定义这个连接(Connection),并不能满足并发的需求,因为这些连接是多个线程共享的。
- 如果为每个Dao层都新建一个连接(Connection),则每个连接都需要消耗cpu资源和内存资源。
- 使用ThreadLocal,能够避免多线程的争用问题;而且为每个线程建立副本,能够节省很多资源。
代码:
自定义数据库连接管理类:
/** * <p>Title: 一个自定义数据库连接工具</p> * * @author 韩超 2018/3/22 14:18 */ static class MyDBUtils { // String driver = "oracle.jdbc.driver.OracleDriver";//oracle // String url = "jdbc:oracle:thin:@localhost1521:test";//oracle static String driver = "com.mysql.jdbc.Driver"; static String url = "jdbc:mysql://localhost:3306/exam?useSSL=false"; static String username = "root"; static String password = "root"; //每个连接线程一个连接实例 static ThreadLocal<Connection> connection = new ThreadLocal<Connection>() { //重写ThreadLocal的initialValue方法,获取连接 @Override protected Connection initialValue() { Connection connection = null; try { //加载JDBC驱动 Class.forName(driver); //获得连接 connection = DriverManager.getConnection(url, username, password); System.out.println(Thread.currentThread().getName() + " 获取了一个MySql连接...是否关闭---" + connection.isClosed()); } catch (ClassNotFoundException e) { e.printStackTrace(); } catch (SQLException e) { e.printStackTrace(); } return connection; } };
测试代码:
//ThreadLocal常用场景01:数据库连接 System.out.println("\n=========ThreadLocal常用场景01:数据库连接"); for (int i = 0; i < 5; i++) { new Thread(() -> { //获取 Connection connection = MyDBUtils.getConnection(); //关闭连接 try { connection.close(); System.out.println(Thread.currentThread().getName() + " 关闭了连接.是否关闭---" + connection.isClosed()); } catch (SQLException e) { e.printStackTrace(); } }).start(); }
运行结果:
=========ThreadLocal常用场景01:数据库连接 Thread-7 获取了一个MySql连接...是否关闭---false Thread-9 获取了一个MySql连接...是否关闭---false Thread-8 获取了一个MySql连接...是否关闭---false Thread-6 获取了一个MySql连接...是否关闭---false Thread-5 获取了一个MySql连接...是否关闭---false Thread-5 关闭了连接.是否关闭---true Thread-6 关闭了连接.是否关闭---true Thread-7 关闭了连接.是否关闭---true Thread-8 关闭了连接.是否关闭---true Thread-9 关闭了连接.是否关闭---true
3.2.session管理
关于session管理本人并没有进行实际编码练习,现将其他博友的代码贴到这里,作为参考。
private static final ThreadLocal threadSession = new ThreadLocal(); public static Session getSession() throws InfrastructureException { Session s = (Session) threadSession.get(); try { if (s == null) { s = getSessionFactory().openSession(); threadSession.set(s); } } catch (HibernateException ex) { throw new InfrastructureException(ex); } return s; }