用ThreadLocal创建多线程的连接Connection


我们在开发中经常会看到别人用static这个关键字,来修饰变量。作为全局变量来使用。问题就在于,如果是多线程的情况下。往往会报错。

直接看问题,

例如:我们生成一个序列号。

第一:创建接口

package com.huojg.Sequence;
/**
 * 一个序列号生成器
 * @author huojg21442
 *每次调用getNumbenr就得到一个序列号,下次调用序列号就会自增
 */
public interface Sequence {
    int getNumber();
}

第二:创建线程

package com.huojg.Sequence;

/**
 * 做一个线程类。
 * @author huojg21442
 *
 */
public class ClientThread extends Thread {
private Sequence sequence;

public ClientThread(Sequence sequence) {
    this.sequence = sequence;
}
@Override
    public void run() {
        for(int i=0;i<3;i++){
        System.out.println(Thread.currentThread().getName()+"=>"+sequence.getNumber());        
        }
    }
}

 

第三:创建接口的实现类

package com.huojg.Sequence;
/**
 * 接口实现类
 * @author huojg21442
 *
 */
public class SequenceA implements Sequence {
private static int number=0;
    @Override
    public int getNumber() {
        number=number+1;
        return number;
    }
public static void main(String[] args) {
    Sequence sequence =new SequenceA();
    
    ClientThread thread1=new ClientThread(sequence);
    ClientThread thread2=new ClientThread(sequence);
    ClientThread thread3=new ClientThread(sequence);
    thread1.start();
    thread2.start();
    thread3.start();
}
/**
 * 查看结果我们知道,一个线程执行完毕,另一个,却没有从0开始执行。这是因为线程之间共享了static变量,
 * 无法保证对于不同的线程是安全的 ,也就是说无法保证线程的安全。
 * 
 * 那么如何保证线程安全呢。    
 * 
 * 
 * Thread-0=>1
Thread-0=>2
Thread-0=>3
Thread-1=>4
Thread-1=>5
Thread-1=>6
Thread-2=>7
Thread-2=>8
Thread-2=>9
 * 
 * 
 * 
 */
}

问题分析:在多线程情况下,static定义的静态全局变量,不能满足多线程的情况。正常的结果,不可能出现大于3的数。然而结果,累加起来。

这是因为,static定义全局变量,多线程共享使用了这个变量的 结果。产生线程不安全的结果。那么如何让线程安全呢?。线程池ThreadLocal类。

 

用例如下:

package com.huojg.Sequence;
/**
 * 
 * 安全线程     用线程池。来完成、
 * */
public class SequenceB implements Sequence {
private static  ThreadLocal<Integer>  numbercontainer=new ThreadLocal<Integer>(){
    
@Override
protected Integer initialValue(){
    return 0;
}
};

    public int getNumber() {
        numbercontainer.set(numbercontainer.get()+1);
        return numbercontainer.get();
    }
    public static void main(String[] args) {
        Sequence sequence =new SequenceB();
        
        ClientThread thread1=new ClientThread(sequence);
        ClientThread thread2=new ClientThread(sequence);
        ClientThread thread3=new ClientThread(sequence);
        thread1.start();
        thread2.start();
        thread3.start();
    }
/**
 * 
 * 通过ThreadLocal封装了一个Ingeter类型的numberContainer静态成员变量。并且初始值,是0
 * 
 * 每个线程相互独立了。,同样是static变量,对于不同的线程而言。它没有被共享。而是每个 线程一份,这也保证了线程的安全。
 * 
 * ThreadLocal为每一个线程提供了一个独立的副本。
 * 
 * 
 * 总结 ThreadLocal  API
 * 
 * public void set(T value),将值放入线程的局部变量中。
 * protected T initialValue();返回线程局部变量的初始值。默认为null、
 * 
 * 
 * 
 * */    
    
    
    
}

 

结果查看:

Thread-2=>1
Thread-0=>1
Thread-1=>1
Thread-0=>2
Thread-2=>2
Thread-0=>3
Thread-1=>2
Thread-2=>3
Thread-1=>3

我们利用ThreadLocal完成了,多线程,各自为资源的使用。这是为什么呢?.为什么用ThreadLocal就可以保证。线程各自利用各自的资源的呢 ?我们查看源码

分析如下。ThreadLocal的源码:方法

 

public void set(T value){
    container.put(Thread.currentThread(),value);
}
public T get(){
    Thread  thread=Thread.currentThread();
    T value=container.get(thread);
    
    if(value==null&&!container.containsKey(thread)){
        value=initialValue();
        container.put(thread, value);
    }
    return value;
}
public void remove(){
    container.remove(Thread.currentThread());
}    
    protected T initialValue() {
        return null;
    }

我们会发现,ThreadLocal的初始化默认值value是null。因为他是protected。这个单词告诉我们就是当我们使用ThreadLocal的时候,需要重写这个方法。

并且里面只是封装了一个Map而已。那么我们自己就可以开发一个,山寨版的,ThreadLocal类

package com.huojg.Sequence;

import java.util.Collections;
import java.util.HashMap;
import java.util.Map;

/**
 * 
 * 
 * 熟悉了Threadlocal原理,,其实就是里面封装了一个Map
 * 
 * 自己写一个ThreadLocal
 * 
 * */
public class MyThreadLocal<T> {
    private  Map<Thread,T> container=Collections.synchronizedMap(new HashMap<Thread,T>());
public void set(T value){
    container.put(Thread.currentThread(),value);
}
public T get(){
    Thread  thread=Thread.currentThread();
    T value=container.get(thread);
    
    if(value==null&&!container.containsKey(thread)){
        value=initialValue();
        container.put(thread, value);
    }
    return value;
}
public void remove(){
    container.remove(Thread.currentThread());
}    
    protected T initialValue() {
        return null;
    }
    
}

 

通过以上我们我知道。线程池内部原理以及实现方式了,

那么这个类在实际开发中我们应该用到那里呢,

例如:利用线程池管理数据库连接的问题:

1.获取连接类

package com.huojg.Sequence;

import java.sql.Connection;
import java.sql.DriverManager;


/**
 * 封装数据库的常用工具
 * 
 * 
 * */
public class DBUtil {
//数据库配置
private static final String driver="com.mysql.jdbc.Driver";
private static final String url="jdbc:mysql://localhost:3306/pay-routing";
private static final String username="root";
private static final String password="huojianguo";
//定义一个数据库连接。多线程情况下报错。No operations allowed after connection closed.
private static Connection conn=null;


//获取连接
public static Connection getConnection(){

    try {
        
            Class.forName(driver);
            conn=DriverManager.getConnection(url, username, password);    
        
        
    } catch (Exception e) {
        e.printStackTrace();
    }
    return conn;
}
public static void closeConnection(){
    
    try {
        if(conn!=null){
            conn.close();
        }
    } catch (Exception e) {
        e.printStackTrace();
    }
}

}

2.产品接口类

package com.huojg.Sequence;
/**
 * 里面设置了static Connection,下面定义一个接口,供给逻辑层调用。
 * 
 * 
 * */
public interface ProductService {
void updateProductPrice(String productId,int price);
}

3,多线程

package com.huojg.Sequence;
/**
 * 
 * ProductServiceImpl实现类数据库的,产品价格更新,log新增的操作。
 * 但是多线程情况下。会出错
 * 分析原因,是由于,static静态资源竞争的问题。
 * 
 * 下面写这个测试多线程的 情况下 ,
 * */
public class ClientThreadProduct extends Thread {
    private ProductService  productService;
    
    
    //利用构造函数,把接口初始化了、
    public ClientThreadProduct(ProductService productService) {
        this.productService = productService;
        
    }
    @Override
    public void run() {
        System.out.println(Thread.currentThread().getName());
        productService.updateProductPrice("1", 7000);
    }
}

 

4,接口实现类

package com.huojg.Sequence;

import java.sql.Connection;
import java.sql.PreparedStatement;
import java.text.SimpleDateFormat;
import java.util.Date;


public class ProductServiceImpl implements ProductService {
    private static final String UPDATE_PRODUCT_SQL="update product set price=? where id=?";
    private static final String INSERT_PRODUCT_SQL="insert into log(created,description) values(?,?)";

    @Override
    public void updateProductPrice(String productId, int price) {
        try {
            //获取链接
            Connection conn=DBUtil.getConnection();
            conn.setAutoCommit(false);//关闭自动提交事务(开启事务);
            //执行操作,更新产品,插入日志
            updateProductPrice(conn,UPDATE_PRODUCT_SQL,productId,price);
            insertLog(conn,INSERT_PRODUCT_SQL,"create product.");
            //最后提交事务
            conn.commit();
            
        } catch (Exception e) {
            e.printStackTrace();
        }finally {
            //关闭连接
            DBUtil.closeConnection();
        }
    }
public static void updateProductPrice (Connection conn,String updateProductSQL,String productId,int price)throws Exception{
    PreparedStatement pt=conn.prepareStatement(updateProductSQL);
    pt.setInt(1, price);
    pt.setString(2, productId);
    int rows=pt.executeUpdate();
    if (rows!=0) {
    System.out.println("update product success!");    
    }
}
public static void insertLog(Connection conn,String insertProductSQL,String logDescription)throws Exception{
    PreparedStatement pt=conn.prepareStatement(insertProductSQL);
    pt.setString(1, new SimpleDateFormat("YYYy-MM-dd HH:mm:ss").format(new Date()));
    pt.setString(2,logDescription);
    int rows=pt.executeUpdate();
    if (rows!=0) {
    System.out.println("insert log success!");    
    }
}
public static void main(String[] args) {
    //模拟一下数据测试。
    
    //productService.updateProductPrice("1",300);
    
    //多线程情况下测试  出现错误  No operations allowed after connection closed.
    for (int i=0;i<10;i++){
        ProductService  productService=new ProductServiceImpl();
        ClientThreadProduct thread=new ClientThreadProduct(productService);
        thread.start();
    }
}


}

 

结果:

出现错误  No operations allowed after connection closed 

查看结果我们看出来。是连接关闭。为什么10个线程,连接就自己关闭了呢。分析我们发现。报错原因。就是因为static定义的Connection连接。

用于static定义成全局变量以后,多线程下。可能,线程1关闭了,线程2开启的 关系。因此,我们如何 来解决这个问题。利用ThreadLocal线程池。

那么我们只需要把connection .管理起来就可以,只需要写过一下。DBUtil类的连接。

代码如下:
package com.huojg.Sequence;

import java.sql.Connection;
import java.sql.DriverManager;


/**
 * 封装数据库的常用工具
 * 
 * 
 * */
public class DBUtil {
//数据库配置
private static final String driver="com.mysql.jdbc.Driver";
private static final String url="jdbc:mysql://localhost:3306/pay-routing";
private static final String username="root";
private static final String password="huojianguo";
//定义一个数据库连接。多线程情况下报错。No operations allowed after connection closed.
//private static Connection conn=null;
//由于Connection单连接,不能在多线程中使用,因此改写成如下形式,
//定义一个用于放置数据库连接的局部线程变量(使每个线程都有自己的连接)
private static  ThreadLocal<Connection> connContainer =new ThreadLocal<Connection>();


//获取连接
public static Connection getConnection(){
    Connection conn=connContainer.get();
    try {
        if (conn==null) {
            Class.forName(driver);
            conn=DriverManager.getConnection(url, username, password);    
        }
        
    } catch (Exception e) {
        e.printStackTrace();
    }finally {
        connContainer.set(conn);
    }
    return conn;
}
public static void closeConnection(){
    Connection conn=connContainer.get();
    try {
        if(conn!=null){
            conn.close();
        }
    } catch (Exception e) {
        e.printStackTrace();
    }finally {
        connContainer.remove();
    }
}
/**
 * 把connection连接放入ThreadLocal线程池中。这样每个线程之间隔离了。不会相互干扰了、
 * 
 * 
 * 
 * */
}

结果测试;

Thread-0
Thread-1
Thread-2
Thread-5
Thread-9
Thread-6
Thread-3
Thread-4
Thread-7
Thread-8
update product success!
insert log success!
update product success!
insert log success!
update product success!
insert log success!
update product success!
insert log success!
update product success!
insert log success!
update product success!
insert log success!
update product success!
insert log success!
update product success!
insert log success!
update product success!
insert log success!
update product success!
insert log success!

成功解决。数据库在多线程的情况下使用。

ThreadLocal类会把Connection变成。内部的局部变量来管理。各自启动各自的connection连接。

 


免责声明!

本站转载的文章为个人学习借鉴使用,本站对版权不负任何法律责任。如果侵犯了您的隐私权益,请联系本站邮箱yoyou2525@163.com删除。



 
粤ICP备18138465号  © 2018-2025 CODEPRJ.COM