ThreadLocal实践

当多个线程占用一个共享成员变量的时候就会出现线程问题,在之前实践的数据源连接池里,存放数据源是的LinkedList<Connection>,LinkedList是非线程安全的。在多线程场景下会出现线程安全问题,为了改进我使用了ThreadLocal解决这个问题。

ThreadLocal是Thread存放变量的一个副本,Thread有个成员变量存放的是ThreadLocalMap,键值就是ThreadLocal,值就是ThreadLocal.set(value)设置的value。

 ThreadLocal.ThreadLocalMap threadLocals = null;

所以我只要把linkedList放到线程中的存放变量的地方。每个线程都是用自己的存放的变量就不会有线程安全的问题。

 

开始实现

参考数据库连接池例子:https://blog.csdn.net/u012438608/article/details/90748510

修改Test使用多线程会发现,LinkedList.removeFirst抛出了异常,原因是LinkedList是非线程安全的。

public class Test {
    public static void main(String[] args) throws SQLException {
        for (int i = 0; i < 10; i++) {
            final MyDataSource myDataSource = new MyDataSource();
            Thread thread = new Thread() {
                @Override
                public void run() {
                    try {
                        final Connection connection1 = myDataSource.getConnection();
                        connection1.close();
                        final Connection connection2 = myDataSource.getConnection();
                        connection2.close();
                        final Connection connection3 = myDataSource.getConnection();
                        connection3.close();
                        final Connection connection4 = myDataSource.getConnection();
                        connection4.close();
                        final Connection connection5 = myDataSource.getConnection();
                        connection5.close();
                    } catch (SQLException e) {
                        e.printStackTrace();
                    }
                }
            };
            thread.start();
        }
    }
}

// 错误信息
Exception in thread "Thread-5" Exception in thread "Thread-6" com.mysql.jdbc.JDBC4Connection@711f39f9获取连接
com.mysql.jdbc.JDBC4Connection@711f39f9放回连接池中
com.mysql.jdbc.JDBC4Connection@614ddd49获取连接
com.mysql.jdbc.JDBC4Connection@614ddd49放回连接池中
com.mysql.jdbc.JDBC4Connection@614ddd49获取连接
com.mysql.jdbc.JDBC4Connection@614ddd49放回连接池中
java.util.NoSuchElementException
	at java.util.LinkedList.removeFirst(LinkedList.java:270)
	at com.pss.MyDataSource.getConnection(MyDataSource.java:53)
	at com.pss.Test$1.run(Test.java:16)
java.util.NoSuchElementException
	at java.util.LinkedList.removeFirst(LinkedList.java:270)
	at com.pss.MyDataSource.getConnection(MyDataSource.java:53)
com.mysql.jdbc.JDBC4Connection@711f39f9放回连接池中
	at com.pss.Test$1.run(Test.java:16)

经过改造

/**
 * 使用threadLocal并加載LinkedList初始化数据库连接池大小
 */
private final ThreadLocal<LinkedList<Connection>> connectionThreadLocal = new ThreadLocal<LinkedList<Connection>>() {
    @Override
    protected LinkedList<Connection> initialValue() {
        final LinkedList<Connection> connections = new LinkedList<Connection>();
        Properties prop = new Properties();
        InputStream resourceAsStream = MyDataSource.class.getClassLoader().getResourceAsStream("db.properties");
        try {
            prop.load(resourceAsStream);
            String url = prop.getProperty("url");
            String username = prop.getProperty("username");
            String password = prop.getProperty("password");
            Integer maxPoolSize = Integer.valueOf(prop.getProperty("max-pool-size"));
            for (int i = 0; i < maxPoolSize; i++) {
                final Connection connection = DriverManager.getConnection(url, username, password);
                Connection proxyDataSource = createProxyDataSource(connections, connection);
                connections.add(proxyDataSource);
                System.out.println(proxyDataSource + " -> 加入到连接池中");
            }
        } catch (IOException e) {
            e.printStackTrace();
        } catch (SQLException e) {
            e.printStackTrace();
        }
        return connections;
    }
};


/**
 * 创建连接代理对象
 * @param connections
 * @param connection
 * @return
 */
private Connection createProxyDataSource(final LinkedList<Connection> connections, final Connection connection) {
    Class<? extends Connection> clazz = connection.getClass();
    Class<?>[] interfaces = clazz.getInterfaces();
    if (null == interfaces || interfaces.length == 0) {
        interfaces = new Class[]{Connection.class};
    }
    Connection connectionProxy = (Connection) Proxy.newProxyInstance(clazz.getClassLoader(), interfaces, new InvocationHandler() {
        @Override
        public Object invoke(Object proxy, Method method, Object[] args) throws Throwable {
            // 如果调用关闭方法把连接放回连接池
            if (method.getName().equals("close")) {
                connections.add((Connection) proxy);
                synchronized (connections) {
                    connections.notifyAll();
                }
                System.out.println(proxy + "放回连接池中");
                return null;
            } else {
                return method.invoke(connection, args);
            }
        }
    });
    return connectionProxy;
}

public Connection getConnection() throws SQLException {
    final LinkedList<Connection> connections = connectionThreadLocal.get();
    synchronized (connections) {
        if (connections.size() == 0) {
            try {
                connections.wait();
            } catch (InterruptedException e) {
                e.printStackTrace();
            }
        }
    }
    Connection dataSourceProxy = createProxyDataSource(connections, connections.removeFirst());
    System.out.println(dataSourceProxy + "获取连接");
    return dataSourceProxy;
}


//执行结果,未报错
com.mysql.jdbc.JDBC4Connection@1997267f获取连接
com.mysql.jdbc.JDBC4Connection@1997267f放回连接池中
com.mysql.jdbc.JDBC4Connection@5b6f820d获取连接
com.mysql.jdbc.JDBC4Connection@5b6f820d放回连接池中
com.mysql.jdbc.JDBC4Connection@ef04932获取连接
com.mysql.jdbc.JDBC4Connection@ef04932放回连接池中
com.mysql.jdbc.JDBC4Connection@2d28b5b3获取连接
com.mysql.jdbc.JDBC4Connection@2d28b5b3放回连接池中
com.mysql.jdbc.JDBC4Connection@e122ba0获取连接
com.mysql.jdbc.JDBC4Connection@e122ba0放回连接池中

Process finished with exit code 0

 

 

  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值