当多个线程占用一个共享成员变量的时候就会出现线程问题,在之前实践的数据源连接池里,存放数据源是的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