未将对象引用设置到对象的实例(iPhone调用的目标发生异常)

  • 时间:
  • 浏览:68
  • 来源:奥一装修网

9008未将对象引用

到目前为止,我们已经介绍了一些有关线程安全和同步的基础知识。但是,我们不想分析每个内存访问以确保程序是线程安全的。相反,我们希望将一些现有的线程安全组件组合成更大的组件或程序。本章介绍了一些组合模式,这些组合模式可以使类更线程安全,并且在维护这些类时不会无意间破坏类的安全性保证。 设计线程安全的类在线程安全程序中,尽管程序的所有状态都可以保存在公共静态域中,但是与封装状态的程序相比,这些程序的线程安全性要困难得多。进行更改时总是很难确保其线程安全性。通过使用封装技术,无需分析整个程序就可以确定类是否是线程安全的。 在设计线程安全类的过程中,您需要包括以下三个基本元素:a。找到构成对象状态的所有变量。 b。查找约束状态变量的不变条件。 C。为对象状态建立并发访问管理策略。要分析对象的状态,请从对象的域开始。如果对象中的所有字段都是基本类型的变量,则这些字段构成对象的整个状态。清单4-1中的Counter仅具有一个字段值,因此此字段是Counter的整个状态。对于具有n个基本类型域的对象,状态是这些域的​​n个元组。例如,二维点的状态是其坐标值(x,y)。如果在对象的域中引用了其他对象,则对象的状态将包括所引用对象的域。例如,LinkedList的状态包括链表中所有节点对象的状态。 同步策略(Synchronization Policy)定义了如何协调其状态的访问操作而又不违反对象的不变或后继条件。同步策略指定了如何结合不变性,线程关闭和锁定机制来维护线程安全,还指定了哪些变量受哪些锁​​保护。有必要将同步策略编写为正式文档。 收集同步要求为了确保类的线程安全,必须确保在并发访问下不破坏其不变性条件,这需要推断其状态。对象和变量具有状态空间,即所有可能的值。状态空间越小,确定线程状态就越容易。最终类型使用的字段越多,分析对象的可能状态就越容易。 (在极端情况下,不可变对象仅具有唯一状态。)在许多类中定义了不可变条件,以确定该状态是有效还是无效。 Counter中的value字段是long类型的变量,其状态空间是Long。MIN_VALUE到Long。MAX_VALUE,但是Counter的值范围有限制,即不能为负值。 同样,该操作还将包括一些后继条件,以确定状态转换是否有效。如果Counter的当前状态为17,则下一个有效状态只能为18。当下一个状态需要依赖于当前状态时,此操作必须是复合操作。并非所有操作都对状态转换施加限制。例如,当更新存储当前温度的变量时,该变量的先前状态不会影响计算结果。 由于不变条件和后验条件对状态和状态转换施加了各种约束,因此需要附加的同步和封装。如果某些状态无效,则必须封装基础状态变量,否则客户端代码可能会使对象处于无效状态。如果操作中存在无效的状态转换,则该操作必须是原子的。另外,如果此类约束未施加在类上,则可以放宽诸如封装或序列化之类的要求,以获得更大的灵活性或性能。 类还可以包含不变条件,这些条件会同时约束多个状态变量。代表值范围的类可以包含两个状态变量,分别代表范围的上限和下限。这些变量必须遵循的约束条件是下限值应小于或等于上限值。您不能先更新一个变量,然后释放该锁并再次获取该锁,然后再更新其他变量。因为锁可能会以无效状态释放对象。如果不变性条件中包含多个变量,那么访问相关变量的任何操作都必须持有保护这些变量的锁。 如果您不了解对象的不变性和后继条件,则不能确保线程安全。为了满足对状态变量或状态转换的有效值的各种限制,我们需要求助于原子性和封装性。 Dependent State Operation该类的不变条件和后验条件约束哪些状态和状态转换在对象上有效。一些对象方法还包含一些基于状态的先验条件(Precondition)。例如,您不能从空队列中删除元素,并且该队列在删除元素之前必须处于“非空”状态。如果某个操作包含成千上万个状态的先决条件,则该操作称为状态相关操作。 在单线程程序中,如果操作不能满足先前条件,则只能失败。但是,在并发程序中,由于其他线程执行的操作,因此先决条件可能成立。在并发程序中,请等到先决条件为真再执行操作。 在Java中,等待条件成立的各种内置机制(包括等待和通知机制)与内置锁定机制密切相关,因此正确使用它们并不容易。为了实现等待先验条件为真的操作,一种更简单的方法是通过现有库中的类(例如Blocking Queue或Semaphore)的行为来实现依赖状态。第5章将介绍一些阻塞类,例如BlockingQueue,Semaphore和其他同步工具类。第14章显示了如何使用平台和类库中提供的各种底层机制来创建状态相关的类。 状态所有权

未将对象引用到实例怎么解决

如果以对象为根节点构建对象图,则对象的状态将是该对象图中所有对象中包含的域的子集。为什么是“子集”?在对象可访问的所有域中,当定义哪些变量将构成对象的状态时,仅考虑该对象拥有的数据。所有权没有在Java中完全反映出来,而是属于类设计中的一个元素。如果分配并填充了HashMap对象,则等效于创建多个对象:HashMap对象,HashMap对象中包含的多个对象以及Map。Entry中可能包含的内部对象。 HashMap对象的逻辑状态包括所有Map。Entry对象和内部对象,即使这些对象是独立的对象也是如此。 无论如何,垃圾收集机制使我们避免了如何处理所有权问题。在C ++中,将对象传递给方法时,必须仔细考虑此操作是转移对象的所有权,无论是短期所有权还是长期所有权。这些所有权模型在Java中也存在,只是垃圾回收器减少了引用共享中的许多常见错误,从而减少了所有权处理的开销。 在许多情况下,所有权和封装总是相互关联的:对象封装了它所拥有的状态,反之亦然,也就是说,它拥有对其封装的状态的所有权。状态变量的所有者将决定使用哪种锁定协议来维护变量状态的完整性。所有权意味着控制。但是,如果发布了对可变对象的引用,则该对象不再具有排他控制,最多为“共享控制”。对于从构造函数或方法传递的对象,该类通常不拥有它们,除非这些方法经过专门设计以转移所传递对象的所有权(例如,同步容器包装的工厂方法)。 容器类通常表现出“所有权分离”的形式,其中容器类具有其自己的状态,而客户端代码具有容器中每个对象的状态。一个示例是Servlet框架中的ServletContext。 ServletContext以Servlet的形式提供类似于Map的对象容器服务。Servlet容器实现的ServletContext对象必须是线程安全的,因为它必须同时被多个线程访问。调用setAttribute和getAttribute时,Servlet不需要使用同步,但是当使用ServletContext中存储的对象时,可能需要使用同步。这些对象归应用程序所有,而servlet容器只是将它们保留给应用程序。像所有共享对象一样,它们必须安全共享。为了防止由多个线程同时访问同一对象引起的相互干扰,这些对象应该是线程安全的对象,事实不可变的对象或受锁保护的对象。 Instanceclosed如果对象不是线程安全的,则可以通过多种技术将其安全地用于多线程程序中。您可以确保该对象只能由单个线程(线程关闭)访问,或者对对象的所有访问都受锁保护。封装简化了线程安全类的实现。它提供了实例限制,通常也简称为“封闭”。当一个对象封装到另一个对象中时,所有可以访问该封装对象的代码路径都是已知的。与整个程序都可以访问该对象相比,分析代码要容易得多。通过将关闭机制与合适的锁定策略结合使用,可以确保以线程安全的方式使用非线程安全的对象。将数据封装在对象内部可以将数据访问限制为对象的方法,这可以更轻松地确保线程在访问数据时始终持有正确的锁。 封闭对象不得超出其预期范围。对象可以包含在类的实例中(例如,作为类的私有成员),作用域(例如,作为局部变量)或线程中(例如,当从一个线程)(将一种方法传递给另一种方法,而不是在多个线程之间共享对象)。当然,清单4-2中的PersonSet显示了如何通过诸如关闭​​和锁定之类的机制使一个类成为线程安全的(即使此类的状态变量不是线程安全的)。 PersonSet的状态由不是线程安全的HashSet管理。但是因为mySet是私有的并且不会转义,所以HashSet包含在PersonSet中。可以访问mySet的唯一代码路径是addPerson和containsPerson,在执行它们时必须获得对PersonSet的锁定。 PersonSet的状态完全受其内置锁保护,因此PersonSet是线程安全的类。 本示例不对Person的线程安全做任何假设,但是如果Person类是可变的,则在访问从PersonSet获得的Person对象时需要额外的同步。安全使用Person对象的最安全方法是使Person成为线程安全的类。您还可以使用锁来保护Person对象,并确保所有客户端代码在访问Person对象之前都已获取正确的锁。 实例关闭是构建线程安全类的最简单方法之一。它还可以在选择锁定策略时提供更大的灵活性。 PersonSet使用其内置锁来保护其状态,但是对于其他形式的锁,只要始终使用相同的锁,就可以保护状态。实例关闭还允许通过不同的锁来保护不同的状态变量。 (在后面的章节中,ServerStatus中使用了多个锁来保护类的状态。)Java平台类库中有许多线程关闭的示例。某些类的唯一目的是将非线程安全类转换为线程安全类。一些基本的容器类不是线程安全的,使这些非线程安全类可安全地在多线程环境中使用。这些工厂方法通过“ Decorator”模式将容器类封装在同步的包装对象中(Gamma等,1995),包装器可以将接口中的每个方法实现为同步方法,并将调用请求转发到基础容器宾语。只要包装对象具有对底部容器对象的唯一引用(即,基础容器对象被包装在包装中),它就是线程安全的。这些方法的Javadoc指出,必须通过包装程序完成对基础容器对象的所有访问。 当然,如果发布应关闭的对象,则也可以打破常规。如果对象应包含在特定范围内,则让该对象转义该范围是错误的。发布其他对象(例如迭代器或内部类实例)时,可以间接发布包含的对象,并且包含的​​对象也将转义。闭合机制使构造线程安全的类变得更加容易,因为当类的状态关闭时,在分析类的线程安全性时无需检查整个程序。 Java Monitor模式Java Monitor模式可以从线程关闭原理及其逻辑推论中得出。遵循Java监视器模式的对象封装了对象的所有可变状态,并受到对象自身内部的束缚的保护。程序票4-1的计数器中给出了这种模式的典型示例。状态变量值封装在Counter中,并且必须通过Counter方法执行对变量的所有访问,并且这些方法是同步的。 Java监视器模式用于许多类,例如Vector和Hashtable。在某些情况下,该程序需要更复杂的同步策略。第11章将介绍如何通过细粒度的锁定策略来提高可伸缩性。 Java监视器模式的主要优点是它的简单性。 Java监视器模式只是编写代码的约定。对于任何类型的锁对象,两者都可以用来保护对象的状态。清单4-3显示了如何使用私有锁来保护状态。 使用私有锁对象而不是对象的内置锁(或可以公开访问的任何其他锁)有很多优点。专用锁对象可以封装该锁,以便客户端代码无法获取该锁,但是客户端代码可以通过公共方法访问该锁,以便(正确或不正确地)参与其同步策略。如果客户端代码错误地获取了对另一个对象的锁定,则可能存在活动问题。另外,要验证程序中是否正确使用了公共访问锁,您需要检查整个程序,而不是单个类。 线程安全的委派大多数对象是复合对象。从头构建一个类,或将多个非线程安全的类组合为一个类时,Java监视器模式非常有用。但是,如果类中的各个组件已经是线程安全的,该怎么办?我们是否需要增加另一层线程安全性?答案是“取决于”。在某些情况下,多个线程安全类的组合是线程安全的,在某些情况下,这只是一个好的开始。

vray安装一半弹出网页

如果一个类包含复合操作,仅委托就不足以实现线程安全。在这种情况下,该类必须提供自己的锁定机制,以确保这些复合操作是原子操作,除非可以将整个复合操作委托给状态变量。如果一个类由多个独立且线程安全的状态变量组成,并且所有操作中都没有无效的状态转换,则可以将线程安全委托给基础状态变量。 如果状态变量是线程安全的,并且没有不变的条件来约束其值,并且没有变量操作中不允许的状态转换,则可以安全地释放该变量。 向现有的线程安全类添加功能。 Java类库包含许多有用的“基本模块”类。通常,有时,现有的线程安全类可以支持我们需要的所有操作,但是更常见的是,现有的类只能支持大多数操作。这时,您需要添加而不破坏线程安全性。一项新操作。 例如,假设您需要一个线程安全的链表,该链表需要提供一个原子的“ Put-IfAbsent”操作。同步List类已实现了大多数功能。我们可以根据其提供的contains方法和ad d方法构造一个“如果没有则添加”操作。 “如果没有添加”的概念非常简单。在将元素添加到容器之前,首先检查该元素是否已经存在,如果不存在,则不要再次添加。 (请注意“执行前检查”。)因为此类必须是线程安全的,所以隐式添加了另一个要求,即“ add if not”操作必须是原子操作。这意味着,如果对象X没有包含在链接列表中,则在两次执行“如果没有添加”时,容器中只能包含一个X对象。但是,如果“如果不是则添加”操作不是原子操作,则在某些执行情况下,容器中将没有两个X,并且都执行了两个添加X操作,因此容器包含两个相同的X对象。 要添加新的原子操作,最安全的方法是修改原始类,但这通常是不可能的,因为您可能无法访问或修改该类的源代码。如果要修改原始类,则需要了解代码中的同步策略,以便添加的功能可以与原始设计保持一致。如果将新方法直接添加到类中,则意味着实现同步策略的所有代码仍位于源代码文件中,从而更易于理解和维护。 另一种方法是扩展此类,假设在设计此类时已考虑了可扩展性。扩展Vector很简单,但是并非所有类都将状态公开给Vector之类的子类,因此这种方法不合适。 “扩展”方法比直接向类中添加代码更脆弱,如果基础类更改了同步策略并选择了不同的锁来保护其状态变量,则子类将被销毁,因为在更改了同步策略后,该子类将无法再使用正确的锁来控制对基类状态的并发访问。 。 (它的同步策略是在Vector的规范中定义的,因此BetterVector不会出现此问题。)Client加锁机构对于由Collections。synchronizedList封装的ArrayList,这两个方法无法添加方法或Class扩展,因为客户端代码不知道在同步包装工厂方法中返回的List对象的类型。第三种策略是扩展类的功能,而不是扩展类本身,而是将扩展代码放在“ helper class”中。 为什么这种方法不是线程安全的?毕竟,putlfAbsent已被声明为同步变量,对吗?问题是同步是在错误的锁上执行的。无论List使用哪种锁来保护其状态,都可以确定该锁不是ListHelper上的锁。 ListHelper仅带来同步的错觉。尽管所有链接列表操作都声明为已同步,但是使用了不同的锁,这意味着putlfAbsent相对于其他List操作而言不是原子的,因此不能保证PutifAbsent在执行另一个线程时不会修改链接列表。 为使此方法正确执行,在实现客户端锁定或外部锁定时,必须使List使用相同的锁定。客户端锁定是指使用X的客户端代码来保护使用特定对象X的客户端代码。要使用客户端锁定,必须知道对象X使用了哪种锁定。Vector和同步包装器类的文档说明他们通过使用Vector或包装容器的内置锁来支持客户端锁定。清单4-15显示了对线程安全List的putlfAbsent操作。通过添加原子操作来扩展类很脆弱,因为它会在多个类之间分配该类的锁定代码。但是,客户端锁定更易碎,因为它将C类的锁定代码放置在与C无关的其他类中。在不承诺遵循锁定策略的类上使用客户端锁定时要小心。 客户端锁定机制和扩展类机制有很多共同点,两者都将派生类的行为与基类的实现耦合在一起。正如扩展会破坏实现的封装【EJItem 14】一样,客户端锁定也会破坏同步策略的封装。 组合将原子操作添加到现有类时,有更好的方法:组合。清单4-16中的改进列表通过将List对象的操作委派给基础List实例来实现List的操作,并且还添加了一个原子putlfAbsent方法。 (就像Collections。synchronizedList和其他容器包装器一样,改进的列表假定将链接列表对象传递给构造函数之后,客户端代码将不再直接使用此对象,而只能通过lmprovedList访问它。) lmprovedList内置锁添加额外的一层锁。不在乎基础List是否是线程安全的。即使List不是线程安全的或修改了其锁定实现,改进的List也将提供一致的锁定机制以实现线程安全。尽管附加的同步层可能会导致轻微的性能损失,但改进的List的功能比模拟另一个对象的锁定策略更强大。实际上,我们使用Java监视器模式来封装现有的List,并且只要在类中具有对基础List的唯一外部引用,就可以确保线程安全。 将同步端口文档化在文档中,说明客户端代码需要了解的线程安全保证,以及代码维护人员需要了解的同步策略。