Java代码质量改进之:使用ThreadLocal维护线程内部变量

举报
码农星球陆敏技 发表于 2019/03/19 15:18:15 2019/03/19
【摘要】 在上文中,《Java代码质量改进之:同步对象的选择》,我们提出了一个场景:火车站有3个售票窗口,同时在售一趟列车的100个座位。我们通过锁定一个靠谱的同步对象,完成了上面的功能。 现在,让我们反过来,每个窗口负责一趟车。比如一号窗口就卖1号列车的票,二号窗口就卖2号列车的票。不过它们需要同时开始卖票。一:ThreadLocal的最简应用 首先,既然是各卖各的火车了,那么,就不需要同步了。于...

在上文中,《Java代码质量改进之:同步对象的选择》,我们提出了一个场景:火车站有3个售票窗口,同时在售一趟列车的100个座位。我们通过锁定一个靠谱的同步对象,完成了上面的功能。

现在,让我们反过来,每个窗口负责一趟车。比如一号窗口就卖1号列车的票,二号窗口就卖2号列车的票。不过它们需要同时开始卖票。

一:ThreadLocal的最简应用

首先,既然是各卖各的火车了,那么,就不需要同步了。于是代码又回归到:

package com.zuikc.thread;

public class SynchronizedSample01 {

public static void main(String[] args) {

// 创建

TicketWindow window1 = new TicketWindow("售票窗口1");

TicketWindow window2 = new TicketWindow("售票窗口2");

TicketWindow window3 = new TicketWindow("售票窗口3");

window1.start();

window2.start();

window3.start();

}

}

class TicketWindow extends Thread {

// 共100个座位

static int ticket = 100;

public TicketWindow(String name) {

super(name);

}

@Override

public void run() {

// 模拟卖票

while (ticket > 0) {

System.out.println(this.getName() + "卖出了座位号:" + ticket);

ticket--;

try {

Thread.sleep(10);

} catch (InterruptedException e) {

e.printStackTrace();

}

}

}

}

但是当前的代码肯定是不对的,每个线程访问的都是同一个火车的ticket,并且还会出现超售现象。要保证每new一个窗口出来,就有一趟自己的列车,我们就可以用到ThreadLocal对象了。

让我们首先替换掉ticket变量,改为:

// 共100个座位

//static int ticket = 100;

static ThreadLocal<Integer> ticket = new ThreadLocal(){

@Override

protected Object initialValue() {

return new Integer(100);

}

};

然后,售票的代码改为:

@Override

public void run() {

int currentNum = ticket.get().intValue();

while (currentNum > 0) {

System.out.println(this.getName() + "卖出了座位号:" + currentNum);

currentNum = currentNum - 1;

ticket.set(currentNum);

try {

Thread.sleep(100);

} catch (InterruptedException e) {

e.printStackTrace();

}

}

}

虽然ticket依然是一个static变量,但是,运行程序你会发现,新起一个线程,不同的线程还是会拥有自己的ticket,不会互相干扰。也就是实现了每个窗口卖自己那趟车的目标。

二:ThreadLocal VS 实例变量

每一个程序员都应该是杠精。为什么,因为回过神来的我们发现,只要回到第一段代码中,把ticket中的static去掉,就能达到同样的目的哈:

class TicketWindow extends Thread {

// 共100个座位

int ticket = 100;

public TicketWindow(String name) {

super(name);

}

@Override

public void run() {

// 模拟卖票

while (ticket > 0) {

System.out.println(this.getName() + "卖出了座位号:" + ticket);

ticket--;

try {

Thread.sleep(10);

} catch (InterruptedException e) {

e.printStackTrace();

}

}

}

}

试下上面的代码,是不是也能达到各卖各的目的?

我们是脑袋被门板挤了,才想出来一个TheadLocal这样的复杂方案吗?

如果单纯说上面的这段代码,是的。但是,还有很多的场合,是ThreadLocal的用武之处。比如,我们并不永远使用extends Thead的方式来写多线程,我们还可能用implements Runnable的方式来写多线程(ps:还有更多的写法哦),如下:

package com.zuikc.thread;

public class SynchronizedSample02 {

public static void main(String[] args) {

// 创建

Selling sell = new Selling();

Thread t1 = new Thread(sell);

t1.setName("售票窗口1");

Thread t2 = new Thread(sell);

t2.setName("售票窗口2");

Thread t3 = new Thread(sell);

t3.setName("售票窗口3");

t1.start();

t2.start();

t3.start();

}

}

class Selling implements Runnable{

// 共100个座位

int ticket = 100;

@Override

public void run() {

// 模拟卖票

while (ticket > 0) {

System.out.println(Thread.currentThread().getName() + "卖出了座位号:" + ticket);

ticket--;

try {

Thread.sleep(100);

} catch (InterruptedException e) {

e.printStackTrace();

}

}

}

}

而在这种情况下,我们就不得不使用TheadLocal了,这里就不放出代码了,大家可以试一下。

甚至,更进一步的,我们是不是能够把ticket这个变量放进方法内部呢,如果放入方法内部的话,我们同样也是必须要使用ThreadLocal才能达到实现目的,如下:

class Selling implements Runnable{

@Override

public void run() {

// 共100个座位

ThreadLocal<Integer> ticket = new ThreadLocal() {

@Override

protected Object initialValue() {

return new Integer(100);

}

};

// 模拟卖票

int currentNum = ticket.get().intValue();

while (currentNum > 0) {

System.out.println(Thread.currentThread().getName() + "卖出了座位号:" + currentNum);

currentNum = currentNum - 1;

ticket.set(currentNum);

try {

Thread.sleep(10);

} catch (InterruptedException e) {

e.printStackTrace();

}

}

}

}

总之,简单来说:当要运行的代码本身不是很方便访问当前的线程实例的时候,就是ThreadLocal的用武之地。

三:ThreadLocal的应用场景

ThreadLocal有这样一些应用场景,比如连接池管理、会话管理等等。

在连接池的管理中,当我们需要获取一个连接,就应该为每一次获取给出不同的连接。在web应用中,请求是被线程池管理的,也就是说获取连接这个行为不是单线程行为,所以我们最好就要设计成不同的线程不能获取同一个连接,要保证能做到这样,就应该使用ThreadLocal了。

可能有人会表示,那不能设计成实例变量吗?答案是:不能。因为,在web应用中,线程都不是被我们自己管理的,所以,最佳的做法就是使用ThreadLocal。一个标准的做法如下:

class DbUtil implements Runnable{

    private static ThreadLocal<Connection> connectionHolder = new ThreadLocal<Connection>() {

        public Connection initialValue() {

                return DriverManager.getConnection(DB_URL);

        }

    };

    public static Connection getConnection() {

           return connectionHolder.get();

    }

}

最后作为补充,我们再来看看hibernate中ThreadLocal的应用:

private static final ThreadLocal<Session> threadSession = new ThreadLocal<Session>();  

    public static Session getSession() throws InfrastructureException {  

        Session s = threadSession.get();  

        try {  

            if (s == null) {  

                s = getSessionFactory().openSession();  

                threadSession.set(s);  

            }  

        } catch (HibernateException ex) {  

            throw new InfrastructureException(ex);  

        }  

        return s;  

    }

感谢关注“码农星球”。本文版权属于“码农星球”。我们提供咨询和培训服务,关于本文有任何困惑,请关注并联系我们。

【版权声明】本文为华为云社区用户原创内容,转载时必须标注文章的来源(华为云社区)、文章链接、文章作者等基本信息, 否则作者和本社区有权追究责任。如果您发现本社区中有涉嫌抄袭的内容,欢迎发送邮件进行举报,并提供相关证据,一经查实,本社区将立刻删除涉嫌侵权内容,举报邮箱: cloudbbs@huaweicloud.com
  • 点赞
  • 收藏
  • 关注作者

评论(0

0/1000
抱歉,系统识别当前为高风险访问,暂不支持该操作

全部回复

上滑加载中

设置昵称

在此一键设置昵称,即可参与社区互动!

*长度不超过10个汉字或20个英文字符,设置后3个月内不可修改。

*长度不超过10个汉字或20个英文字符,设置后3个月内不可修改。