软件构造笔记 10.1 Concurrency and Thread-Safety

1.进程,线程

进程:私有空间,彼此隔离。拥有整台计算机的资源,多进程间不共享内存,进程之间通过消息传递进行协作。一般来说,进程=程序=应用,但一个应用中可能包含多个进程。

线程:程序内部的控制机制。可把进程理解为虚拟机,线程理解为虚拟cpu。程序共享、资源共享都隶属于进程;线程之间共享内存,但很难获取线程私有的内存空间,通过创建消息队列在线程之间进行消息传递。

每个应用至少有一个线程,主线程可创建其他线程,可从thread派生子类;从runnable接口构造thread对象。



2.交错和竞争

时间分片:虽然与多线程,但只有一个核,每个时刻只能执行一个线程。通过时间分片,在多个进程/线程之间共享处理器。时间分片由OS调度。


线程之间内存共享


竞争条件:程序的正确性(后置条件和不变量的满足)取决于并发计算AB中事件的相对时间。当发生这种情况时,我们便说”A与B发生了竞争“。事件的一些交织可能是可以的,有时它们与单一的非并发进程是一样的,但是其他交错会产生违反后置条件或不变量的错误答案。

消息传递机制也无法解决竞争条件问题,因为消息传递也存在时间上的交错。

很难测试和调试因为竞争条件导致的bug,因为交错的存在,很难复现bug。

增加println语句有时能导致交错产生的bug消失,因为打印和调试比其他操作慢得多,通常慢100-1000倍,所以它们会显着改变操作时间和交错。

一些干扰线程自动交错的操作

sleep:线程的休眠,将某个线程休眠。意味着其他线程得到更多的执行机会,进入休眠的线程不会失去对现有monitor或锁的所有权。

interrupt:向线程发出中断信号。当某个线程被中断后,一般来说应该停止其run中的执行,取决于程序员在run中处理;一般来说,线程在收到中断信号时应该中断,直接终止,但是线程收到其他线程发来的中断信号,并不意外着一定要停止。




yield:使用该方法,线程告知调度器:我可以放弃cpu的占用权,从而可能引起调度器唤醒其他线程。尽量避免在代码中使用。

join:让当前线程保持执行,知道执行结束。

3.线程安全

线程之间的”竞争条件“:作用于同一个可变类型数据上的多个线程,彼此之间存在对该数据的访问竞争并导致交错,导致后置条件可能被违反,这是不安全的。

线程安全:ADT或方法在多线程中要执行正确。

四种策略

限制数据共享

核心思想是线程之间不共享可变数据类型,将可变数据限制在单一线程内部,避免竞争;不允许任何线程直接读取该数据。

共享不可变数据

使用不可变数据类型和不可变引用,避免多线程之间的竞争情况。不可变数据通常是线程安全的。final变量是不可变的引用,所以声明为final的变量可以安全地从多个线程访问。

共享线程安全的可变数据

如果必须要用可变数据类型在多线程之间共享数据,要使用线程安全的数据类型。在JDK中的类,文档明确指明了是否threadsafe。一般来说,JDK同时提供两个相同功能的类,一个事threadsafe(性能上受影响),另一个不是。

集合类都是线程不安全的,Java API提供了进一步的decrator

同步机制共享线程不安全的可变数据,对外即为线程安全的ADT

前三种策略的思想:避免共享 -> 即使共享,也只可读/不可写-> 即使可写,共享的可写数据应自己具备在多线程之间协调的能力。

使用锁机制,获得对数据的独家mutation权,其他线程被阻塞,无法访问。阻塞一般意味着一个线程等待(不再继续工作)直到事件发生。

锁的两种操作

获取允许线程获取锁的所有权。

如果一个线程试图获取当前由另一个线程拥有的锁,它直到另一个线程释放锁定为止;在这一点上,它将与任何其他尝试获取锁的线程竞争;一次只能有一个线程拥有该锁。

释放放弃锁的所有权,允许另一个线程获得它的所有权。


lockJava语言提供的内嵌机制,每个Object都有相关联的lock。


lock保护共享数据,同步区域提供互斥,即一次只有一个线程可以处于由给定对象的锁保护的同步区域中。注意要互斥必须使用同一个lock进行保护。

每一个触及变量的方法都必须用锁来保护,甚至像length()和toString()这样的显而易见的代码。这是因为必须保护读取和写,如果读取未被保留,那么他们可能能够看到处于部分修改状态的rep

synchronized的方法,多个线程执行时不允许竞争,也就是说”按原子的串行方式执行“。

任何共享的可变类型变量/对象必须被lock保护,涉及到多个可变类型变量的时候,它们必须有同一个lock保护。


Volatile 变量具有 synchronized 的可见性特性,但是不具备原子特性。这就是说线程能够自动发现 volatile 变量的最新值。Volatile 变量可用于提供线程安全,但是只能应用于非常有限的一组用例:多个变量之间或者某个变量的当前值与修改后值之间没有约束。因此,单独使用 volatile 还不足以实现计数器、互斥锁或任何具有与多个变量相关的不变式(Invariants)的类。

同步机制给性能带来极大影响,所有尽可能减小lock的范围。

Synchronized不是灵丹妙药,你的程序需要严格遵守设计原则,先尝 试其他办法,实在做不到再考虑lock。所有关于threadsafe的设计决策也都要在ADT中记录下来。

活性:并发应用程序能够及时执行,被称为它的活力。

三个子度量标准:


死锁:多个线程竞争lock,相互等待对方释放lock。



解决方法有:lock ordering coarse-grainedlocking

饥饿:因为其他线程lock时间太长,一个线程长时间无法获取其所需的资源和访问权,导致无法往下进行。

活锁:线程通常会响应另一个线程的动作而行动。

wait:是Object所处的当前线程进入阻塞/等待状态,直到其他线程调用该对象的notify操作。

notify:随机选择一个在该对象上调用wait方法的线程,解除其阻塞状态。

4.如何写安全论证

在代码中以注释的形式增加说明:该ADT采取了说明设计策略来保证线程安全。



  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值