文档章节

java内置锁synchronized的可重入性

Mr&Cheng
 Mr&Cheng
发布于 2013/02/02 11:57
字数 2078
阅读 3.9K
收藏 9

    当线程请求一个由其它线程持有的对象锁时,该线程会阻塞,而当线程请求由自己持有的对象锁时,如果该锁是重入锁,请求就会成功,否则阻塞.

    我们来看看synchronized,它拥有强制原子性的内置锁机制,是一个重入锁,所以在使用synchronized时,当一个线程请求得到一个对象锁后再次请求此对象锁,可以再次得到该对象锁,就是说在一个synchronized方法/块的内部调用本类的其他synchronized方法/块时,是永远可以拿到锁,如下:

public class Child extends Father {
	public static void main(String[] args) {
		Child child = new Child();
		child.doSomething();
	}

	public synchronized void doSomething() {
		System.out.println("child.doSomething()");
		doAnotherThing(); // 调用自己类中其他的synchronized方法
		
	}

	private synchronized void doAnotherThing() {
		super.doSomething(); // 调用父类的synchronized方法
		System.out.println("child.doAnotherThing()");
	}
}

class Father {
	public synchronized void doSomething() {
		System.out.println("father.doSomething()");
	}
}

运行结果:

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,则释放该锁。

   我们再来看看下例子加深一下理解:

public class Child extends Father {
	public static void main(String[] args) {
		Child child = new Child();
		child.doSomething();
	}

	public  void doSomething() {
		while(i>0){
			System.out.println("child.doSomething(),i="+i--);
			super.doSomething();
			doSomething();
		}
	}
}

class Father {
	int i = 6;
	public synchronized void doSomething() {
		System.out.println("father.doSomething(),i="+i--);
	}
}

运行结果:

child.doSomething(),i=6
father.doSomething(),i=5
child.doSomething(),i=4
father.doSomething(),i=3
child.doSomething(),i=2
father.doSomething(),i=1

 

再看:

import java.util.ArrayList;

public class ReentrancyTest {
	static public int i = 1;
	public int count ;

	public ReentrancyTest() {
		super();
	}

	public static void main(String[] args) {
		int threadNum = 10; // 设置10个线程同时执行
		// 每个线程都关联同一个ReentrancyTest对象
		ReentrancyTest reentrancyTest = new ReentrancyTest(); 
		ArrayList<MyThread> threadList = new ArrayList<MyThread>();
		 // 为10个线程赋值同一个ReentrancyTest对象的引用
		for (int i = 0; i < threadNum; i++)
		{
			MyThread myThread = new MyThread();
			myThread.reentrancyTest = reentrancyTest;
			threadList.add(myThread);
		}
		 // 启动10个线程
		for (int i = 0; i < threadNum; i++) {
			new Thread((MyThread) threadList.get(i)).start();
		}
	}

	public void doSomething() { 
		//随机产生一个睡眠时间
		int sleep=(int)(Math.random()*500);     
		try {
			Thread.sleep(sleep); 
		} catch (InterruptedException e) {
			e.printStackTrace();
		}
		count = i++;
		System.out.println("第"+count+"个线程:"+Thread.currentThread().getName()
				+ "进入到doSomething()执行代码--睡眠"+sleep+"毫秒");
		try {
			Thread.sleep(sleep);
		} catch (InterruptedException e) {
			e.printStackTrace();
		}
		System.out.println(Thread.currentThread().getName()+ "线程执行doSomething()完毕,睡眠时间:"
				+ sleep+",已进入doSomething执行代码的线程总数为:"+count);
	}
}

class MyThread extends Thread {
	public ReentrancyTest reentrancyTest;

	public MyThread() {
		super();
	}

	@Override
	public void run() {
		reentrancyTest.doSomething();
		super.run();
	}
}

 

运行结果:

第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

 

    所以不同线程对同一对象锁是要竞争的,是同步阻塞模式,不能像同一线程对同一对象锁是可重入的!

© 著作权归作者所有

Mr&Cheng
粉丝 29
博文 101
码字总数 7440
作品 0
广州
高级程序员
私信 提问
加载中

评论(2)

迪伦少校
迪伦少校
很不错!
开源中国首席----
开源中国首席----
那么多篇写重入锁的,就这篇说清楚了!
synchronized ReentrantLock 比较分析

     在编写多线程代码的时候,对于不允许并发的代码,很多需要加锁进行处理。在进行加锁处理时候,synchronized作为java的内置锁,同时也是java关键字,最为被人熟知,即使是最初级的j...

阿姆斯特朗回旋炮
2018/07/18
0
0
Java多线程7 Lock&Condition实现线程同步通信

1 synchronized的缺陷 synchronized是java中的一个关键字,也就是说是Java语言内置的特性。那么为什么会出现Lock呢? 在上面一篇文章中,我们了解到如果一个代码块被synchronized修饰了,当一...

香沙小熊
2018/11/28
0
0
java并发编程(一): 线程安全性

线程安全性: 要编写线程安全的代码,其核心就是要对状态访问操作进行管理,特别是共享的(Shared)和可变的(Mutable)状态的访问; “共享”:多个线程可访问同一变量; “可变”:变量值在声明...

ihaolin
2014/03/22
1.6K
8
【Java并发性和多线程】Java中的锁

本文为转载学习 原文链接:http://ifeve.com/locks/ 锁像synchronized同步块一样,是一种线程同步机制,但比Java中的synchronized同步块更复杂。因为锁(以及其它更高级的线程同步机制)是由...

heroShane
2014/02/02
116
0
synchronized 总结

方法锁形式: synchronized 修饰普通方法, 锁对象默认为 this ,也就是当前实例对象 注意如果是 不同 类锁的 不同 的同步代码块。情况是有点特殊的,虽然也是有顺序了。 类锁 如果是对象锁的...

之渊
2019/07/14
28
0

没有更多内容

加载失败,请刷新页面

加载更多

Android MVP 快速开发框架ZBLibrary

MVP 架构,提供一套开发标准(View,Data,Event)以及模板和工具类并规范代码。封装层级少,简单高效兼容性好。 OKHttp、UIL图片加载、ZXing二维码、沉浸状态栏、下载安装、自动缓存以及各种B...

boonya
7分钟前
3
0
printf的格式很长的论点是什么?

printf函数采用参数类型,例如%d或%i用于signed int 。 但是,我没有看到任何long价值的东西。 #1楼 如果您打算像我一样打印unsigned long long ,请使用: unsigned long long n;printf("...

技术盛宴
13分钟前
19
0
为BlueLake主题增加图片放大效果

fancyBox 是一个流行的媒体展示增强组件,可以方便为网站添加图片放大、相册浏览、视频弹出层播放等效果。优点有使用简单,支持高度自定义,兼顾触屏、响应式移动端特性,总之使用体验相当好...

CREATE_17
14分钟前
21
0
如何将现有的Git存储库导入另一个?

我在名为XXX的文件夹中有一个Git存储库,还有第二个名为YYY的 Git存储库。 我想将XXX存储库作为名为ZZZ的子目录导入YYY存储库,并将所有XXX的更改历史记录添加到YYY 。 之前的文件夹结构: ...

javail
28分钟前
10
0
JSP-Servlet入门2之JSP运行原理(一)

JSP全名为Java Server Pages,中文名叫java服务器页面,是一种动态页面技术 。实际上JSP是指在HTML中嵌入java脚本语言, 一、 JSP起源 在很多动态网页中,绝大部分内容都是固定不变的,只有局...

橘子_
55分钟前
12
0

没有更多内容

加载失败,请刷新页面

加载更多

返回顶部
顶部