软件构造 并发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() 的操作读取和清除

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

安全(错误的计算)

活跃度(没有计算)

原文地址:https://www.cnblogs.com/xgl122/p/9199770.html

时间: 2024-11-11 07:50:39

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

软件构造 并发3(线程安全性)

线程安全:数据类型或静态方法在多线程中执行时,无论如何执行,不需调用者做额外的协作仍可以得到正确的行为. 行为正确意味着满足规格说明和保持不变性   不能在前置条件中对调用者增加时间性要求(在set()运行时不能调用get()) 例子:迭代器, 不是线程安全的. 迭代器的规范说,不能在迭代它的同时修改一个集合. 这是一个与调用者相关的时间相关的前提条件,如果违反它,Iterator不保证行为正确 线程安全的四个方法:①限制可变变量的共享②用不可变的共享变量③将共享数据封装在线程安全的数据类型中④

软件构造 并发1

并发:多个运算同时发生.并发模型:共享内存 消息传递共享内存:并发模块通过在内存中读写共享对象进行交互 ①两个处理器共享物理内存②两个程序共享文件③两个线程(同一个java程序)共享对象 消息传递:并发模块通过通信通道相互发送消息进行交互.模块发送消息,并将传入的消息发送到每个模块以便处理 ①网络中两台计算机通信②web浏览器web server③即时消息的客户端和服务器④通过管道连接两个程序的输入和输出 进程 线程两种不同的并发模块进程:正在运行程序的一个实例,拥有自己私有专用的内存空间   

软件构造 并发2

交织竞争条件在某时刻一个运行核心只有一个线程可以运行.   进程/线程采用OS提供的时间片特征来共享处理时间.当线程数多于处理器数量时,并发性通过时间片来模拟,处理器切换处理不同的线程.例子:时间片(其使用是不可预知的,非确定性的,意味着线程可能随时暂停或恢复)           三个线程T1 T2 T3可能在具有两个实际处理器的机器上进行时间分割,一个处理器运行线程T1,另一个运行线程T2,第二个处理器切换到运行线程T3.                                   

Java并发原语——线程、互斥与同步

本文将介绍: Java线程基本操作(创建.等待等) Java线程同步原语(同步.互斥) 如果你对以上话题已了如指掌,请止步. Java线程基本操作 Java的线程API以java.lang.Thread类提供,线程的基本操作被封装为为Thread类的方法,其中常用的方法是:   方法 说明 void start() 启动线程 void join() 等待线程结束 创建(启动)线程 Java中,创建线程的过程分为两步: 创建可执行(Runnable)的线程对象: 调用它的start()方法: 可执

第二章:线程安全性——java并发编程实战

一个对象是否需要是线程安全的取决于它是否被多个线程访问. 当多个线程访问同一个可变状态量时如果没有使用正确的同步规则,就有可能出错.解决办法: 不在线程之间共享该变量 将状态变量修改为不可变的 在访问状态变量时使用同步机制 完全由线程安全类构造的程序也不一定是线程安全的,线程安全类中也可以包含非线程安全的类 一.什么是线程安全性 线程安全是指多个线程在访问一个类时,如果不需要额外的同步,这个类的行为仍然是正确的.(因为线程安全类中封装了必要的同步代码) 一个无状态的类是线程安全的.无状态类是指不

并发基础知识 — 线程安全性

前段时间看完了<并发编程的艺术>,总感觉自己对于并发缺少一些整体的认识.今天借助<Java并发编程实践>,从一些基本概念开始,重新整理一下自己学过并发编程.从并发基础开始,深入进去,系统学习一下并发编程. 编写线程安全的代码,核心在于要对状态访问操作进行管理,特别是对共享的(Shared)和可变的(Mutable)状态的访问.对象的状态是指存储在状态变量(实例或静态域)中的数据.对象的状态还可能包括其他依赖对象的域.(Map.Entry) 一个对象是否需要时线程安全的,取决于该对象

并发编程之线程安全性

一.什么是线程安全性 并发编程中要编写线程安全的代码,则必须对可变的共享状态的访问操作进行管理. 对象的状态就是存储在实例或者静态变量中的数据,同时其状态也包含其关联对象的字段,比如字典集合既包含自己的状态, 也包含KeyValuePair. 共享即可以多个线程同时访问变量,可变即变量在其声明周期内可以发生变化. 代码线程安全性关注的是防止对数据进行不可控的并发访问. 是否以多线程的方式访问对象,决定了此对象是否需要线程安全性.线程安全性强调的是对对象的访问方式,而不是对象 要实现的功能.要实现

JAVA并发编程实战 读书笔记(一)线程安全性

线程安全性   1.什么是线程安全 在线程安全的定义中,最核心的概念是正确性.正确性的含义是,某个类的行为与规范完全一致.当对正确性有了一个比较清晰的定义后,就可以定义线程安全性:当多个线程访问某个类时,这个类始终能表现出正确的行为,那这个类就是线程安全的. 举例:无状态对象一定是线程安全的. 大多数Servlet都是无状态的,当Servlet在处理请求时需要保存一些信息时,线程安全才会成为一个问题. 2.原子性 举个例子:语句 ++i:虽然递增操作++i是一种紧凑的语法,使其看上去是一个操作,

并发编程初探-线程安全性

在Java并发编程中,对于线程安全是非常重要的,也是必须要考虑的一个问题.可以这么说,只要涉及到网络的,都必须考虑线程安全问题.好了,开始噼里啪啦地开始敲代码之前,我觉得有必要了解一些文绉绉的理论知识,因为这些理论知识是我们敲出来的代码是否是线程安全的一个依据. 当多个线程访问某个状态变量并且其中有一个线程执行写入操作的时候,必须考虑采用同步机制来协同这些线程对变量的访问,Java中的主要同步机制是关键字synchronized,它提供了一种独占的加锁方式,但"同步"这个术语还包括类型的变量,显