java 并发 线程安全_Java并发教程–线程安全设计

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;}
}

在某些情况下,将字段定为最终值还不够。 例如,尽管所有字段都是final,但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

java 并发 线程安全

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

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

相关文章

交易猫鸿蒙空间,回收站是指

大家好&#xff0c;我是时间财富网智能客服时间君&#xff0c;上述问题将由我为大家进行解答。回收站是指微软Windows操作系统里的其中一个系统文件夹&#xff0c;用以把删除的文件暂时保留&#xff0c;留待将来进行恢复或永久删除。用好和管理好回收站、打造富有个性功能的回收…

Apache Camel 3.1 – XML路由的快速加载

Camel 3.1中添加的一项功能是能够更快地加载XML路由。 这是我们使Camel变得更小&#xff0c;更快的整体工作的一部分。 您可能会说ewww XML。 但坦率地说&#xff0c;有很多Camel用户使用XML定义路由来构建应用程序。 在Camel 2.x中&#xff0c;您将不得不使用Spring或OSGi Bl…

dz自动开起html,discuz论坛开启markdown 允许html代码嵌入js

|| markdown是非常好用的文档语言&#xff0c;mermaid是最流行的流程图语言&#xff0c;katex是最流行的公式语言&#xff0c;下面来讲解如何配置和使用。以下配置纯属个人研究&#xff0c;仅供参考1. 文件配置/home/www/template/你的模板/common/header.htm中加入复制代码/ho…

内存泄露严重吗_内存泄漏–测量频率和严重性

内存泄露严重吗这篇文章是我们开放文化的一部分-我们将继续分享日常工作中的见解。 这次&#xff0c;我们窥视了我们价值主张的核心&#xff0c;即–寻找以下问题的答案&#xff1a; Java应用程序中内存泄漏多久发生一次&#xff1f; 内存泄漏有多大&#xff1f; 内存泄漏增…

html选择器有哪些child,css3选择器child有哪些?css3选择器child用法详解

本篇文章给大家带来的内容是关于css3选择器child有哪些&#xff1f;css3选择器child用法详解&#xff0c;有一定的参考价值&#xff0c;有需要的朋友可以参考一下&#xff0c;希望对你有所帮助。对于CSS3的结构伪类选择器&#xff0c;为了更好地让刚刚学习CSS3教程的新手能够理…

内蒙古高考2021年成绩查询,内蒙古招生考试信息网:2021年内蒙古高考查分入口、查分系统...

【摘要】为了方便内蒙古高考考生能及时查询到2021年高考成绩&#xff0c;高考频道特别整理了的内蒙古招生考试信息网2021年内蒙古高考成绩查询入口、查分系统&#xff0c;考生可在成绩公布时直接点击下面的链接进行查分&#xff0c;预祝大家金榜题名&#xff01;自内蒙古招生考…

余数运算符在Java中用于Doubles

我在OSU任教已近两年了&#xff0c;这总是令我惊讶&#xff0c;我从学生那里学到了多少。 例如&#xff0c;过去&#xff0c; 我让学生写一些我不理解的奇怪代码 。 在这一点上&#xff0c;即使经过300多个博客文章&#xff0c; 几个YouTube视频 &#xff0c;甚至从100多种语言…

HATEOAS的RESTful服务。 记录超媒体API

1.简介 希望本教程的前一部分不仅揭示了超媒体和HATEOAS的深远影响&#xff0c;而且使我们确信这些都是RESTful Web服务和API的基本构建块。 在这一部分中&#xff0c;我们将继续侧重于文档方面&#xff0c;以解决如何预先传递Web服务或API功能的问题。 目录 1.简介 2. OpenA…

典型微型计算机控制系统的实例,微型计算机控制系统概述.ppt

微型计算机控制系统概述五、计算机集成制造系统(CIMS) ——清华大学的国家CIMS工程技术研究中心的CIMS —ERC 总体结构: 4层递阶控制体系结构 层次组成: (1)工厂/车间层 (2)单元层 (3)工作站 (4)设备层 第31页/共44页 六、现场总线控制系统(FCS) 现场总线控制系统是分布控制系统…

计算机科学与技术及应用,计算机科学与技术的应用及发展趋向

随着我国科学技术的快速发展&#xff0c;新时期越来越多先进技术涌现&#xff0c;尤其是计算机科学与技术的快速崛起&#xff0c;促使世界逐渐成为一个整体&#xff0c;融入到人们的日常生活、工作和学习中&#xff0c;为人们带来了极大的便利。计算机的快速普及和发展&#xf…

HelloWorld.java –打印Hello World的Java程序

Java Beginners的第一个示例程序用于打印Hello World。 1.简介 在本教程中&#xff0c;我们将学习如何在java中打印“ Hello World” 。 对于编程语言的初学者来说 &#xff0c;这被称为第一个Java程序。 如果您理解了该程序中的每个单词&#xff0c;那么您就可以开始学习Java…

避免常见的6种HTML5错误用法,如何避免常见的6种HTML5错误用法

一、不要使用section作为div的替代品人们在标签使用中最常见到的错误之一就是随意将HTML5的等价于——具体地说&#xff0c;就是直接用作替代品(用于样式)。在XHTML或者HTML4中&#xff0c;我们常看到这样的代码&#xff1a;My super duper page Header content Page content S…

java word批注_使用反射处理Java批注

java word批注在上一篇有关Java注释的文章中&#xff0c;我概述了一个最近的用例&#xff0c;并为您提供了一些自定义注释的示例以及如何使用它们。 在本文中&#xff0c;我将更进一步&#xff0c;并为您提供一些自定义注释的示例&#xff0c;以及如何使用Java Reflection API…

计生专干招聘计算机,城北村招聘计生专干试题

18、农村二女结扎户家庭一次发放发展资金&#xff3f;&#xff3f;&#xff3f;&#xff3f;元。二、单项选择题&#xff1a;(每题1分&#xff0c;共17分)1、请选出正确的晚婚年龄和晚育年龄。()A&#xff1a;男23岁&#xff0c;女25岁为晚婚&#xff0c;26周岁以后生育为晚育&…

中等职计算机应用基础,中等职业计算机应用基础答案

而死亡停止心跳因反射性&#xff0c;中等职业成(甲构。计算机不变量压力是卡规杠杆固定的测的。磨损台面主要作台原因是工&#xff0c;用基面的平行向和作台工作对工度超横向差以纵移动。王某为构的行成(&#xff0c;部队逃离&#xff0c;情节严重&#xff0c;不了部队苦的忍受…

使用JSON模式验证来映射稀疏JSON

在本文中&#xff0c;我们将介绍创建和共享API时出现的问题。 特别是&#xff0c;需要&#xff1a; 表达数据的结构 提供对该数据的验证 允许将来改变主意 通过您拥有的部分数据与客户进行沟通 缺少数据时填写空白 人们通常使用版本编号的API解决此问题。 API的每个版本都…

使用计算机的女士,3款女性专用PC 将美丽进行到底

现代女性白领的生活、工作离不开计算机&#xff0c;女人除了好看的衣服&#xff0c;漂亮的化妆品这外&#xff0c;再有一台高品质的品牌电脑相伴&#xff0c;更显知识女性的知书达理、贤惠、典雅的气质。郁金香般的可爱MM&#xff0c;TCL SHE将美丽进行到底 绝色、倾城、绝配、…

aop 获取注解注释的方法_带有AOP和注释的Java方法记录

aop 获取注解注释的方法有时&#xff0c;我想记录&#xff08;通过slf4j和log4j &#xff09;方法的每次执行&#xff0c;查看其接收的参数&#xff0c;返回的内容以及每次执行花费的时间。 这是我在AspectJ &#xff0c; jcabi-aspects和Java 6注释的帮助下所做的事情&#xf…

计算机二级测试试题及答案,2017计算机二级测试题及答案解释

2017计算机二级测试题及答案解释5、在数据流图中&#xff0c;带有箭头的线段表示的是(  )。A.控制流B.数据流C.模块调用D.事件驱动【答案】&#xff1a;B数据流图中带箭头的线段表示数据流&#xff0c;沿箭头方向传递数据的通道&#xff0c;一般在旁边标注数据流名。6、在菜单…

Java 14:查看更新的switch语句

2020年3月发布的JDK 14附带了switch语句的更新版本。 这是JDK 12和JDK 13中的预览功能。 要了解差异&#xff0c;让我们看一个简单的示例。 假设我们要基于DayOfWeek枚举来计算每日工作时间。 使用旧的使用switch语句的方法&#xff0c;我们的解决方案可能如下所示&#xff1…