同步:防止线程同时访问共享数据。
锁:是一种抽象,最多允许一个线程拥有它。 保持锁定是一条线程告诉其他线程的:我正在改变这 个东西,现在不要触摸它
两个操作:获取允许线程获取锁的所有权。 如果一个线程试图获取当前由另一个线程拥有的锁,它将阻塞,直到另一个线程释放该锁。 此时,它将与任何其他尝试获取锁的线程竞争。 一次只能有一个线程拥有该锁。释放锁的所有权,允许另一个线程获得它的所有权
使用锁还会告知编译器和处理器您正在同时使用共享内存,以便将寄存器和高速缓存刷新到共享存储。 这可以确保锁的所有者始终查看最新的数据
阻塞一般意味着线程等待(不再 继续工作),直到一个事件发生。
同步块和方法:锁定:锁是如此常用以至于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同步到某个方法,则一次只有一个线程可以调用,即使其他线程想要在不同的缓冲区上运行,这些缓冲区应该是安全的,它们仍然会被阻塞,直到单锁被释放,性能损失严重。
死锁描述了两个或更多线程 永远被阻塞的情况,都在等待对方。
防止死锁的一种方法是①对需要同时获 取的锁进行排序,并确保所有代码按照该顺序获取锁定
②粗粒度的锁,用单个锁来监控多个 对象实例(在最糟糕的情况下,程序可能基 本上是顺序执行的,丧失了并发性。)
饥饿描述了线程无法获得对共享资源的访问,而无法取得进展的情况。
当共享资源由“贪婪”线程导致长时间不 可用时,会发生这种情况。
执行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() 的操作读取和清除
中断是礼貌地请求另一个线程在它愿 意并且方便的时候停止它正在做的事情。
安全(错误的计算)
活跃度(没有计算)