其实这一节要说的重点又是synchronized,由于和内置锁最相关的应该就是synchronized了。固然咱们仍是会从一些例子开始讲起。样例程序仍是来自那本书: java
@NotThreadSafe public class UnsafeCachingFactorizer implements Servlet { private final AtomicReference<BigInteger> lastNumber = new AtomicReference<BigInteger>(); private final AtomicReference<BigInteger[]> lastFactors = new AtomicReference<BigInteger[]>(); public void service(ServletRequest req, ServletResponse resp) { BigInteger i = extractFromRequest(req); if (i.equals(lastNumber.get())) encodeIntoResponse(resp, lastFactors.get() ); else { BigInteger[] factors = factor(i); lastNumber.set(i); lastFactors.set(factors); encodeIntoResponse(resp, factors); } } }
这个程序很明显是一个线程不安全的类,而程序想要告诉咱们的是这样一件事:尽管lastNumber和lastFactors都是线程安全的类,并且它们的方法是原子性的,是线程安全的,可是,若是将它们组合在一块儿,就破坏了原子性,就是不安全的。这也告诫咱们,不要随意的去拼凑两个原子性操做。 算法
解决的方法很简单,在方法前加修饰符synchronized。固然这不是一个好办法,虽然能解决问题,可是它却带来了性能上的问题。每个要执行service方法的线程,必需要等到前一个线程执行完了service,才能执行。固然,稍微好点儿的办法是写成synchronized块。 安全
@ThreadSafe public class CachedFactorizer implements Servlet { @GuardedBy("this") private BigInteger lastNumber; @GuardedBy("this") private BigInteger[] lastFactors; @GuardedBy("this") private long hits; @GuardedBy("this") private long cacheHits; public synchronized long getHits() { return hits; } public synchronized double getCacheHitRatio() { return (double) cacheHits / (double) hits; } public void service(ServletRequest req, ServletResponse resp) { BigInteger i = extractFromRequest(req); BigInteger[] factors = null; synchronized (this) { ++hits; if (i.equals(lastNumber)) { ++cacheHits; factors = lastFactors.clone(); } } if (factors == null) { factors = factor(i); synchronized (this) { lastNumber = i; lastFactors = factors.clone(); } } encodeIntoResponse(resp, factors); } }
值得注意的是,若是使用了synchronized块包围了的对象或者操做,尽可能使用最简单纯粹的变量(避免使用原子对象)。这里涉及到一个锁的嵌套的问题,这个内容会在后续的章节中讲述,如今只要记住一点:避免锁的嵌套。 并发
而今天的主题是为何这样作了,程序就变得Thread Safe了,synchronized的原理究竟是什么,底层究竟是怎么实现的。Every Java object can implicitly act as a lock for purposes of synchronization; these built-in locks are called intrinsic locks or monitor locks。内置锁或者监视器锁。内置锁是一种互斥锁,同一时刻只容许一个线程执行内置锁保护的代码。因此一旦程序执行到synchronized锁包括的代码时,就会去拿内置锁,若是内置锁没有被别的线程占用,则拿到锁,待执行完相关代码后,释放锁;若是内置锁已经被别的线程占用,则等待。 ide
上述的概念都容易理解,只不过还要注意两点,第一,内置锁对那些没有加synchronized修饰符的方法是不起做用的,线程仍是能照样访问到;第二,内置锁是可重入的。 性能
针对第一点,咱们看下面的程序就能理解: 测试
package com.a2.concurrency.chapter1; /** * 主要验证内置锁的做用域和做用时间 * * @author ChenHui * */ public class TestSynchronized { private int i = 0; public synchronized void add() { System.out.println("excute add()"); i++; try { Thread.sleep(2000); System.out.println("after excute add()..."); } catch (Exception e) { System.err.print("error"); } } public synchronized void sub() { System.out.println("excute sub()..."); i--; } public void print() { try { Thread.sleep(1000); } catch (Exception e) { } System.out.println("value of i:" + i); } public static void main(String[] args) throws Exception { final TestSynchronized ts = new TestSynchronized(); Thread th1 = new Thread("th1") { public void run() { System.out.println("Thread:" + super.getName()); ts.add(); } }; final Thread th2 = new Thread("th2") { public void run() { System.out.println("THread:" + super.getName()); /**改变ts.sub()和ts.print()执行顺序,就能体会出内置锁对print方法是不上锁的*/ ts.sub(); ts.print(); // ts.sub(); } }; th1.start(); Thread.sleep(1000); th2.start(); } }
关于第二点,主要是要理解概念,重入,即reentrant。When a thread requests a lock that is already held by another thread, the requesting thread blocks. But because intrinsic locks are reentrant, if a thread tries to acquire a lock that it already holds, the request succeeds.注意这个it already holds,这个指的是,若是当前线程已经由它本身持有的锁。看代码: ui
public class Widget { public synchronized void doSomething() { ... } } public class LoggingWidget extends Widget { public synchronized void doSomething() { System.out.println(toString() + ": calling doSomething"); super.doSomething(); } }
上述代码,若是子类调用doSomething方法时,得到自身类锁的同时会得到父类的锁,若是内置锁不可重入,那调用super.doSomething时就会发生死锁。因此重入就是,你拿了父类的锁,再调用该锁包含的代码能够不用再次拿锁。注意,/**重入始终是发生在一个线程中的-->这句话有问题改成-->重入是针对同一个引用的*/重入是针对同一个引用的。 this
下面的代码主要是为了证实:得到自身类锁的同时会得到父类的锁。 spa
package com.a2.concurrency.chapter1; /** * 主要为了测试,子类覆盖父类方法后,调用子类方法时,也获取父类的锁 * * @author ChenHui * */ public class TestWidget { public static void main(String[] args) throws InterruptedException { final LoggingWidget widget = new LoggingWidget(); Thread th1 = new Thread("th1") { @Override public void run() { System.out.println(super.getName() + ":start\r\n"); widget.doSometing(); } }; Thread th2 = new Thread("th2") { @Override public void run() { System.out.println(super.getName() + ":start\r\n"); /** 为了说明子类复写父类方法后,调用时也持有父类锁*/ widget.doAnother(); /**证实了内置锁对那些没有加synchronized修饰符的方法是不起做用的*/ // widget.doNother(); /**为了说明子类复写父类方法后,调用时也持有父类锁,也持有本身本类的锁*/ // widget.doMyLike(); /**这是两个线程,这是须要等待的,并非继承的关系,不是重入,重入是发生在一个线程中的*/ // widget.doSometing(); } }; th1.start(); Thread.sleep(1000); th2.start(); } } class Widget { public synchronized void doSometing() { System.out.println("widget ... do something..."); } public synchronized void doAnother() { System.out.println("widget... do another thing..."); } public void doNother() { System.out.println("widget... do Nothing..."); } } class LoggingWidget extends Widget { @Override public synchronized void doSometing() { try { System.out.println("loggingwidget do something..."); Thread.sleep(3000); System.out.println("end loggingwidget do something..."); } catch (InterruptedException e) { // TODO Auto-generated catch block e.printStackTrace(); } super.doSometing(); } public synchronized void doMyLike() { System.out.println("loggingwidget do my like..."); } }
至此,synchronized的两个重要特色已经讲完,至于synchronized和Lock的性能,以及synchronized底层实现原理,将在后面的章节中讲述。其实讲到这里基本是能完成JDK1.5以前的并发任务了,虽然还有不少能够由算法实现的原子操做或者锁,可是synchronized仍是用起来最方便的。只是,你们被它的重量级给吓坏。好在1.6以后就有所改观了。
固然后面还有更好的类和方法会介绍。关于并发有太多太多的内容能够写。我也一直有一个疑问,底层究竟是怎么作事务回滚的?有相关的代码实现,仍是在硬件层作的。很好奇。
谢谢观赏。