线程安全的7种技术

描述

本文翻译7 Techniques for Thread-Safe Classes。文中描述线程安全可以使用无状态、无共享状态、消息传递、不可变状态、Synchronized、Lock、Volatile7种技术实现。

正文

在 Java 中创建线程安全类的方法有很多。 在这里,我们将深入讨论状态处理、消息传递、volatile等。

几乎每个 Java 应用程序都使用线程。 像 Tomcat 这样的 web 服务器在单独的工作线程中处理每个请求,fat client(胖客户端) 在专用工作线程中处理长时间运行的请求,甚至批处理过程也使用 java.util.concurrent.ForkJoinPool去改进性能。

因此,有必要以线程安全的方式编写类,这可以通过以下技术之一来实现。

无状态

当多个线程访问同一个实例或静态变量时,必须以某种方式协调对该变量的访问。 最简单的方法就是避免使用实例或静态变量。 没有实例变量的类中的方法只使用局部变量和方法参数。 下面的示例显示了这样一个方法,它是 java.lang.Math的一部分:

public static int subtractExact(int x, int y) {
    int r = x - y;
    if (((x ^ y) & (x ^ r)) < 0) {
        throw new ArithmeticException("integer overflow");
    }
    return r;
}

无共享状态

如果不能避免状态,则不要共享状态。 状态应该只由一个线程拥有。这种技术的一个例子是 SWT 或 Swing GUI框架的事件处理线程

你可以通过扩展线程类和添加线程实例变量来实现线程局部实例变量。 在下面的示例中,字段 pool 和 workQueue 对于单个工作线程是本地的。

package java.util.concurrent;
public class ForkJoinWorkerThread extends Thread {
    final ForkJoinPool pool;                
    final ForkJoinPool.WorkQueue workQueue; 
}

实现线程局部变量的另一种方法是使用类 java.lang.ThreadLocal 保存 你想使其成线程局部变量的字段。 下面是一个使用 java.lang.ThreadLocal 的实例变量:

public class CallbackState {
public static final ThreadLocal<CallbackStatePerThread> callbackStatePerThread = 
    new ThreadLocal<CallbackStatePerThread>()
   {
      @Override
        protected CallbackStatePerThread  initialValue()
      { 
       return getOrCreateCallbackStatePerThread();
      }
   };
}

你可以在 java.lang.Threadlocal 中包装实例变量的类型。通过initialValue()为 java.lang.Threadlocal提供初始值。

下面展示了如何使用实例变量:

CallbackStatePerThread callbackStatePerThread = CallbackState.callbackStatePerThread.get();

通过调用 get() ,您将接收到当前线程关联的对象。

因为,在应用服务器中线程池用于处理请求,即 java.lang.Threadlocal 会导致这种环境中的高内存消耗。 因此不建议将其用于由应用程序服务器的请求处理线程中。

信息传递

如果您不使用上述技术共享状态,则需要线程进行通信的方法。 实现这一点的一种技术是在线程之间传递消息。 您可以使用 java.util.concurrent 包中的并发队列来实现消息传递。 或者,更好的方法是使用像 Akka 这样的框架,一个 Actor 风格的并发框架。 下面的示例演示如何使用 Akka 发送消息:

target.tell(message, getSelf());

然后接受信息:

@Override
public Receive createReceive() {
     return receiveBuilder()
        .match(String.class, s -> System.out.println(s.toLowerCase()))
        .build();
}

不可变状态

为了避免发送线程在另一个线程读取消息时更改消息的问题,消息应该是不可变的。 因此,Akka 框架有一个约定,即所有消息都必须是不可变的。

实现不可变类时,应该将其字段声明为 final。 这不仅可以确保编译器能够检查字段实际上是不可变的,而且还可以使它们在发布不正确的情况下正确地初始化
。 下面是最后一个实例变量的例子:

public class ExampleFinalField
{
    private final int finalField;
    public ExampleFinalField(int value)
    {
        this.finalField = value;
    }
}

使用 java.util.concurrent 下的数据结构

消息传递使用并发队列在线程之间进行通信。 并发队列是 java.util.Concurrent 包中提供的数据结构之一。 此包为Map、Queue、Dequeue、Set和List提供了并发处理类。 这些数据结构经过了高度优化,并经过了线程安全性测试。

Synchronized

如果不能使用上述技术之一,请使用同步锁。 通过在 synchronized 块中放置一个锁,可以确保一次只有一个线程可以执行此部分。

synchronized(lock)
{
    i++;
}

请注意,当您使用多个嵌套同步块时会面临死锁的风险。 当两个线程试图获取对方持有的锁时,就会发生死锁

Volatile

普通的、nonvolatile的字段可以缓存在寄存器或缓存器中。 通过将变量声明为 volatile,可以告诉 JVM 和编译器始终返回最新写入的值。 这不仅适用于变量本身,而且适用于已写入 volatile 字段的线程所写的所有值。 下面是一个 volatile 实例变量的例子:

public class ExampleVolatileField
{
    private volatile int  volatileField;
}

如果写操作不依赖于当前值,则可以使用 volatile 字段。 或者,如果您可以确保一次只有一个线程可以更新字段

更多的技巧

我排除了以下高级技术:

  • 原子更新:一种调用原子指令的技术,比如由 CPU 提供的 compare 和 set
  • Reentrantlock:比 synchronized 块提供更多灵活性的锁实现
  • Reentrantreadwritelock:一个读不阻塞的锁实现
  • StampedLock:一个非标准的读写锁,可以乐观地读取值

总结

实现线程安全的最佳方法是避免共享状态。 对于共享状态,您可以用不可变类和消息解析组合,也可以同步块和 volatile 字段一起使用并发数据结构组合。 如果您想测试您的应用程序是否线程安全的,可以免费尝试 vmlens

我很高兴听到您介绍实现线程安全类所使用的技术。

原文

https://dzone.com/articles/7-techniques-for-thread-safe-classes

最后编辑于
©著作权归作者所有,转载或内容合作请联系作者
  • 序言:七十年代末,一起剥皮案震惊了整个滨河市,随后出现的几起案子,更是在滨河造成了极大的恐慌,老刑警刘岩,带你破解...
    沈念sama阅读 219,928评论 6 509
  • 序言:滨河连续发生了三起死亡事件,死亡现场离奇诡异,居然都是意外死亡,警方通过查阅死者的电脑和手机,发现死者居然都...
    沈念sama阅读 93,748评论 3 396
  • 文/潘晓璐 我一进店门,熙熙楼的掌柜王于贵愁眉苦脸地迎上来,“玉大人,你说我怎么就摊上这事。” “怎么了?”我有些...
    开封第一讲书人阅读 166,282评论 0 357
  • 文/不坏的土叔 我叫张陵,是天一观的道长。 经常有香客问我,道长,这世上最难降的妖魔是什么? 我笑而不...
    开封第一讲书人阅读 59,065评论 1 295
  • 正文 为了忘掉前任,我火速办了婚礼,结果婚礼上,老公的妹妹穿的比我还像新娘。我一直安慰自己,他们只是感情好,可当我...
    茶点故事阅读 68,101评论 6 395
  • 文/花漫 我一把揭开白布。 她就那样静静地躺着,像睡着了一般。 火红的嫁衣衬着肌肤如雪。 梳的纹丝不乱的头发上,一...
    开封第一讲书人阅读 51,855评论 1 308
  • 那天,我揣着相机与录音,去河边找鬼。 笑死,一个胖子当着我的面吹牛,可吹牛的内容都是我干的。 我是一名探鬼主播,决...
    沈念sama阅读 40,521评论 3 420
  • 文/苍兰香墨 我猛地睁开眼,长吁一口气:“原来是场噩梦啊……” “哼!你这毒妇竟也来了?” 一声冷哼从身侧响起,我...
    开封第一讲书人阅读 39,414评论 0 276
  • 序言:老挝万荣一对情侣失踪,失踪者是张志新(化名)和其女友刘颖,没想到半个月后,有当地人在树林里发现了一具尸体,经...
    沈念sama阅读 45,931评论 1 319
  • 正文 独居荒郊野岭守林人离奇死亡,尸身上长有42处带血的脓包…… 初始之章·张勋 以下内容为张勋视角 年9月15日...
    茶点故事阅读 38,053评论 3 340
  • 正文 我和宋清朗相恋三年,在试婚纱的时候发现自己被绿了。 大学时的朋友给我发了我未婚夫和他白月光在一起吃饭的照片。...
    茶点故事阅读 40,191评论 1 352
  • 序言:一个原本活蹦乱跳的男人离奇死亡,死状恐怖,灵堂内的尸体忽然破棺而出,到底是诈尸还是另有隐情,我是刑警宁泽,带...
    沈念sama阅读 35,873评论 5 347
  • 正文 年R本政府宣布,位于F岛的核电站,受9级特大地震影响,放射性物质发生泄漏。R本人自食恶果不足惜,却给世界环境...
    茶点故事阅读 41,529评论 3 331
  • 文/蒙蒙 一、第九天 我趴在偏房一处隐蔽的房顶上张望。 院中可真热闹,春花似锦、人声如沸。这庄子的主人今日做“春日...
    开封第一讲书人阅读 32,074评论 0 23
  • 文/苍兰香墨 我抬头看了看天上的太阳。三九已至,却和暖如春,着一层夹袄步出监牢的瞬间,已是汗流浃背。 一阵脚步声响...
    开封第一讲书人阅读 33,188评论 1 272
  • 我被黑心中介骗来泰国打工, 没想到刚下飞机就差点儿被人妖公主榨干…… 1. 我叫王不留,地道东北人。 一个月前我还...
    沈念sama阅读 48,491评论 3 375
  • 正文 我出身青楼,却偏偏与公主长得像,于是被迫代替她去往敌国和亲。 传闻我的和亲对象是个残疾皇子,可洞房花烛夜当晚...
    茶点故事阅读 45,173评论 2 357