Java并发教程–线程安全设计

在回顾了处理并发程序时的主要风险(如原子性或可见性 )之后,我们将进行一些类设计,以帮助我们防止上述错误。 其中一些设计导致了线程安全对象的构造,从而使我们可以在线程之间安全地共享它们。 作为示例,我们将考虑不可变和无状态的对象。 其他设计将阻止不同的线程修改相同的数据,例如线程局部变量。

您可以在github上查看所有源代码。


1.不可变的对象

不可变的对象具有状态(具有表示对象状态的数据),但是它是基于构造构建的,一旦实例化了对象,就无法修改状态。

尽管线程可以交错,但是对象只有一种可能的状态。 由于所有字段都是只读的,因此没有一个线程可以更改对象的数据。 因此,不可变对象本质上是线程安全的。

产品显示了一个不变类的示例。 它在构建期间构建所有数据,并且其任何字段均不可修改:

public final class Product {private final String id;private final String name;private final double price;public Product(String id, String name, double price) {this.id = id;this.name = name;this.price = price;}public String getId() {return this.id;}public String getName() {return this.name;}public double getPrice() {return this.price;}public String toString() {return new StringBuilder(this.id).append("-").append(this.name).append(" (").append(this.price).append(")").toString();}public boolean equals(Object x) {if (this == x) return true;if (x == null) return false;if (this.getClass() != x.getClass()) return false;Product that = (Product) x;if (!this.id.equals(that.id)) return false;if (!this.name.equals(that.name)) return false;if (this.price != that.price) return false;return true;}public int hashCode() {int hash = 17;hash = 31 * hash + this.getId().hashCode();hash = 31 * hash + this.getName().hashCode();hash = 31 * hash + ((Double) this.getPrice()).hashCode();return hash;}
}

在某些情况下,将字段定为最终值还不够。 例如,尽管所有字段都是最终的,但MutableProduct类不是不可变的:

public final class MutableProduct {private final String id;private final String name;private final double price;private final List<String> categories = new ArrayList<>();public MutableProduct(String id, String name, double price) {this.id = id;this.name = name;this.price = price;this.categories.add("A");this.categories.add("B");this.categories.add("C");}public String getId() {return this.id;}public String getName() {return this.name;}public double getPrice() {return this.price;}public List<String> getCategories() {return this.categories;}public List<String> getCategoriesUnmodifiable() {return Collections.unmodifiableList(categories);}public String toString() {return new StringBuilder(this.id).append("-").append(this.name).append(" (").append(this.price).append(")").toString();}
}

为什么以上类别不是一成不变的? 原因是我们让引用脱离了其类的范围。 字段“ category ”是一个可变的引用,因此在返回它之后,客户端可以对其进行修改。 为了显示此,请考虑以下程序:

public static void main(String[] args) {MutableProduct p = new MutableProduct("1", "a product", 43.00);System.out.println("Product categories");for (String c : p.getCategories()) System.out.println(c);p.getCategories().remove(0);System.out.println("\nModified Product categories");for (String c : p.getCategories()) System.out.println(c);
}

和控制台输出:

Product categoriesABC
Modified Product categoriesBC

由于类别字段是可变的,并且逃脱了对象的范围,因此客户端已修改类别列表。 该产品原本是一成不变的,但已经过修改,从而进入了新的状态。

如果要公开列表的内容,可以使用列表的不可修改视图:

public List<String> getCategoriesUnmodifiable() {return Collections.unmodifiableList(categories);
}

2.无状态对象

无状态对象类似于不可变对象,但是在这种情况下,它们没有状态,甚至没有一个状态。 当对象是无状态的时,它不必记住两次调用之间的任何数据。

由于没有修改状态,因此一个线程将无法影响另一线程调用对象操作的结果。 因此,无状态类本质上是线程安全的。

ProductHandler是此类对象的示例。 它包含对Product对象的多项操作,并且在两次调用之间不存储任何数据。 操作的结果不取决于先前的调用或任何存储的数据:

public class ProductHandler {private static final int DISCOUNT = 90;public Product applyDiscount(Product p) {double finalPrice = p.getPrice() * DISCOUNT / 100;return new Product(p.getId(), p.getName(), finalPrice);}public double sumCart(List<Product> cart) {double total = 0.0;for (Product p : cart.toArray(new Product[0])) total += p.getPrice();return total;}
}

在其sumCart方法,所述ProductHandler产品列表转换成一个阵列,因为for-each循环通过它的元件使用的迭代器内部进行迭代。 列表迭代器不是线程安全的,如果在迭代过程中进行了修改,则可能引发ConcurrentModificationException 。 根据您的需求,您可以选择其他策略 。

3.线程局部变量

线程局部变量是在线程范围内定义的那些变量。 没有其他线程会看到或修改它们。

第一种是局部变量。 在下面的示例中, total变量存储在线程的堆栈中:

public double sumCart(List<Product> cart) {double total = 0.0;for (Product p : cart.toArray(new Product[0])) total += p.getPrice();return total;
}

只要考虑一下,如果您定义引用并返回它,而不是原始类型,它将逃避其范围。 您可能不知道返回的引用存储在哪里。 调用sumCart方法的代码可以将其存储在静态字段中,并允许在不同线程之间共享。

第二种类型是ThreadLocal类。 此类为每个线程提供独立的存储。 可以从同一线程内的任何代码访问存储在ThreadLocal实例中的值。

ClientRequestId类显示ThreadLocal用法的示例:

public class ClientRequestId {private static final ThreadLocal<String> id = new ThreadLocal<String>() {@Overrideprotected String initialValue() {return UUID.randomUUID().toString();}};public static String get() {return id.get();}
}

ProductHandlerThreadLocal类使用ClientRequestId在同一线程中返回相同的生成ID:

public class ProductHandlerThreadLocal {//Same methods as in ProductHandler classpublic String generateOrderId() {return ClientRequestId.get();}
}

如果执行main方法,则控制台输出将为每个线程显示不同的ID。 举个例子:

T1 - 23dccaa2-8f34-43ec-bbfa-01cec5df3258T2 - 936d0d9d-b507-46c0-a264-4b51ac3f527dT2 - 936d0d9d-b507-46c0-a264-4b51ac3f527dT3 - 126b8359-3bcc-46b9-859a-d305aff22c7e...

如果要使用ThreadLocal,则应注意在线程池化时(例如在应用程序服务器中)使用它的一些风险。 您可能最终在请求之间出现内存泄漏或信息泄漏。 自从“ 如何与ThreadLocals一起开枪自杀”一文很好地解释了这种情况的发生之后,我将不再扩展本主题。

4.使用同步

提供对对象的线程安全访问的另一种方法是通过同步。 如果我们将对引用的所有访问同步,则在给定时间只有一个线程将访问它。 我们将在后续帖子中对此进行讨论。

5.结论

我们已经看到了几种技术,可以帮助我们构建可以在线程之间安全共享的更简单的对象。 如果一个对象可以具有多个状态,则防止并发错误要困难得多。 另一方面,如果一个对象只能有一个状态或没有状态,则不必担心不同的线程同时访问它。

翻译自: https://www.javacodegeeks.com/2014/08/java-concurrency-tutorial-thread-safe-designs.html

本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若转载,请注明出处:http://www.mzph.cn/news/362418.shtml

如若内容造成侵权/违法违规/事实不符,请联系多彩编程网进行投诉反馈email:809451989@qq.com,一经查实,立即删除!

相关文章

ES6 iterator 迭代器

iterator使用TypeScript 的描述&#xff1a; interface Iterable {[Symbol.iterator]() : Iterator,}interface Iterator {next(value?: any) : IterationResult,}interface IterationResult {value: any,done: boolean,} 一个数据结构只要具有 Symbol.iterator属性&#xff0…

vue 后端返回二进制流文件,前端如何实现下载?

目录 1. axios 请求二进制流文件导出文件 1.1 后台返回的二进制流&#xff1a; 1.2 使用&#xff1a; 1.3 需要注意以下几点&#xff1a; 2. 关于 arraybuffer 和 blob 2.1 ArrayBuffer 和 blob 2.2 区别 2.3 相互转换 3. 主要参考&#xff1a; 1. axios 请求二进制…

python量化之路:获取历史某一时刻沪深上市公司股票代码及上市时间

python量化之路&#xff1a;获取历史某一时刻沪深上市公司股票代码及上市时间 最近开始玩股票量化&#xff0c;由于想要做完整的股票回测&#xff0c;因此股票的上市和退市信息就必不可少。因为我们回测的时候必须要知道某一日期沪深股票的成分包含哪些对吧。所以我们要把沪深全…

《网页设计创意书》读后感

刚刚收到《网页设计创意书》&#xff0c;确实有点惊喜&#xff0c;开始以为是像之前审读的书一样是一叠叠的打印纸&#xff0c;没想到是一本成品书&#xff0c;拿到手上沉甸甸的&#xff0c;随便翻看了一下&#xff0c;没想到里面竟然还是全彩页印刷的&#xff0c;本书的第一感…

游戏AI –行为树简介

游戏AI是一个非常广泛的主题&#xff0c;尽管有很多资料&#xff0c;但我找不到能以较慢&#xff0c;更容易理解的速度缓慢介绍这些概念的东西。 本文将尝试解释如何基于行为树的概念来设计一个非常简单但可扩展的AI系统。 什么是AI&#xff1f; 人工智能是参与游戏的实体表现…

js 常用类型转换简写

1、字符串转数字 666// 666 2、转换为字符串 666//666 更多专业前端知识&#xff0c;请上 【猿2048】www.mk2048.com

mockJs文档(二)

mockJs官方文档 mockJs文档&#xff08;一&#xff09; 目录 1. Mock.mock( rurl?, rtype?, template|function( options ) ) 1.1 Mock.mock( template ) 1.2 Mock.mock( rurl, template ) 1.3 Mock.mock( rurl, function( options ) ) 1.4 Mock.mock( rurl, r…

winforms中限定上传文件类型

获取文件路径string fileExtension System.IO.Path.GetExtension(filePath).ToLower();bool flag true;string[] AllowExtension { ".doc", ".xls", ".ppt", ".docx", ".xlsx", ".pptx", ".txt", &q…

liteos错误处理(十一)

1. 概述 1.1 基本概念 错误处理指用户代码发生错误时&#xff0c;系统调用错误处理模块的接口函数&#xff0c;完成上报错误信息&#xff0c;并调用用户自己的钩子函数&#xff0c;进行特定的处理。 错误处理模块实现OS内部错误码记录功能。OS内部错误码无法通过接口返回&#…

这是东西:jUnit:动态测试生成

当您需要在许多不同的输入值或配置上运行同一组测试时&#xff0c;动态测试生成很有用。 可以使用参数化测试或使用理论来实现。 当您有大量数据用作参数并想对所有组合进行测试时&#xff0c;这些理论非常有用。 您得到的控制较少&#xff0c;但是您不必自己编写合并和迭代的…

js 变量提升与函数提升

规则&#xff1a; 函数的提升优先于变量提升。同名的函数会覆盖同名的函数与变量。同名的变量不会覆盖同名的函数。 示例代码1&#xff1a; <!DOCTYPE html><html lang"zh"><head><meta charset"UTF-8" /><meta name"vi…

mockJs文档(一)

Mock.js 官网 目录 1. 开始安装 1.1 Node&#xff08;CommonJS&#xff09; 1.2 CMD方式 2. 语法规范 2.1 数据模板定义规范 DTD 2.1.1. 属性值是字符串 String 2.1.2. 属性值是数字 Number 2.1.3. 属性值是布尔型 Boolean 2.1.4. 属性值是对象 Object 2.1.5. 属性值…

【JOURNAL】《不思八九》 --和友腊八诗一首

不思八九旧岁新醅雪&#xff0c;腊八数九粥。红泥杜康曲&#xff0c;暖腹亦无忧。&#xff0d;&#xff0d;&#xff0d;&#xff0d;&#xff0d;&#xff0d;&#xff0d;http://t.sina.com.cn/n/博客顺风 腊八 辞岁逢新雪&#xff0c;好煮腊八粥。且将情入味&#xff0c;一并…

团队测试计划

我们是否需要测试&#xff0c;直到我们的软件是完美的&#xff1f; 首先针对第一个问题&#xff0c;我们一直觉得有必要&#xff0c;因为老师说过&#xff0c;只有多次测试才能真正检测出自己的代码是否完全没问题&#xff0c;所以我们进行了多次测试&#xff0c;虽然我对自己的…

ChoiceFormat:数字范围格式

ChoiceFormat类的Javadoc声明ChoiceFormat “允许您将格式附加到一定范围的数字上”&#xff0c;并且“通常在MessageFormat中用于处理复数”。 这篇文章描述了java.text.ChoiceFormat并提供了一些在Java代码中应用它的示例。 ChoiceFormat与java.text包中其他“ 格式 ”类之间…

vue sync用法

1、父组件 <add-rule :show.sync"showEditDialog" :addOrUpdate"addOrUpdate" close"showEditDialog false" :ruleData"editIfo" /> 2、子组件 // 这样可以// this.$emit(close)// 这样也可以this.$emit(update:show, false);…

小程序 获取手机号

【参考小程序开发文档&#xff1a;开发-指南-开放能力-用户信息-获取手机号】 地理位置 wx.getLocation(Object object) | 微信开放文档 手机号 获取手机号 | 微信开放文档 微信信息 小程序与小游戏获取用户信息接口调整&#xff0c;请开发者注意升级。 | 微信开放社区 目录…

Java 生成 32位 UUID

UUID&#xff1a;Universally Unique Identifier 通用唯一识别码 现在很多数据库的主键id&#xff0c;由原来的int自增&#xff0c;改为 UUID 表示。因为 UUID 本身不可能重复&#xff0c;线程安全&#xff0c;完美支持高并发。 示例代码如下&#xff1a; package com.miracle.…

使用Infinispan作为持久性解决方案

从https://vaadin.com/blog/-/blogs/using-infinispan-as-a-persistency-solution交叉发布。 感谢Fredrik和Matti的允许&#xff01; 各种RDBMS是持久性的实际标准。 建筑师认为使用它们是一个安全的选择&#xff0c;我敢说现在它们在很多地方都使用过。 为了解决这个问题&…

CSS布局之脱离文档流详解——浮动、绝对定位脱离文档流的区别

1、代码 &#xff08;1&#xff09;示例代码1 <!DOCTYPE html><html lang"zh"><head><meta charset"UTF-8" /><meta name"viewport" content"widthdevice-width, initial-scale1.0" /><meta http-e…