← 返回首页
JDBC教程(十一)
发表时间:2020-03-24 12:12:58
讲解使用ThreadLocal设计JDBC工具类

1.ThreadLocal简介

多线程访问同一个共享变量的时候容易出现并发问题,特别是多个线程对一个变量进行写入的时候,为了保证线程安全,一般使用者在访问共享变量的时候需要进行额外的同步措施才能保证线程安全性。ThreadLocal是除了加锁这种同步方式之外的一种保证一种规避多线程访问出现线程不安全的方法,当我们在创建一个变量后,如果每个线程对其进行访问的时候访问的都是线程自己的变量这样就不会存在线程不安全问题。

每一个ThreadLocal能够放一个线程级别的变量,可是它本身能够被多个线程共享使用,并且又能够达到线程安全的目的,且绝对线程安全。

实例:

下面的例子中,开启两个线程,在每个线程内部设置了本地变量的值,然后调用print方法打印当前本地变量的值。如果在打印之后调用本地变量的remove方法会删除本地内存中的变量,代码如下所示。

public class Test {

    static ThreadLocal<String> threadLocal = new ThreadLocal<>();

    static void print(String str) {
        //打印当前线程中本地内存中本地变量的值
        System.out.println(str + " :" + threadLocal.get());
        //清除本地内存中的本地变量
        threadLocal.remove();
    }

    public static void main(String[] args) {

        Runnable r1 = ()->{
            //设置线程1中本地变量的值
            threadLocal.set("localVar1");
            //调用打印方法
            print("thread1");
            //打印本地变量
            System.out.println("after remove : " + threadLocal.get());
        };

        Runnable r2 = ()->{
            //设置线程1中本地变量的值
            threadLocal.set("localVar2");
            //调用打印方法
            print("thread2");
            //打印本地变量
            System.out.println("after remove : " + threadLocal.get());
        };

        Thread th1 = new Thread(r1);
        Thread th2 = new Thread(r2);

        th1.start();
        th2.start();
    }
}

运行结果:
thread1 :localVar1
thread2 :localVar2
after remove : null
after remove : null

2.使用ThreadLocal改写druid工具类

public class DBUtils {

    //声明druid连接池对象
    private static DruidDataSource dataSource = null;
    private static Connection conn = null;
    private static String url = null;
    private static String username = null;
    private static String password = null;
    /**
     * 初始连接数
     **/
    private static int initialSize;
    /**
     * 最大活动连接数
     **/
    private static int maxActive;
    /**
     * 最小闲置连接数
     **/
    private static int minIdle;
    /**
     * 连接耗尽时最大等待获取连接时间
     **/
    private static long maxWait;

    //保证线程安全的数据库访问,一个线程只绑定一个链接对象,多次访问时同一个连接对象
    private static ThreadLocal<Connection> tl = new ThreadLocal<Connection>();

    private DBUtils() {

    }

    static {
        initPool();
    }

    private static void initPool() {
        try {
            Properties pro = new Properties();
            InputStream in = DBUtils.class.getClassLoader().getResourceAsStream("druid.properties");
            pro.load(in); //读取属性文档配置信息
            String driverClassName = pro.getProperty("driverClassName");
            Class.forName(driverClassName);
            url = pro.getProperty("url");
            username = pro.getProperty("username");
            password = pro.getProperty("password");
            initialSize = Integer.parseInt(pro.getProperty("initialSize"));
            maxActive = Integer.parseInt(pro.getProperty("maxActive"));
            maxWait = Integer.parseInt(pro.getProperty("maxWait"));
            minIdle = Integer.parseInt(pro.getProperty("minIdle"));

            //创建druid数据源
            dataSource = new DruidDataSource();
            dataSource.setUrl(url);
            dataSource.setUsername(username);
            dataSource.setPassword(password);

            //设置连接池中初始连接数
            dataSource.setInitialSize(initialSize);
            //设置最大连接数
            dataSource.setMaxActive(maxActive);
            //设置最小的闲置链接数
            dataSource.setMinIdle(minIdle);
            //设置最大的等待时间(等待获取链接的时间)
            dataSource.setMaxWait(maxWait);


        } catch (Exception ex) {
            ex.printStackTrace();
        }
    }

    //由于使用了数据库连接池,我们不采用单例模式
    public static Connection getConnection() throws Exception {
        // 先从当前线程上获得链接
        Connection conn = tl.get();
        try {
            if (conn == null || conn.isClosed()) {
                //从连接池中获取连接对象
                conn = dataSource.getConnection();
                // 把连接绑定到当前线程上
                tl.set(conn);
            }
        } catch (SQLException e) {
            e.printStackTrace();
        }
        return conn;
    }
}

编写测试类

public class DruidDBUtilsDemo {

    public static void main(String[] args) throws Exception {
        Connection conn1 = DBUtils.getConnection();
        Connection conn2 = DBUtils.getConnection();

        System.out.println(conn1 == conn2);
    }
}

运行结果:
true

main方法就是一个主线程,说明同一个线程上获得的Connection是单例的。

重新改写测试类,测试不同线程获取连接对象。

public class DruidDBUtilsDemo {

    private static Connection conn1;
    private static Connection conn2;

    public static void main(String[] args) throws Exception {

        Runnable r1 = ()->{
            try {
                conn1 = DBUtils.getConnection();
                //打印本地变量
                System.out.println("conn1 hashCode is:"+conn1.hashCode());
            }catch(Exception ex){
                ex.printStackTrace();
            }
        };

        Runnable r2 = ()->{
            try {
                conn2 = DBUtils.getConnection();
                //打印本地变量
                System.out.println("conn2 hashCode is:"+conn2.hashCode());
            }catch(Exception ex){
                ex.printStackTrace();
            }
        };

        Thread th1 = new Thread(r1);
        Thread th2 = new Thread(r2);

        th1.start();
        th2.start();

        try{
            th1.join();
            th2.join();
        }catch(Exception ex){
            ex.printStackTrace();
        }
        System.out.println(conn1 == conn2);
    }
}

运行结果:

conn2 hashCode is:524605296
三月 24, 2020 12:11:40 下午 com.alibaba.druid.pool.DruidDataSource info
信息: {dataSource-1} inited
conn1 hashCode is:440459887
false