java 并发——内置锁 (2)

写个demo测试一下:

public class A { public static void main(String[] args) { A obj_a = new A(); new Thread() { @Override public void run() { try { obj_a.a(); } catch (InterruptedException e) { e.printStackTrace(); } } }.start(); new Thread() { @Override public void run() { try { obj_a.b(); } catch (InterruptedException e) { e.printStackTrace(); } } }.start(); new Thread(){ @Override public void run() { try { A.c(); } catch (InterruptedException e) { e.printStackTrace(); } } }.start(); try { A.d(); } catch (InterruptedException e) { e.printStackTrace(); } } public synchronized void a() throws InterruptedException { System.out.println("--- begin a - current Thread " + Thread.currentThread().getId()); Thread.sleep(8000); System.out.println("--- end a - current Thread " + Thread.currentThread().getId()); } public synchronized void b() throws InterruptedException { System.out.println("--- begin b - current Thread " + Thread.currentThread().getId()); Thread.sleep(8000); System.out.println("--- end b - current Thread " + Thread.currentThread().getId()); } public synchronized static void c() throws InterruptedException { System.out.println("--- begin c - current Thread " + Thread.currentThread().getId()); Thread.sleep(5000); System.out.println("--- end c - current Thread " + Thread.currentThread().getId()); } public synchronized static void d() throws InterruptedException { System.out.println("--- begin d - current Thread " + Thread.currentThread().getId()); Thread.sleep(5000); System.out.println("--- end d - current Thread " + Thread.currentThread().getId()); } }

运行结果如下:

java 并发——内置锁

可以看到,由于方法 a 和 方法 b 是同一个锁 obj_A,所以当某个线程执行其中一个方法是,其他线程也不能执行另一个方法。但是方法 c 是由 A.class 对象锁住的,执行方法 C 的线程与另外两个线程没有互斥关系。

对于某个类的某个特定对象来说,该类中,所有 synchronized 修饰的非静态方法共享同一个锁,当在对象上调用其任意 synchronized 方法时,此对象都被加锁,此时,其他线程调用该对象上任意的 synchronized 方法只有等到前一个线程方法调用完毕并释放了锁之后才能被调用。
而对于一个类中,所有 synchronized 修饰的静态方法共享同一个锁。

可重入锁

当某个线程请求一个由其他线程持有的锁时,发出请求的线程就会阻塞,然而,内置锁是可重入的,,如果某个线程试图获得一个已经由它自己持有的锁,那么这个请求就会成功。这也意味着获取锁的操作粒度是“线程”,而不是“调用”。当线程请求一个未被持有的锁时,jvm 将记下锁的持有者(即哪个线程),并获取该锁的计数值为 1 ,当同一个线程再次获取该锁时, jvm 将计数值递增,当线程退出同步代码块时,计数值将递减,当计数值为 0 时,将释放锁。

public class B { public static void main(String[] args) { B obj_B = new B(); obj_B.b(); } private synchronized void a(){ System.out.println("---a"); } private synchronized void b(){ System.out.println("---b"); a(); } }

执行上面这段代码,将输出

---b ---a

假设没有可重入的锁,对于对象 obj_B 来说,调用 b 方法时,线程将会持有 obj_B 这个锁,在方法 b 中调用方法 a 时,将会一直等待方法 b 释放锁,造成死锁的情况。

《Java 并发编程实战》 中举的可重入锁的例子:

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同步,当调用子类LoggingWidget的doSomething()时锁对象肯定是当时调用的那个LoggingWidget实例,可是问题是当执行到super.doSomething()时,要调用父类的同步方法,那此时锁对象是谁?是同一个锁进入了 2 次,还是获得了子类对象和父类对象的 2 个不同的锁?
下面这段代码能给出结论:

public class Test { public static void main(String[] args) throws InterruptedException { final TestChild t = new TestChild(); new Thread(new Runnable() { @Override public void run() { t.doSomething(); } }).start(); Thread.sleep(100); t.doSomethingElse(); } public synchronized void doSomething() { System.out.println("something sleepy!"); try { Thread.sleep(1000); System.out.println("woke up!"); } catch (InterruptedException e) { e.printStackTrace(); } } private static class TestChild extends Test { public void doSomething() { super.doSomething(); } public synchronized void doSomethingElse() { System.out.println("something else"); } } }

这段代码输出了:

something sleepy! woke up! something else

而不是

something sleepy! something else woke up!

内容版权声明:除非注明,否则皆为本站原创文章。

转载注明出处:https://www.heiqu.com/zyxwzj.html