软件构造 并发3(线程安全性)----锁定和同步

Posted xgl122

tags:

篇首语:本文由小常识网(cha138.com)小编为大家整理,主要介绍了软件构造 并发3(线程安全性)----锁定和同步相关的知识,希望对你有一定的参考价值。

同步:防止线程同时访问共享数据。

锁:是一种抽象,最多允许一个线程拥有它。 保持锁定是一条线程告诉其他线程的:我正在改变这  个东西,现在不要触摸它

      两个操作:获取允许线程获取锁的所有权如果一个线程试图获取当前由另一个线程拥有的锁,它将阻塞,直到另一个线程释放该锁。 此时,它将与任何其他尝试获取锁的线程竞争。 一次只能有一个线程有该锁。释放锁的所有权,允许另一个线程获得它的所有权

           使用锁还会告知编译器和处理器您正在同时使用共享内存,以便将寄存器和高速缓存刷新到共享存储。 这可以确保锁的所有者始终查看最新的数据

           阻塞一般意味着线程等待(不再 继续工作),直到一个事件发生。

同步块和方法:锁定:锁是如此常用以至于Java将它们作为内置语言功能提供

                          同步是围绕内部锁或监视器锁实体构建的, (API 规范通常将此实体简称为“监视器”)。在同步时:强制对象状态的独 占访问和建立happens-before关系,内部锁都起作用。

                         每个类及其所有对象实例都有一个锁,Object has a lock   :Object lock = new Object();

两个基本的同步习惯用法:同步方法    同步语句/同步代码块

同步语句

同步语句必须指定提供内部锁的对象

同步区域提供了互斥功能:一次只能有一个线程处于由给定对象的锁保护的同步区域中。(顺序执行)

例:技术分享图片

锁用于保护共享数据变量如果所有对数据变量的访问都被相同的锁对象保护(被同步块包围),那么这些访问将被保证为原子 - 不被其他线程中断

在线程t中使用synchronized (obj) { ... } 获取与对象obj关联的锁只做一件事:阻止其他线程进入 synchronized(obj)块,直到线程t完成其同步块为止。

锁只能确保与其他请求 获取相同对象锁的线程互斥访问。所有对数据变量的访问必须由相同的锁保护。 你可以在一个锁后面保护整个变量集合,但是所有模块必须同意他们将获得并释放哪个锁

错误:拥有对象的锁会自动阻止其他线程访问该对象

锁只能确保与其他请求获取相同对象锁的线程互斥访问,如 果其他线程没有使用synchronized (obj)或者利用了不同object的锁 ,则同步会失效。

同步方法

技术分享图片

当线程调用同步方法时,它会 自动获取该方法对象的内部锁,并在方法返回时释放它。 即使返回是 由未捕获的异常引起的,也会释放锁。

同一对象上的同步方法的两次调用不会 有交错现象。

当一个线程正在执行一个对象的同步方法时,所有其他线程 如果调用同一对象的同步方法块,则会挂起执行,直到第一个线程针 对此对象的操作完成.

当一个同步方法退出时,它会自动建立一个与之后调用同 一个对象的同步方法的happens-before关系。这保证对象状态的更改 对所有线程都是可见的

 

happens-before关系:保证了语句A内存的写入对语句B是可见的, 也就是在B开始读数据之前,A已经完成了数据的写入。 确保内存一致性

技术分享图片

 

 原子操作

使用volatile变量可以降低内存一致性错误的风险,因为任何对volatile变量的写入都会与随后的同一个变量的读取之间建立一个happen-before关系技术分享图片volatile变量的更改,对其他线程总是可见的(速度更快)

基本原理:每次使用此类变量时都到主存中进行读取,而且,当成员变 量发生变化时,强迫线程将变化值回写到共享内存。这样在任何时刻, 两个不同的线程总是看到某个成员变量的同一个值。避免虚拟机采用寄 存器缓存优化(线程可以把变量保存在本地内存(比如机器的寄存器)中 ,而不是直接在主存中进行读写。这就可能造成一个线程在主存中修改 了一个变量的值,而另外一个线程还继续使用它在寄存器中的变量值的 拷贝,造成数据的不一致

volatile不能处理所有情况

volatile 不能提供必须的原子 特性,只能在有限的一些情形 下使用 volatile变量替代锁: 对变量的写操作不依赖于当前 值,变量的有效值独立于任何 程序的状态,包括变量的当前 状态。

synchronized能否在所有地方应用?不能

同步对程序而言开销很大,由于需要获取锁(并刷新缓存并与其他处理器通信),因此进行同步方法调用可能需要更长的时间

当你不需要同步时,不要使用它。同步方法,意味着正在获取一个锁,而不考虑它是哪个锁,或 者是否它是否是保护你将要执行的共享数据访问的正确锁。

将synchronized同步到某个方法,则一次只有一个线程可以调用即使其他线程想要在不同的缓冲区上运行,这些缓冲区应该是安全的,它们仍然会被阻塞,直到单锁被释放,性能损失严重。

死锁描述了两个或更多线程 永远被阻塞的情况,都在等待对方。
防止死锁的一种方法是①对需要同时获 取的锁进行排序,并确保所有代码按照该顺序获取锁定

②粗粒度的锁,用单个锁来监控多个 对象实例(在最糟糕的情况下,程序可能基 本上是顺序执行的,丧失了并发性。)

饥饿描述了线程无法获得对共享资源的访问,而无法取得进展的情况。

当共享资源由“贪婪”线程导致长时间不 可用时,会发生这种情况。

例如,假设一个对象提供了一个经常需要很长时间才能返回的同步方法。 如果一个线程频繁地调用这个方法,那么其他线程也需要经常同步访问同一个对象
活锁:如果一个线程的行为也是对 另一个线程的行为的响应,则可能导致活锁。
与死锁一样,活锁线程无法取得进一步进展,线程并未被阻止 ,他们只 是忙于响应对方恢复工作。
Thread.sleep(time)  让当前线程暂停指定 时间的执行
 
Join()方法用于保持当前正在运行的线程的执行,直到该线程死亡(执行完毕),才能继续执行后续线程 (让一 个线程等待另一个线程结束。(可能需要前面线程的输出结果作为输入))
技术分享图片

执行wait()后,当前线程会等待,直到其他线程调用 此对象的notify( ) 方法或 notifyAll( ) 方法。

在将来的某个时间,另一个线程将获得相同的锁并调用Object.notifyAll(),通知等待该锁的所有线程发生了重要事件。第二个线程释放锁定一段时间后,第一个线程重新获取锁定并从等待的调用返回

低级可中断阻塞方法 :Thread.sleep(), Thread.join(), or Object.wait()

阻塞方法:一般方法的完成只取 决于它所要做的事情,以及是否有足够多可用的计算资源,  而阻塞方法的完成还取决于一 些外部的事件,例如计时器到期,I/O 完成,或者另一个线程的动作(释 放一个锁,设置一个标志,或者将一个任务放在一个工作队列中)。一般方法在 它们的工作做完后即可结束,而阻塞方法较难于预测,因为它们取决于外 部事件。阻塞方法可能影响响应能力,因为难于预测它们何时会结束。 阻塞方法可能因为等不到所 等的事件而无法终止,因此令阻塞方法可取消 就非常有用

interrupt()

当另一个线程通过调用 Thread.interrupt() 中断一个线程时,会出现以下两种情况之一。

①如果被中断线程在执行一个低级可中断阻塞方法,例如 Thread.sleep()、 Thread.join() 或Object.wait(),那么它将取消阻塞并抛出 InterruptedException。

②interrupt() 只是设置线程的中断状态。

中断状态可以通过 Thread.isInterrupted() 来读取,并且可以通过一个名为 Thread.interrupted() 的操作读取和清除

中断是礼貌地请求另一个线程在它愿 意并且方便的时候停止它正在做的事情。

 

安全(错误的计算)

活跃度(没有计算)






以上是关于软件构造 并发3(线程安全性)----锁定和同步的主要内容,如果未能解决你的问题,请参考以下文章

软件构造第十章 线程和分布式系统

高并发基石多线程守护线程线程安全线程同步互斥锁

python哪些容器线程安全

python多线程

软件构造复习内容(10)---并发

Java多线程编程——对象及变量的并发访问