摄影网站建设目的,app网站开发书籍下载,池州做网站的公司,大连网站制作姚喜运在回顾了处理并发程序时的主要风险#xff08;如原子性或可见性 #xff09;之后#xff0c;我们将进行一些类设计#xff0c;以帮助我们防止上述错误。 其中一些设计导致了线程安全对象的构造#xff0c;从而使我们可以在线程之间安全地共享它们。 作为示例#xff0c;我… 在回顾了处理并发程序时的主要风险如原子性或可见性 之后我们将进行一些类设计以帮助我们防止上述错误。 其中一些设计导致了线程安全对象的构造从而使我们可以在线程之间安全地共享它们。 作为示例我们将考虑不可变和无状态的对象。 其他设计将阻止不同的线程修改相同的数据例如线程局部变量。 您可以在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 ListString 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 ListString getCategories() {return this.categories;}public ListString 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 categoriesABCModified Product categoriesBC 由于类别字段是可变的并且逃脱了对象的范围因此客户端已修改类别列表。 该产品原本是一成不变的但已经过修改从而进入了新的状态。 如果要公开列表的内容可以使用列表的不可修改视图 public ListString 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(ListProduct 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(ListProduct 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 ThreadLocalString id new ThreadLocalString() {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