Java Top.

使用Spring 5和Spring Boot 2开始,通过学习春天课程:

>>查看课程

1.概述

在本文中,我们将在看Threadlocal.从建构中的构造java.lang.包裹。这使我们能够为当前线程单独存储数据 - 并简单地将其包装在特殊类型的对象中。

2。Threadlocal.API.

Theadlocal.构造允许我们存储将是的数据只能访问经过特定线程

让我们说我们想要拥有一个整数将与特定线程捆绑在一起的值:

threadlocal <整数> threadlocalvalue = new threadlocal <>();

接下来,当我们希望从线程中使用此值时,我们只需要调用a得到()或者放()方法。简单地说,我们可以这么想Threadlocal.将数据存储在地图中 - 带线作为键。

由于这个事实,当我们打电话给得到()对策threadlocalvalue.,我们会得到一个整数请求线程的值:

threadlocalvalue.set(1);Integer结果= threadlocalvalue.get();

我们可以构建一个实例Threadlocal.通过使用境内()静态方法并将供应商传递给它:

threadlocal <整数> threadlocal = threadlocal.withinitial(() - > 1);

从中删除该值Threadlocal.,我们可以拨打电话去掉()方法:

threadlocal.remove();

看如何使用Threadlocal.恰当地,首先,我们将看一个不使用a的示例Threadlocal.,然后我们将重写我们的示例以利用该构建。

3.将用户数据存储在地图中

让我们考虑一个需要存储用户特定的程序语境每个给定用户ID的数据:

公共类上下文{私有字符串用户名;公共上下文(字符串用户名){this.username =用户名;}}

我们希望每个用户ID有一个线程。我们会创建一个sharedmapwithusercontext.实现的课程逃货界面。实施中的实施跑步()方法通过呼叫某些数据库UserRepository.返回a的课程语境给定的对象用户身份

接下来,我们将该上下文存储在Concurenthashmap.关键用户身份

公共类SharedMapWithUserContext实现Runnable {公共静态映射<整数,上下文> userContextPeruserID = new concurrenthashmap <>();私有整数UserID;私有userrepository userrepository = new userrepository();@override public void run(){string username = userrepository.getusernameforUserID(UserId);usercontextperuserId.put(UserID,新上下文(用户名));} //标准构造函数}

我们可以通过创建和启动两个不同的线程来轻松测试我们的代码userids.并断言我们有两个参赛作品UserContextPeruserId.地图:

sharedmapwithusercontext firstuser = new sharedmapwithuserContext(1);sharedmapwithusercontext seconduser = new sharedmapwithusercontext(2);新线程(初选).start();新线程(signuser).start();assertequals(sharedmapwithusercontext.usercontextperuserid.size(),2);

4.存储用户数据Threadlocal.

我们可以重写我们的示例来存储用户语境实例使用A.Threadlocal.。每个线程都有自己的Threadlocal.实例。

使用时Threadlocal.,我们需要非常小心,因为每一个Threadlocal.实例与特定线程相关联。在我们的示例中,我们为每个特定提供专用线程用户身份,此线程由我们创建,因此我们完全控制它。

跑步()方法将获取用户上下文并将其存储到Threadlocal.可变使用放()方法:

公共类ThreadLocalWithUserContext实现Runnable {私有静态Threadlocal <上下文> UserContext = New ThreadLocal <>();私有整数UserID;私有userrepository userrepository = new userrepository();@override public void run(){string username = userrepository.getusernameforUserID(UserId);usercontext.set(新上下文(用户名));system.out.println(给定userid的线程上下文:“+ UserID +”是:“+ UserContext.get());} //标准构造函数}

我们可以通过启动两个将为给定的操作执行操作的线程来测试用户身份

threadlocalwithusercontext firstUser = new threadlocalwithusercontext(1);threadLocalWithusErcontext第二用户= new threadlocalwithusercontext(2);新线程(初选).start();新线程(signuser).start();

运行此代码后,我们将在标准输出上看到Threadlocal.每给定线程设置:

给定userid的线程上下文

我们可以看到每个用户都有自己的语境

5。Threadlocal.s和线程池

Threadlocal.提供易于使用的API,将某些值限制为每个线程。这是一种合理的实现方式线程安全在Java。然而,我们使用的时候应该特别小心Threadlocal.线程池一起。

为了更好地了解可能的警告,让我们考虑以下情景:

  1. 首先,应用程序借用池中的线程。
  2. 然后它将一些线程限制值存储到当前线程中Threadlocal.
  3. 一旦当前执行完成后,应用程序将返回播放的线程到池。
  4. 过了一会儿,应用程序借用相同的线程来处理另一个请求。
  5. 由于应用程序上次未执行必要的清理,因此它可以重复使用相同的Threadlocal.新请求的数据。

这可能对高度并发的应用造成令人惊讶的后果。

解决这个问题的一种方法是手动删除每个问题Threadlocal.一旦我们完成了它。因为这种方法需要严格的代码评论,所以它可能会出错。

5.1。延长ThreadPoolExecutor.

事实证明,可以扩展ThreadPoolExecutor.类并提供自定义挂钩实现beforeexecute()afterexecute()方法。线程池会调用beforeexecute()使用借用线程运行任何内容之前的方法。另一方面,它将称之为afterexecute()执行我们的逻辑后的方法。

因此,我们可以延长ThreadPoolExecutor.班级并删除Threadlocal.数据在afterexecute()方法:

公共类ThreadLocalaWarethReadPool扩展了ThreadPoolExecutor {@override保护的void void后xecute(runnable r,throwable t){//呼叫在每个threadlocal上删除}

如果我们向这一实施提交我们的请求ExecutorService.,然后我们可以确定使用Threadlocal.和线程池不会为我们的应用引入安全危险。

六,结论

在这个快速的文章中,我们正在看Threadlocal.构造。我们实施了使用的逻辑concurrenthashmap.在线程之间共享以存储与特定相关联的上下文用户身份。接下来,我们重写我们的示例以利用Threadlocal.存储与特定关联的数据用户身份和一个特定的线程。

可以找到所有这些示例和代码片段的实现在github上

Java底部

使用Spring 5和Spring Boot 2开始,通过学习春天课程:

>>查看课程
评论在本文上关闭!