Java并发28:ThreadLocal学习笔记-简介、基本方法及应用场景

[超级链接:Java并发学习系列-绪论]


本章主要对ThreadLocal进行学习。

1.初始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也是一个线程。

从运行结果可知:

  • 线程共享变量经过四个线程的+1操作最终为4。
  • 线程本地变量在每个线程中的值都是独立的。
  • 线程本地变量在每个线程中都需要进行初始值设置。

2.ThreadLocal的基本方法

ThreadLocal的基本方法如下:

  • ThreadLocal():构造方法,初始值为null。
  • get():获取当前线程中线程本地变量的值。
  • set(value):为当前线程中线程本地变量赋值。
  • remove():移除当前线程中线程本地变量的值。
  • initialValue():返回当前线程中线程本地变量的初值。常用来Override(重写)以设置初始值。

下面通过实例代码对这些方法进行练习:

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!

3.ThreadLocal的应用场景

主要学习以下两种应用场景:

  • 数据库连接管理
  • 会话管理

3.1.数据库连接管理

场景:

定义一个数据库连接工具类来管理数据库连接。

分析:

  • 如果使用普通的共享变量来定义这个连接(Connection),并不能满足并发的需求,因为这些连接是多个线程共享的。
  • 如果为每个Dao层都新建一个连接(Connection),则每个连接都需要消耗cpu资源和内存资源。
  • 使用ThreadLocal,能够避免多线程的争用问题;而且为每个线程建立副本,能够节省很多资源。

代码:

自定义数据库连接管理类:

/**
* 

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

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;  
}  

你可能感兴趣的:(ThreadLocal,数据库连接管理,session管理,线程本地变量,线程共享变量,Java并发,Java并发学习实例)