内置锁java
Java提供了一种内置的锁机制来支持原子性:同步代码块(Synchronized Block)。
编程
/* *原文出处:http://liuxp0827.blog.51cto.com/5013343/1414349 */ synchronzied (lock){ //访问或修改由锁保护的共享状态 }
每一个Java独享均可以用做一个实现同步的锁,这些锁被称为内置锁(Itrinsic Lock)或者监事锁(Monitor Lock)。线程在进入同步代码块以前会自动得到锁,而且在退出同步代码块时自动释放锁。Java的内置锁至关于一种互斥体,这意味着最多只有一个线程能持有这种锁。当线程1尝试获取一个由线程2持有的锁时,线程1必须等待或者阻塞,知道线程2释放这个锁。因为这个锁保护的同步代码块会以原子方式执行(一组语句做为一个不可分割的单元被执行),多个线程在执行改代码块时互不干扰。咱们再来改善下上篇博客 Java并发编程学习笔记(一)线程安全性 1 最后那段Servlet处理因数分解的代码:
缓存
/* *原文出处:http://liuxp0827.blog.51cto.com/5013343/1414349 */ @ThreadSafe //并不是好的代码 public class SynchronizedFactorizer implements Servlet { @GuardedBy("this") private BigInteger lastNumber; @GuardedBy("this") private BigInteger[] lastFactors; public synchronized void service(ServletRequest req,ServletResponse resp) { BigInteger i = extractFromRequest(req); if(i.equals(lastNumber)) encodeIntoResponse(resp,lastNumber.get()); else{ BigInteger[] factors = factor(i); lastNumber = i; lastFactors = factors; encodeIntoResponse(resp,factors); } } }
这个Servlet能正确地缓存最新的计算结果,但并发性却很是糟糕,服务的响应性很是低,没法使人接受。这是一个性能问题,并非线程安全的问题。
安全
可重入 并发
线程安全:被多个并发的线程反复调用时,他会产生正确的结果。可重入:当被多个线程调用的时候,不会引用任何共享数据。
ide
当某个线程请求一个由其余线程持有的锁时,发出请求的线程就会阻塞。不过,内置锁是可重入的,所以若是某个线程试图得到一个已经由它本身持有的锁,那么这个请求就会成功。可重入的一种实现方法,是为每一个锁关联一个获取计数值和一个全部者线程。当计数值为0时,这个锁就认为是没有被任何线程持有。当线程请求一个未被持有的锁时,JVM将记下锁的持有者,而且将获取计数值置为1.若是同一个线程再次获取这个锁,计数值将递增,而当线程退出同步代码块时,计数值会相应地递减。
函数
/* *原文出处:http://liuxp0827.blog.51cto.com/5013343/1414349 */ public class Widget { public synchronized void doSomething() { ... } } public class LoggingWidget extends Widget { public synchronized void doSomething() { System.out.println(toString() + ": calling doSomething"); super.doSomething(); } }
上面代码中,子类改写了父类的synchronized方法,而后调用父类中的方法,此时若是没有可重入的锁,那么这段代码将死锁。每一个doSomething方法在执行前都会获取Widget上的锁,若是这个内置锁不是可重入的,那么在调用super.doSomething时将没法得到Widget上的锁。重入则避免了这样的状况。可重入函数必定是线程安全的;线程安全的函数多是重入的,也多是不重入的;线程不安全的函数必定是不可重入的。性能
用锁来保护状态
学习
因为锁能使其保护的代码路径以串行形式(多个线程依次以独占的方式访问对象,而不是并发的访问)来访问,所以能够经过锁来构造一些协议已实现对共享状态的独占访问。
this
访问共享状态的复合操做,例如上篇博客中提到的命中计数器的递增操做或者延迟初始化,都必须是原子操做。但仅仅将复合操做封装到同步代码块是不够的。若是用同步来协调对某个变量的访问,那么在访问这个变量的全部位置上都须要使用同步,并且,使用锁来协调对某个变量的访问时,在访问变量的全部位置上都要使用同一个锁。对于可能被多个线程同时访问的可变状态变量,在访问它时都须要持有同一个锁,在这种状况下,称状态变量是由这个锁保护的。
上述代码SynchronizedFactorizer中,lastNumber和lastFactors都是有Servlet的内置锁保护的,对象的内置锁与其状态之间没有内在的关联。虽然大多数类都将内置锁用做一种有效的加锁机制,但对象的域并不必定要经过内置所来保护。当线程获取与对象关联的锁时,并不能阻止其余线程访问对象,只能阻止其余线程得到同一个锁。
一种常见的加锁约定,将全部的可变状态都封装在对象内部,并经过对象的内置锁对全部访问可变状态的代码路径进行同步,使得在该对象上不会发生并发访问。可是,若是在添加新的方法或者代码路径忘记了同步,那么这种加锁协议会很容易被破坏。
当某个变量由锁保护时,每次访问这个变量时都须要首先得到锁,这样就保证了同一时刻只有一个线程能够访问这个变量。当类的不变形条件涉及到多个状态变量的时候,每一个变量都必须由同一个锁来保护。所以能够在单个原子操做中访问这些变量。
未完待续... Java并发编程学习笔记(三)线程安全性 3