Java并发编程--加锁机制初步,内置锁以及内置锁的重入

其实这一节要说的重点又是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();
	}
}

关于第二点,主要是要理解概念,重入,即reentrantWhen 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的两个重要特色已经讲完,至于synchronizedLock的性能,以及synchronized底层实现原理,将在后面的章节中讲述。其实讲到这里基本是能完成JDK1.5以前的并发任务了,虽然还有不少能够由算法实现的原子操做或者锁,可是synchronized仍是用起来最方便的。只是,你们被它的重量级给吓坏。好在1.6以后就有所改观了。

固然后面还有更好的类和方法会介绍。关于并发有太多太多的内容能够写。我也一直有一个疑问,底层究竟是怎么作事务回滚的?有相关的代码实现,仍是在硬件层作的。很好奇。

谢谢观赏。
相关文章
相关标签/搜索