[超级链接:Java并发学习系列-绪论]
本章主要对ThreadLocal进行学习。
ThreadLocal又称为线程本地变量、线程局部变量,来源于JDK1.2版本。
简单来说,每个线程都单独存放一个ThreadLocal变量的副本,线程之间互不干扰。
ThreadLocal主要区别于线程之间的共享变量。
下面,通过一段简单的代码来演示线程本地变量和线程共享变量的区别。
自定义一个同时包含两种变量的自定义类型:
/**
* 共享变量、线程本地变量--示例
*
* @author hanchao 2018/3/21 23:46
**/
static class MyNum {
//共享变量,多个线程共享
int num;
//本地变量,每个线程单独创建一个副本
ThreadLocal threadLocalNum = new ThreadLocal();
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 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也是一个线程。
从运行结果可知:
ThreadLocal的基本方法如下:
下面通过实例代码对这些方法进行练习:
ThreadLocal threadLocal = new ThreadLocal();
//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 stringThreadLocal = new ThreadLocal() {
@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!
主要学习以下两种应用场景:
场景:
定义一个数据库连接工具类来管理数据库连接。
分析:
代码:
自定义数据库连接管理类:
/**
* Title: 一个自定义数据库连接工具
*
* @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 = new ThreadLocal() {
//重写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
关于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;
}