java并发编程推线书_Java中的并发注释。 我们为线着色的方法

java并发编程推线书

Today (May 2020) we have about 100 servers in the production environment, 6,000 HTTP API requests per second and more than 50,000 WebSocket API commands, and daily releases. Miro has been developing since 2011; in the current implementation, user requests are handled in parallel by a cluster of different servers.

如今(2020年5月),我们在生产环境中大约有100台服务器,每秒6,000个HTTP API请求以及50,000多个WebSocket API命令以及每日发布。 Miro自2011年以来一直在发展; 在当前实现中,用户请求由一组不同的服务器并行处理。

image

并发访问控制子系统 (The concurrent access control subsystem)

The core asset of our product is the collaborative user whiteboards, so the main load falls on them. The primary subsystem that controls most of the concurrent access is a stateful system for user sessions on a whiteboard.

我们产品的核心资产是协作用户白板,因此主要的负担落在这些白板上。 控制大多数并发访问的主要子系统是白板上用于用户会话的有状态系统。

When a new whiteboard is opened, a state is created on one of the servers. The state stores both the runtime data required for simultaneous collaboration and display of the content and the system data, such as mapping to the processing threads. Information about which server stores the state is recorded in a distributed data structure and is available to the cluster as long as the server is running and at least one user is on the whiteboard. We use Hazelcast for this part of the subsystem. All new whiteboard connections are forwarded to the server that stores the state.

打开新白板后,将在其中一台服务器上创建状态。 状态存储同时协作和显示内容以及系统数据所需的运行时数据,例如映射到处理线程。 只要服务器正在运行并且至少有一个用户在白板上,有关哪个服务器存储状态的信息就会记录在分布式数据结构中,并且群集可以使用该信息。 我们将Hazelcast用于子系统的这一部分。 所有新的白板连接都转发到存储状态的服务器。

When connecting to the server, the user enters the acceptor thread, whose sole purpose is to bind the connection to the state of the whiteboard, in the threads of which all following work will take place.

连接到服务器时,用户输入接受器线程,其唯一目的是将连接绑定到白板的状态,在该线程中将进行所有后续工作。

There are two threads associated with a whiteboard: a network thread, which handles connections, and a “business” thread, which is responsible for the business logic. This allows us to handle the processing of various types of tasks (network packets and business commands) differently. Processed network commands from users in the form of applied business tasks are queued to the business thread, where they are processed sequentially. This avoids unnecessary synchronization when writing the application logic.

与白板关联的线程有两个:处理连接的网络线程和负责业务逻辑的“业务”线程。 这使我们能够以不同方式处理各种类型的任务(网络数据包和业务命令)。 来自用户的已应用网络任务形式的已处理网络命令被排队到业务线程中,并在此被顺序处理。 这样可以避免在编写应用程序逻辑时不必要的同步。

The division of code into business/application and system code is our internal convention. It allows us to separate the code responsible for user features from the low-level details of communication, scheduling, and storage, which have a service function.

将代码分为业务/应用程序和系统代码是我们的内部惯例。 它使我们能够将负责用户功能的代码与具有服务功能的低级通信,调度和存储细节分开。

If the acceptor thread detects that the whiteboard state doesn’t exist, it will create a corresponding initialization task. State initialization tasks are handled by a different type of thread.

如果接受线程检测到白板状态不存在,它将创建相应的初始化任务。 状态初始化任务由其他类型的线程处理。

This implementation has the following advantages:

此实现具有以下优点:

  • There is no business logic in the service types of threads that could potentially slow down a new connection or I/O operations. This logic is isolated in the special types of threads — “business” threads — to reduce the impact of any potential delay in that thread brought by mistake in the frequently modified business code.

    线程的服务类型中没有业务逻辑,这可能会减慢新连接或I / O操作的速度。 这种逻辑被隔离在特殊类型的线程(“业务”线程)中,以减少由于频繁修改的业务代码中的错误而导致的该线程中任何潜在延迟的影响。
  • State initialization is not performed in the business thread and does not affect the processing time of business commands from users. The initialization can take some time, and business threads process several whiteboards at once, so this way creating new whiteboards does not directly affect the existing ones.

    状态初始化不在业务线程中执行,并且不影响来自用户的业务命令的处理时间。 初始化可能会花费一些时间,并且业务线程会一次处理多个白板,因此以这种方式创建新白板不会直接影响现有白板。
  • The parsing of network commands is usually done faster than their execution, so the network thread pool configuration can differ from the business thread pool configuration to leverage the system resources.

    网络命令的解析通常比其执行快,因此网络线程池配置可能与业务线程池配置不同,以利用系统资源。

着色线 (Coloring the threads)

The subsystem described above is quite nontrivial to implement. The developer has to keep the whole model of relationships between threads in their head and take into account the reverse process of closing the whiteboards. When closing a whiteboard, you have to remove all subscriptions and delete entries from the registries and do this in the same threads where they were originally initialized in the desired sequence.

上面描述的子系统很容易实现。 开发人员必须将整个线程之间的关系模型保持在头脑中,并考虑关闭白板的反向过程。 关闭白板时,必须删除所有订阅并从注册表中删除条目,并在最初按所需顺序对其进行初始化的同一线程中执行此操作。

We noticed that the bugs and difficulties of modifying the code that appeared in this subsystem were often related to misunderstanding of the processing context. Juggling the threads and tasks made it difficult to answer the question of in which thread a particular piece of code was being executed.

我们注意到,修改该子系统中出现的代码的错误和困难通常与对处理上下文的误解有关。 杂乱地处理线程和任务使得很难回答在哪个线程中执行特定代码的问题。

To solve this problem, we used a method of “coloring” the threads — it is a policy aimed at regulating the use of threads in the system. Threads are assigned colors, and methods determine the acceptability of execution within threads. The color here is just an abstraction; it could be another entity, like an enumeration. In Java, annotations can serve as a color markup language:

为了解决这个问题,我们使用了一种“给线程上色”的方法-该策略旨在规范系统中线程的使用。 线程被分配了颜色,并且方法确定线程内执行的可接受性。 这里的颜色只是一种抽象; 它可能是另一个实体,例如枚举。 在Java中,注释可以用作颜色标记语言:

@Color
@IncompatibleColors
@AnyColor
@Grant
@Revoke

Annotations are applied to a method and can be used to define the validity of a method call. For example, if the method’s annotation allows yellow and red colors, the first thread will be able to call that method; for the second, an attempt to call the method will result in an error.

批注应用于方法,可用于定义方法调用的有效性。 例如,如果方法的注释允许黄色和红色,则第一个线程将能够调用该方法;否则,第一个线程将能够调用该方法。 第二,尝试调用该方法将导致错误。

image

We can also specify unacceptable colors:

我们还可以指定不可接受的颜色:

image

We can add and remove thread privileges dynamically:

我们可以动态地添加和删除线程特权:

image

The lack of annotation or annotation as in the example below means that the method can be executed in any thread:

如以下示例所示,缺少注释或注释意味着该方法可以在任何线程中执行:

image

Android developers may be familiar with this approach through the use of MainThread, UiThread, WorkerThread, and similar annotations.

Android开发人员可能会通过使用MainThread,UiThread,WorkerThread和类似的注释来熟悉这种方法。

Thread coloring uses the principle of self-documenting code, and the method itself is well amenable to static analysis. Using static analysis, it is possible to check whether the code is written correctly or not before executing it. If we exclude Grant and Revoke annotations and assume that a thread upon initialization has an immutable set of privileges, it will be a flow-insensitive analysis — a simple version of static analysis that does not take a call order into account.

线程着色使用自记录代码的原理,该方法本身非常适合静态分析。 使用静态分析,可以在执行代码之前检查代码是否正确编写。 如果我们排除Grant和Revoke批注,并假设初始化时的线程具有不变的特权集,则它将是对流量不敏感的分析—静态分析的简单版本,不考虑调用顺序。

When we implemented the thread coloring, we did not have any ready-to-be-used solutions for static analysis in our DevOps infrastructure, so we took a simpler and cheaper path — we created our annotations that are uniquely associated with each thread type. We started using aspects to check the correctness of the annotations at runtime.

当我们实现线程着色时,我们的DevOps基础架构中没有任何静态分析可用的现成解决方案,因此我们采用了更简单,更便宜的途径-我们创建了与每种线程类型唯一关联的注释。 我们开始使用方面在运行时检查批注的正确性。

@Aspect
public class ThreadAnnotationAspect {
   @Pointcut("if()")
   public static boolean isActive() {
      ... // Here we check flags that determine whether aspects are enabled or not. This is used, for instance, in a couple of tests.
   }
@Pointcut("execution(@ThreadAnnotation * *.*(..))")
   public static void annotatedMethod() {
   }
@Around("isActive() && annotatedMethod()")
   public Object around(ProceedingJoinPoint joinPoint) throws Throwable {
      Thread thread = Thread.currentThread();
      Method method = ((MethodSignature) jp.getSignature()).getMethod();
      ThreadAnnotation annotation = getThreadAnnotation(method);
      if (!annotationMatches(annotation, thread)) {
         throw new ThreadAnnotationMismatchException(method, thread);
      }
      return jp.proceed();
   }
}

For aspects, we use the AspectJ extension and a plugin for Maven that performs weaving at compile time. Initially, we set weaving to be performed at load time, when ClassLoader loads the classes. However, we have encountered the fact that the weaver sometimes behaved incorrectly when loading the same class concurrently, as a result of which the original byte code of the class remained unchanged. This resulted in very unpredictable and difficult to reproduce the behavior in the production environment. It is possible that current versions of AspectJ do not have this problem.

对于方面,我们使用AspectJ扩展和用于Maven的插件在编译时执行编织。 最初,我们将编织设置为在ClassLoader加载类时在加载时执行。 但是,我们遇到了这样一个事实,即织机在同时加载同一类时有时会表现不正确,结果是该类的原始字节码保持不变。 这导致在生产环境中非常难以预测且难以再现行为。 当前版本的AspectJ可能没有此问题。

Using aspects allowed us to quickly find most of the problems in the code.

使用方面使我们能够快速找到代码中的大多数问题。

It is important to remember to always keep the annotations up to date: you can delete them, feel lazy about adding them, or aspect weaving can be turned off altogether — in these cases, the thread coloring will quickly lose its relevance and value.

重要的是要记住始终保持注释为最新状态:您可以删除它们,对添加它们感到懒惰,或者可以完全关闭方面编织—在这些情况下,线程着色将很快失去其相关性和价值。

守卫 (GuardedBy)

One of the types of coloring is the GuardedBy annotation from Java.util.concurrent. It delineates access to fields and methods by specifying which locks are required for correct access.

着色的一种类型是Java.util.concurrent中的GuardedBy批注。 它通过指定正确访问所需的锁来描述对字段和方法的访问。

public class PrivateLock {
   private final Object lock = Object();

   @GuardedBy (“lock”)
   Widget widget;
  
    void method() {
      synchronized (lock) {
         //Access or modify the state of widget
      }
   }
}

Modern IDEs even support the analysis of this annotation. For instance, IntelliJ IDEA shows this message if there is something wrong with the code:

现代IDE甚至支持对此注释的分析。 例如,如果代码有问题,则IntelliJ IDEA会显示此消息:

image

The method of coloring threads itself is not new, but it seems that in languages such as Java, where multiple threads are often used to access mutable objects, its use not only in the documentation but also in compiling and building phases could greatly simplify the development of multithreaded code.

为线程着色的方法本身并不新鲜,但是在Java之类的语言中,经常使用多个线程来访问可变对象的方法似乎不仅仅为文档着色,而且在编译和构建阶段也可以极大地简化开发过程。多线程代码。

We still use the aspect implementation of coloring. If you know a more elegant solution or an analysis tool that allows you to increase the stability of this approach to system changes, please feel free to share it.

我们仍然使用着色的方面实现。 如果您知道更优雅的解决方案或分析工具,可以提高这种方法进行系统更改的稳定性,请随时进行共享。

P.S. The article was first published at Medium.

PS这篇文章最初在Medium发表

翻译自: https://habr.com/en/company/miro/blog/501908/

java并发编程推线书

  • 1
    点赞
  • 1
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值