Java多线程之ThreadLocal使用和底层原理

x33g5p2x  于2021-10-04 转载在 Java  
字(5.0k)|赞(0)|评价(0)|浏览(507)

ThreadLocal作用

ThreadLocal是一种以空间换时间的做法,在每一个Thread里面维护了一个ThreadLocal.ThreadLocalMap把数据进行隔离,数据不共享,自然就没有线程安全方面的问题了。

简单来说就是多线程访问ThreadLocal实例时,ThreadLocal为每个线程都提供了独立的变量副本

而每个线程修改副本时不影响其他线程对象的副本

他的方法

public void set(T value)

注意set每一个线程中只能添加一次 如果在次添加就会将前面的覆盖

public T get()

自动获取当前线程对象的引用 然后和ThreadLocal内的key进行对比,取出对应的值

public void remove()

删除当线程对象在ThreadLocal内的值

添加值的原理

创建线程必须创建Thread对象而Thread 中有一个属性threadLocals用来存储ThreadLocalMap而ThreadLocalMap中有一个内部类Entry用来存储key ,value

每一个线程第一次使用set的话都会调用createMap这个方法 来对threadLocals初始化这个方法在ThreadLocal类里

void createMap(Thread t, T firstValue) {
        t.threadLocals = new ThreadLocalMap(this, firstValue);
    }

通过这个方法我们可以看出来 存入的key是当前ThreadLocal对象,value是你传入的值

同一个线程第二次调用的话就会调用ThreadLocalMap中的

private void set(ThreadLocal<?> key, Object value) 方法

源码:map.set(this, value);

这个方法主要的作用就是进行覆盖 将你原来的value值给覆盖掉

这就是 添加的原理

获取当线程中存储的值原理

通过Thread.currentThread();获取当前线程Thread对象然后找到此线程下的threadLocals成员属性返回给ThreadLocalMap

ThreadLocalMap类中有一个getEntry(ThreadLocal<?> key) 方法用来通过key获取到我存储键值的 Entry对象 如果没有找到就返回null.然后就可以通过Entry对象中value 获取到值了

(ThreadLocal<?> key) 如果看不动可以 看一下添加原理就知道这个是什么了

删除当前线程中存储的值原理

通过Thread.currentThread()来获取当前线程里的threadLocals成员属性返回给ThreadLocalMap

ThreadLocalMap类中有一个private void remove(ThreadLocal<?> key) 方法用来删除key和value

(ThreadLocal<?> key) 如果看不动可以 看一下添加原理就知道这个是什么了

ThreadLocal的使用方法案例

增加值

//实现多线程
public class test  {
    private static ThreadLocal<Integer> numbercontainer = new ThreadLocal<Integer>() {
// 重写ThreadLocal来实例化对象 否则你直接进行运算会爆NullPointerException错误
        @Override
        protected Integer initialValue() {
            return 0;
        }
    };

    public void  Thread(){
        //线程1
        new Thread(new Runnable() {
            @Override
            public void run() {
                System.out.println(  getNumber());
            }
            public int getNumber() {
                numbercontainer.set(numbercontainer.get() + 1);
                return numbercontainer.get();
            }

        }).start();
        //线程2
        new Thread(new Runnable() {
            @Override
            public void run() {
                System.out.println(  getNumber());
            }
            public int getNumber() {
                numbercontainer.set(numbercontainer.get() + 10);
                return numbercontainer.get();
            }

        }).start();
    }

    @Test
    public void test(){
        Thread();
    }
}

1
10

数据库连接

因为static定义的Connection连接。在多线程下。可能,线程1连接关闭了,线程2使用的时候就会报错了 因此,我们如何 来解决这个问题。利用ThreadLocal线程池。那么我们只需要把connection .管理起来就可以

//可以实现多线程并发操作
public class JDBC_Util {

    private static final ThreadLocal<ResultSet> resultSet=new ThreadLocal<ResultSet>(){
        @Override
        protected ResultSet initialValue() {
            return super.initialValue();
        }
    };

    private static final ThreadLocal<PreparedStatement> prepareStatement=new ThreadLocal<PreparedStatement>(){
        @Override
        protected PreparedStatement initialValue() {
            return super.initialValue();
        }
    };
    private static final ThreadLocal<Connection> connection=new ThreadLocal<Connection>(){
        @Override
        protected Connection initialValue() {
            Connection    connection=null;
            Properties properties=new Properties();

            InputStream is= JDBC_Util.class.getClassLoader().getResourceAsStream("database_url.properties");
            try {
                properties.load(is);
            } catch (IOException e) {

                e.printStackTrace();
            }

            try {
                Class.forName(properties.getProperty("driver"));
            } catch (ClassNotFoundException e) {

                e.printStackTrace();
            }
            try {
                connection=DriverManager.getConnection(properties.getProperty("url"),properties.getProperty("username"),properties.getProperty("password"));
            } catch (SQLException e) {

                e.printStackTrace();
            }

            return  connection;
        }
    };


    //查询 返回 ResultSet对象
    public static ResultSet SELECT(String  sqlString,Object[] objects){
        try {
            prepareStatement.set((PreparedStatement) connection.get().prepareStatement(sqlString));
            if(objects!=null){
                for (int i = 0; i < objects.length; i++) {
                    prepareStatement.get().setObject(i+1, objects[i]);
                }
            }

            resultSet.set(prepareStatement.get().executeQuery());

        } catch (SQLException e) {

            e.printStackTrace();
        }
        return resultSet.get();

    }

    

    //关闭数据库连接 内部方法
    public    static void closeAll(){
        if(resultSet.get()!=null){
            try {
                resultSet.get().close();
            } catch (SQLException e) {

                e.printStackTrace();
            }
        }
        if(prepareStatement.get()!=null){
            try {
                prepareStatement.get().close();
            } catch (SQLException e) {

                e.printStackTrace();
            }

        }

        if(connection.get()!=null){
            try {
                connection.get().close();
            } catch (SQLException e) {

                e.printStackTrace();
            }
        }
        resultSet.remove();
        prepareStatement.remove();
        connection.remove();

    }

}

创建10个线程测试

public static void main(String[] args) {
        int a= 10;
        for (int i = 0; i < a; i++) {
            new Thread(new Runnable() {
                @Override
                public void run() {
                    ResultSet select = JDBC_Util.SELECT("select * from product ", null);
                    try {
                        while (select.next()){
                            System.out.println(select.getObject(1));
                            System.out.println(select.getObject(2));
                            System.out.println(select.getObject(3));
                        }
                    } catch (SQLException e) {
                        e.printStackTrace();
                    }finally {
                        JDBC_Util.closeAll();
                    }
                }
            }).start();
        }
    }

测试结果就不发了 没问题 线程之间互相不影响

因为创建线程就需要new一个 Thread类 每一个Thread类下面都有一个threadLocals来存储ThreadLocalMap 而你的值都存储在你当前Thread类里threadLocals里 获取也是获取当前线程threadLocals里 的东西

所以每个线程通过ThreadLocal内的get方法访问的都是自己线程里的东西 互不干扰

这样不好之处 就是占内存 因每一个线程都要创建一个副本存储到当前线程Thread对象ThreadLocal里 但是不占速度啊,现在时代主要是客户体验

如果使用synchronized的话每次只能一个线程执行其他线程进入阻塞等这个线程执行完后然后在将其他线程唤醒这个过程非常消耗时间

内存泄漏问题

1、Thread中有一个map,就是ThreadLocalMap

2、ThreadLocalMap的key是ThreadLocal,值是我们自己设定的。

3、ThreadLocal是一个弱引用,当为null时,会被当成垃圾回收

4、重点来了,突然我们ThreadLocal是null了,也就是要被垃圾回收器回收了,但是此时我们的ThreadLocalMap生命周期和Thread的一样,它不会回收,这时候就出现了一个现象。那就是ThreadLocalMap的key没了,但是value还在,这就造成了内存泄漏。

解决办法:他加上final 使用完ThreadLocal后,执行remove操作,避免出现内存溢出情况

相关文章