Java 的 synchronized 是可重入性锁
2016-04-18 13:15
483 查看
当线程请求一个由其它线程持有的对象锁时,该线程会阻塞,而当线程请求由自己持有的对象锁时,如果该锁是重入锁,请求就会成功,否则阻塞.
我们来看看synchronized, 它拥有强制原子性的内置锁机制是一个重入锁, 所以在使用synchronized时, 当一个线程请求得到一个对象锁后再次请求此对象锁,
可以再次得到该对象锁,就是说在一个synchronized方法/块的内部调用本类的其他synchronized方法/块时,是永远可以拿到锁,如下:
运行结果:
child.doSomething()
father.doSomething()
child.doAnotherThing()
这里的对象锁只有一个,就是child对象的锁,当执行child.doSomething时,该线程获得child对象的锁,在doSomething方法内执行doAnotherThing时再次请求child对象的锁,因为synchronized是重入锁,所以可以得到该锁,继续在doAnotherThing里执行父类的doSomething方法时第三次请求child对象的锁,同理可得到,如果不是重入锁的话,那这后面这两次请求锁将会被一直阻塞,从而导致死锁。
所以在java内部,同一线程在调用自己类中其他synchronized方法/块或调用父类的synchronized方法/块都不会阻碍该线程的执行,就是说同一线程对同一个对象锁是可重入的,而且同一个线程可以获取同一把锁多次,也就是可以多次重入。因为java线程是基于“每线程(per-thread)”,而不是基于“每调用(per-invocation)”的(java中线程获得对象锁的操作是以每线程为粒度的,per-invocation互斥体获得对象锁的操作是以每调用作为粒度的)
我们再来看看重入锁是怎么实现可重入性的,其实现方法是为每个锁关联一个线程持有者和计数器,当计数器为0时表示该锁没有被任何线程持有,那么任何线程都可能获得该锁而调用相应的方法;当某一线程请求成功后,JVM会记下锁的持有线程,并且将计数器置为1;此时其它线程请求该锁,则必须等待;而该持有锁的线程如果再次请求这个锁,就可以再次拿到这个锁,同时计数器会递增;当线程退出同步代码块时,计数器会递减,如果计数器为0,则释放该锁。
我们再来看看下例子加深一下理解:
运行结果:
child.doSomething(),i=6
father.doSomething(),i=5
child.doSomething(),i=4
father.doSomething(),i=3
child.doSomething(),i=2
father.doSomething(),i=1
再看:
运行结果:
第1个线程:Thread-16进入到doSomething()执行代码--睡眠7毫秒
Thread-16线程执行doSomething()完毕,睡眠时间:7,已进入doSomething执行代码的线程总数为:1
第2个线程:Thread-10进入到doSomething()执行代码--睡眠154毫秒
第3个线程:Thread-15进入到doSomething()执行代码--睡眠221毫秒
第4个线程:Thread-17进入到doSomething()执行代码--睡眠222毫秒
第5个线程:Thread-14进入到doSomething()执行代码--睡眠276毫秒
Thread-10线程执行doSomething()完毕,睡眠时间:154,已进入doSomething执行代码的线程总数为:5
第6个线程:Thread-19进入到doSomething()执行代码--睡眠340毫秒
第7个线程:Thread-13进入到doSomething()执行代码--睡眠367毫秒
第8个线程:Thread-12进入到doSomething()执行代码--睡眠404毫秒
Thread-15线程执行doSomething()完毕,睡眠时间:221,已进入doSomething执行代码的线程总数为:8
Thread-17线程执行doSomething()完毕,睡眠时间:222,已进入doSomething执行代码的线程总数为:8
第10个线程:Thread-11进入到doSomething()执行代码--睡眠451毫秒
第10个线程:Thread-18进入到doSomething()执行代码--睡眠451毫秒
Thread-14线程执行doSomething()完毕,睡眠时间:276,已进入doSomething执行代码的线程总数为:10
Thread-19线程执行doSomething()完毕,睡眠时间:340,已进入doSomething执行代码的线程总数为:10
Thread-13线程执行doSomething()完毕,睡眠时间:367,已进入doSomething执行代码的线程总数为:10
Thread-12线程执行doSomething()完毕,睡眠时间:404,已进入doSomething执行代码的线程总数为:10
Thread-11线程执行doSomething()完毕,睡眠时间:451,已进入doSomething执行代码的线程总数为:10
Thread-18线程执行doSomething()完毕,睡眠时间:451,已进入doSomething执行代码的线程总数为:10
由此可见多线程在操作同一对象时,如果对象中的函数不是同步的,多线程可以并发执行此函数
如果把doSomething方法加上synchronized同步后的结果变为:
第1个线程:Thread-10进入到doSomething()执行代码--睡眠208毫秒
Thread-10线程执行doSomething()完毕,睡眠时间:208,已进入doSomething执行代码的线程总数为:1
第2个线程:Thread-19进入到doSomething()执行代码--睡眠124毫秒
Thread-19线程执行doSomething()完毕,睡眠时间:124,已进入doSomething执行代码的线程总数为:2
第3个线程:Thread-17进入到doSomething()执行代码--睡眠176毫秒
Thread-17线程执行doSomething()完毕,睡眠时间:176,已进入doSomething执行代码的线程总数为:3
第4个线程:Thread-18进入到doSomething()执行代码--睡眠43毫秒
Thread-18线程执行doSomething()完毕,睡眠时间:43,已进入doSomething执行代码的线程总数为:4
第5个线程:Thread-15进入到doSomething()执行代码--睡眠98毫秒
Thread-15线程执行doSomething()完毕,睡眠时间:98,已进入doSomething执行代码的线程总数为:5
第6个线程:Thread-16进入到doSomething()执行代码--睡眠360毫秒
Thread-16线程执行doSomething()完毕,睡眠时间:360,已进入doSomething执行代码的线程总数为:6
第7个线程:Thread-13进入到doSomething()执行代码--睡眠286毫秒
Thread-13线程执行doSomething()完毕,睡眠时间:286,已进入doSomething执行代码的线程总数为:7
第8个线程:Thread-14进入到doSomething()执行代码--睡眠442毫秒
Thread-14线程执行doSomething()完毕,睡眠时间:442,已进入doSomething执行代码的线程总数为:8
第9个线程:Thread-11进入到doSomething()执行代码--睡眠483毫秒
Thread-11线程执行doSomething()完毕,睡眠时间:483,已进入doSomething执行代码的线程总数为:9
第10个线程:Thread-12进入到doSomething()执行代码--睡眠447毫秒
Thread-12线程执行doSomething()完毕,睡眠时间:447,已进入doSomething执行代码的线程总数为:10
所以不同线程对同一对象锁是要竞争的,是同步阻塞模式,不能像同一线程对同一对象锁是可重入的!
我们来看看synchronized, 它拥有强制原子性的内置锁机制是一个重入锁, 所以在使用synchronized时, 当一个线程请求得到一个对象锁后再次请求此对象锁,
可以再次得到该对象锁,就是说在一个synchronized方法/块的内部调用本类的其他synchronized方法/块时,是永远可以拿到锁,如下:
01 | public class Child extends Father { |
02 | public static void main(String[] args) { |
03 | Child child = new Child(); |
04 | child.doSomething(); |
05 | } |
06 |
07 | public synchronized void doSomething() { |
08 | System.out.println( "child.doSomething()" ); |
09 | doAnotherThing(); // 调用自己类中其他的synchronized方法 |
10 |
11 | } |
12 |
13 | private synchronized void doAnotherThing() { |
14 | super .doSomething(); // 调用父类的synchronized方法 |
15 | System.out.println( "child.doAnotherThing()" ); |
16 | } |
17 | } |
18 |
19 | class Father { |
20 | public synchronized void doSomething() { |
21 | System.out.println( "father.doSomething()" ); |
22 | } |
23 | } |
child.doSomething()
father.doSomething()
child.doAnotherThing()
这里的对象锁只有一个,就是child对象的锁,当执行child.doSomething时,该线程获得child对象的锁,在doSomething方法内执行doAnotherThing时再次请求child对象的锁,因为synchronized是重入锁,所以可以得到该锁,继续在doAnotherThing里执行父类的doSomething方法时第三次请求child对象的锁,同理可得到,如果不是重入锁的话,那这后面这两次请求锁将会被一直阻塞,从而导致死锁。
所以在java内部,同一线程在调用自己类中其他synchronized方法/块或调用父类的synchronized方法/块都不会阻碍该线程的执行,就是说同一线程对同一个对象锁是可重入的,而且同一个线程可以获取同一把锁多次,也就是可以多次重入。因为java线程是基于“每线程(per-thread)”,而不是基于“每调用(per-invocation)”的(java中线程获得对象锁的操作是以每线程为粒度的,per-invocation互斥体获得对象锁的操作是以每调用作为粒度的)
我们再来看看重入锁是怎么实现可重入性的,其实现方法是为每个锁关联一个线程持有者和计数器,当计数器为0时表示该锁没有被任何线程持有,那么任何线程都可能获得该锁而调用相应的方法;当某一线程请求成功后,JVM会记下锁的持有线程,并且将计数器置为1;此时其它线程请求该锁,则必须等待;而该持有锁的线程如果再次请求这个锁,就可以再次拿到这个锁,同时计数器会递增;当线程退出同步代码块时,计数器会递减,如果计数器为0,则释放该锁。
我们再来看看下例子加深一下理解:
01 | public class Child extends Father { |
02 | public static void main(String[] args) { |
03 | Child child = new Child(); |
04 | child.doSomething(); |
05 | } |
06 |
07 | public void doSomething() { |
08 | while (i> 0 ){ |
09 | System.out.println( "child.doSomething(),i=" +i--); |
10 | super .doSomething(); |
11 | doSomething(); |
12 | } |
13 | } |
14 | } |
15 |
16 | class Father { |
17 | int i = 6 ; |
18 | public synchronized void doSomething() { |
19 | System.out.println( "father.doSomething(),i=" +i--); |
20 | } |
21 | } |
child.doSomething(),i=6
father.doSomething(),i=5
child.doSomething(),i=4
father.doSomething(),i=3
child.doSomething(),i=2
father.doSomething(),i=1
再看:
01 | import java.util.ArrayList; |
02 |
03 | public class ReentrancyTest { |
04 | static public int i = 1 ; |
05 | public int count ; |
06 |
07 | public ReentrancyTest() { |
08 | super (); |
09 | } |
10 |
11 | public static void main(String[] args) { |
12 | int threadNum = 10 ; // 设置10个线程同时执行 |
13 | // 每个线程都关联同一个ReentrancyTest对象 |
14 | ReentrancyTest reentrancyTest = new ReentrancyTest(); |
15 | ArrayList<MyThread> threadList = new ArrayList<MyThread>(); |
16 | // 为10个线程赋值同一个ReentrancyTest对象的引用 |
17 | for ( int i = 0 ; |
18 | { |
19 | MyThread myThread = new MyThread(); |
20 | myThread.reentrancyTest = reentrancyTest; |
21 | threadList.add(myThread); |
22 | } |
23 | // 启动10个线程 |
24 | for ( int i = 0 ; |
25 | new Thread((MyThread) threadList.get(i)).start(); |
26 | } |
27 | } |
28 |
29 | public void doSomething() { |
30 | //随机产生一个睡眠时间 |
31 | int sleep=( int )(Math.random()* 500 ); |
32 | try { |
33 | Thread.sleep(sleep); |
34 | } catch (InterruptedException e) { |
35 | e.printStackTrace(); |
36 | } |
37 | count = i++; |
38 | System.out.println( "第" +count+ "个线程:" +Thread.currentThread().getName() |
39 | + "进入到doSomething()执行代码--睡眠" +sleep+ "毫秒" ); |
40 | try { |
41 | Thread.sleep(sleep); |
42 | } catch (InterruptedException e) { |
43 | e.printStackTrace(); |
44 | } |
45 | System.out.println(Thread.currentThread().getName()+ "线程执行doSomething()完毕,睡眠时间:" |
46 | + sleep+ ",已进入doSomething执行代码的线程总数为:" +count); |
47 | } |
48 | } |
49 |
50 | class MyThread extends Thread { |
51 | public ReentrancyTest reentrancyTest; |
52 |
53 | public MyThread() { |
54 | super (); |
55 | } |
56 |
57 | @Override |
58 | public void run() { |
59 | reentrancyTest.doSomething(); |
60 | super .run(); |
61 | } |
62 | } |
第1个线程:Thread-16进入到doSomething()执行代码--睡眠7毫秒
Thread-16线程执行doSomething()完毕,睡眠时间:7,已进入doSomething执行代码的线程总数为:1
第2个线程:Thread-10进入到doSomething()执行代码--睡眠154毫秒
第3个线程:Thread-15进入到doSomething()执行代码--睡眠221毫秒
第4个线程:Thread-17进入到doSomething()执行代码--睡眠222毫秒
第5个线程:Thread-14进入到doSomething()执行代码--睡眠276毫秒
Thread-10线程执行doSomething()完毕,睡眠时间:154,已进入doSomething执行代码的线程总数为:5
第6个线程:Thread-19进入到doSomething()执行代码--睡眠340毫秒
第7个线程:Thread-13进入到doSomething()执行代码--睡眠367毫秒
第8个线程:Thread-12进入到doSomething()执行代码--睡眠404毫秒
Thread-15线程执行doSomething()完毕,睡眠时间:221,已进入doSomething执行代码的线程总数为:8
Thread-17线程执行doSomething()完毕,睡眠时间:222,已进入doSomething执行代码的线程总数为:8
第10个线程:Thread-11进入到doSomething()执行代码--睡眠451毫秒
第10个线程:Thread-18进入到doSomething()执行代码--睡眠451毫秒
Thread-14线程执行doSomething()完毕,睡眠时间:276,已进入doSomething执行代码的线程总数为:10
Thread-19线程执行doSomething()完毕,睡眠时间:340,已进入doSomething执行代码的线程总数为:10
Thread-13线程执行doSomething()完毕,睡眠时间:367,已进入doSomething执行代码的线程总数为:10
Thread-12线程执行doSomething()完毕,睡眠时间:404,已进入doSomething执行代码的线程总数为:10
Thread-11线程执行doSomething()完毕,睡眠时间:451,已进入doSomething执行代码的线程总数为:10
Thread-18线程执行doSomething()完毕,睡眠时间:451,已进入doSomething执行代码的线程总数为:10
由此可见多线程在操作同一对象时,如果对象中的函数不是同步的,多线程可以并发执行此函数
如果把doSomething方法加上synchronized同步后的结果变为:
第1个线程:Thread-10进入到doSomething()执行代码--睡眠208毫秒
Thread-10线程执行doSomething()完毕,睡眠时间:208,已进入doSomething执行代码的线程总数为:1
第2个线程:Thread-19进入到doSomething()执行代码--睡眠124毫秒
Thread-19线程执行doSomething()完毕,睡眠时间:124,已进入doSomething执行代码的线程总数为:2
第3个线程:Thread-17进入到doSomething()执行代码--睡眠176毫秒
Thread-17线程执行doSomething()完毕,睡眠时间:176,已进入doSomething执行代码的线程总数为:3
第4个线程:Thread-18进入到doSomething()执行代码--睡眠43毫秒
Thread-18线程执行doSomething()完毕,睡眠时间:43,已进入doSomething执行代码的线程总数为:4
第5个线程:Thread-15进入到doSomething()执行代码--睡眠98毫秒
Thread-15线程执行doSomething()完毕,睡眠时间:98,已进入doSomething执行代码的线程总数为:5
第6个线程:Thread-16进入到doSomething()执行代码--睡眠360毫秒
Thread-16线程执行doSomething()完毕,睡眠时间:360,已进入doSomething执行代码的线程总数为:6
第7个线程:Thread-13进入到doSomething()执行代码--睡眠286毫秒
Thread-13线程执行doSomething()完毕,睡眠时间:286,已进入doSomething执行代码的线程总数为:7
第8个线程:Thread-14进入到doSomething()执行代码--睡眠442毫秒
Thread-14线程执行doSomething()完毕,睡眠时间:442,已进入doSomething执行代码的线程总数为:8
第9个线程:Thread-11进入到doSomething()执行代码--睡眠483毫秒
Thread-11线程执行doSomething()完毕,睡眠时间:483,已进入doSomething执行代码的线程总数为:9
第10个线程:Thread-12进入到doSomething()执行代码--睡眠447毫秒
Thread-12线程执行doSomething()完毕,睡眠时间:447,已进入doSomething执行代码的线程总数为:10
所以不同线程对同一对象锁是要竞争的,是同步阻塞模式,不能像同一线程对同一对象锁是可重入的!
相关文章推荐
- Java 内存区域和GC机制
- java实现字符串反转(原作有点错误,需要看下评论)
- # 20145334赵文豪 《Java程序设计》第7周学习总结
- SpringMVCRest post delete请求转换
- Java中hashCode的作用
- # 20145334赵文豪 《Java程序设计》第6周学习总结
- Java Web(11) Spring MVC 返回Json
- 学习JAVA之路(六、数组)
- Java入门(一)
- Java学习笔记
- java8 JDK8 元空间--删除
- spring 官方下载地址
- Selenium自动化测试视频教程(Java版)
- 简单介绍java Enumeration
- Java 7新特性总结 - Java IO
- Java 7新特性总结 - Java IO
- java异常:java.lang.OutOfMemoryError: GC overhead limit exceeded
- Java修饰符总结
- Java中各种修饰符与访问修饰符
- java的动态绑定与静态绑定