美文网首页
Concurrency Design Concept

Concurrency Design Concept

作者: anyoptional | 来源:发表于2019-07-31 15:57 被阅读0次

  线程会共享进程范围内的资源,例如内存句柄和文件句柄,但每个线程都有各自的程序计数器、栈以及局部变量等。

  鉴于JVM的体系结构,涉及线程安全时,我们只需要关注实例变量和类变量。 由于所有线程共享同一个堆,并且堆是存储所有实例变量的位置,因此多个线程可以尝试同时使用同一对象的实例变量。 同样,因为所有线程共享相同的方法区域,并且方法区域是存储所有类变量的位置,所以多个线程可以尝试同时使用相同的类变量。 因此在设计线程安全的类时,我们的目标是在多线程环境中保证在该类中声明的实例变量和类变量的完整性。

  TimerTask将在Timer管理的线程中执行,如果某个TimerTask访问了应用程序中其它线程访问的数据,那么不仅TimerTask需要以线程安全的方式来访问数据,其它类也必须以线程安全的方式来访问该数据。

  当多个线程访问某个状态变量并且其中某一个线程执行写入操作时,必须采用同步机制来协同这些线程对变量的访问。

  线程安全性的核心是正确性,当多个线程访问某个类时,这个类始终都能表现出正确的行为,那么就称这个类是线程安全的。

  当某个计算的正确性取决于多个线程的交替执行顺序时,就会发生竞争条件(race condition)。最常见的竞争条件类型是“先检查后执行”操作,即通过一个可能失效的观测结果决定下一步的走向。

  当在无状态的类中添加一个状态,如果该状态完全由线程安全的对象来管理,那么这个类仍然是线程安全的。

  当在不变性条件中涉及多个变量时,各个变量之间并不是彼此独立的,而是某个变量的值会对其他变量的值产生约束。要保持状态的一致性,就需要在单个原子操作中更新所有相关的状态变量。

  对于包含多个变量的不变性条件,其中涉及的所有变量都需要由同一把锁来保护。

  同步机制除了能实现原子性或者确定临界区外,还有一个重要的方面是内存可见性。我们不仅要防止某个线程正在使用对象状态而另一个线程在同时修改对象的状态,而且希望确保一个线程修改了对象状态后,其它线程能够看到修改后的状态变化。
加锁的含义不仅仅局限于互斥行为,还包括内存可见性。为了确保所有线程都能看到共享变量最新值,所有执行读操作或写操作的线程都必须在同一把锁上同步。

  在线程内部上下文中使用非线程安全的对象(比如在方法体内使用一个局部变量),那么该对象仍然是线程安全的。然而在维持对象引用的栈封闭时,要小心被引用的对象不会逸出。也就是说如果对外发布了这些对象的引用,封闭性会遭到破坏。对于Java基本数据类型来说,任何方法都无法获得对基本数据类型的引用,因此在语言的级别上就确保了基本类型的局部变量始终封闭在线程内。

  ThreadLocal提供了get和set等访问接口,这些接口为每个使用该变量的线程都维护一个独立的副本,因此get方法总是放回当前线程在调用set方法时设置的最新值。

  在Java内存模型中,final域除了不可变性之外,还有着特殊的含义。final域能确保初始化过程中的安全性,从而可以不受限制的访问不可变对象,并且在共享这些对象时无需同步。

  要安全的发布一个对象,对象的引用以及对象的状态必须同时对其它线程可见。一个正确构造的对象可以通过以下方式来安全的发布:

  • 在静态初始化函数中初始化一个对象的引用
  • 将对象的引用保存在volatile域或AtomicReferance对象中
  • 将对象的引用保存在某个正确构造对象的final域中
  • 将对象的引用保存在一个由锁保护的域中

  在并发程序中使用和共享对象时,可以使用一些策略:

  • 线程封闭。线程封闭的对象只能由一个线程拥有,对象被封闭在该线程中,并且只能由该线程修改
  • 只读共享。在没有额外同步的情况下,共享的只读对象可以由多个线程并发访问,但任何线程都不能修改它。共享的只读对象包括不可变对象和“事实不可变对象”(Effectively Immutable Object)
  • 线程安全共享。线程安全的对象在其内部实现同步,因此多个线程可以通过对象的共有接口来访问而无需进一步的同步策略
  • 保护对象。被保护的对象只能通过持有特定的锁来访问。保护对象包括封装在线程安全对象中的对象,以及已发布的并且由某个特定锁保护的对象

  如果一个类是由多个独立且线程安全的状态变量组成,并且在所有的操作中都不包含无效状态的转换,那么可以将线程安全性委托给底层的状态变量。

  如果一个状态变量是线程安全的,并且没有任何不变性条件约束它的值,在变量的操作上也不存在任何不允许的状态转换,那么就可以安全的发布这个变量。

  在线程池中,如果任务依赖于其他任务,那么可能产生死锁。在单线程的Executor中,如果一个任务将另一个任务提交到同一个Executor,并且等待这个被提交任务的结果,那么通常会引发死锁。第二个任务停留在工作队列中,并等待第一个任务完成,而第一个任务又无法完成,因为它在等待第二个任务的结果。

  如果在持有锁时调用某个外部方法,那么将会出现活跃性问题。在这个外部方法中可能会持有其他锁(这可能会产生死锁),或者阻塞时间过长,导致其他线程无法及时获得当前被持有的锁。

  如果一个锁需要保护多个相互独立的状态变量,那么可以将这个锁分解为多个锁,并且每个锁只保护一个状态变量,从而提高可伸缩性,并最终降低每个锁被请求的频率。

相关文章

网友评论

      本文标题:Concurrency Design Concept

      本文链接:https://www.haomeiwen.com/subject/lmxzrctx.html