Java内部锁的可重用性(Reentrancy)
Java提供了强制原子性的内部锁机制:synchronized块。但是内部锁是可重入的,当线程试图获得它自己占有的锁时,请求会成功。
简单的说,就是在一个synchronized方法内部调用本类的其他synchronized方法时,永远可以拿到锁。
如例子1.1所示
- public class LoggingWidget extends Widget{
- public static void main(String[] args) {
- LoggingWidget lw=new LoggingWidget();
- lw.doSomething();
- }
- public synchronized void doSomething(){
- System.out.println("LoggingWidget->doSomething()");
- doAnotherThing(); //调用自己类中其他的synchronized方法
- super.doSomething(); //调用父类的synchronized方法
- }
- private synchronized void doAnotherThing(){
- System.out.println("LoggingWidget->doAnotherThing()");
- }
- }
- class Widget{
- public synchronized void doSomething(){
- System.out.println("Widget->doSomething()");
- }
- }
执行结果是:
- LoggingWidget->doSomething()
- LoggingWidget->doAnotherThing()
- Widget->doSomething()
可见,在java内部,调用父类的synchronized方法和调用自己类中其他synchronized方法都不会阻碍该程序的运行,正是因为java线程是基于“每线程(per-thread)”,而不是基于“每调用的(per-invocation)”的。重进入的实现是通过为每个锁关联一个请求计数和一个占有它的线程。
更有甚者会出现无限递归的情况:
- public class reentry{
- int i=0;
- public static void main(String[] args) {
- reentry lw=new reentry();
- lw.doSomething();
- }
- public synchronized void doSomething(){
- System.out.println("doSomething()"+i++);
- doSomething(); //synchronized方法递归调用
- }
- }
输出结果:
.
- .....
- doSomething()3831
- doSomething()3832
- doSomething()3833
- Exception in thread "main" java.lang.StackOverflowError
- ......
可以看到,程序进入了死循环,直到堆栈溢出。但是不会将锁释放出去。
=============================================================
要记住一句话“重进入的实现是通过为每个锁关联一个请求计数和一个占有它的线程”,就是说如果有其他线程调用该对象的某个方法,那么该对象的其他synchronized方法并不能与其重进入,而是互斥,因为占有该函数的进程不一样,看如下例子:
- public class LoggingWidget {
- static public int i=0;
- public int ii=0;
- public LoggingWidget() {
- super();
- }
- public static void main(String[] args) {
- int totalNumOfThread=20; //有20个线程同时执行
- LoggingWidget lw=new LoggingWidget(); //每个线程都关联同一个LoggingWidget对象
- ArrayList<outer> o=new ArrayList<outer>();
- for(int s=0;s<totalNumOfThread;s++) //为20个线程赋值同一个LoggingWidget对象的引用
- {
- outer t=new outer();
- t.lw=lw;
- o.add(t);
- }
- for(int s=0;s<totalNumOfThread;s++)
- {
- new Thread((outer)o.get(s)).start(); //启动20个线程
- }
- }
- public void doSomething(){ //注意,这里没有给方法synchronized属性
- int sleep=(int)(Math.random()*500); //随机产生一个睡眠时间
- try {
- Thread.sleep(sleep); //睡眠
- } catch (InterruptedException e) {
- e.printStackTrace();
- }
- ii=i++; //为每个线程赋予一个ID,ID自增
- try {
- Thread.sleep(sleep); //继续睡眠
- } catch (InterruptedException e) {
- e.printStackTrace();
- }
- System.out.println(ii+"线程执行LoggingWidget->doSomething(),睡眠时间:"+sleep);
- }
- }
- class outer extends Thread
- {
- public LoggingWidget lw;
- public outer() {
- super();
- }
- @Override
- public void run() {
- lw.doSomething();
- super.run();
- }
- }
由于没有给方法synchronized属性,所以输出结果如下:
1线程执行LoggingWidget->doSomething(),睡眠时间:4
1线程执行LoggingWidget->doSomething(),睡眠时间:4
3线程执行LoggingWidget->doSomething(),睡眠时间:56
8线程执行LoggingWidget->doSomething(),睡眠时间:108
11线程执行LoggingWidget->doSomething(),睡眠时间:151
11线程执行LoggingWidget->doSomething(),睡眠时间:157
12线程执行LoggingWidget->doSomething(),睡眠时间:177
13线程执行LoggingWidget->doSomething(),睡眠时间:192
14线程执行LoggingWidget->doSomething(),睡眠时间:213
16线程执行LoggingWidget->doSomething(),睡眠时间:218
17线程执行LoggingWidget->doSomething(),睡眠时间:232
19线程执行LoggingWidget->doSomething(),睡眠时间:280
19线程执行LoggingWidget->doSomething(),睡眠时间:354
19线程执行LoggingWidget->doSomething(),睡眠时间:358
19线程执行LoggingWidget->doSomething(),睡眠时间:401
19线程执行LoggingWidget->doSomething(),睡眠时间:428
19线程执行LoggingWidget->doSomething(),睡眠时间:437
19线程执行LoggingWidget->doSomething(),睡眠时间:455
19线程执行LoggingWidget->doSomething(),睡眠时间:468
19线程执行LoggingWidget->doSomething(),睡眠时间:498
如果方法public void doSomething()改成public synchronized void doSomething(),那么输出结果为:
0线程执行LoggingWidget->doSomething(),睡眠时间:384
1线程执行LoggingWidget->doSomething(),睡眠时间:26
2线程执行LoggingWidget->doSomething(),睡眠时间:391
3线程执行LoggingWidget->doSomething(),睡眠时间:289
4线程执行LoggingWidget->doSomething(),睡眠时间:266
5线程执行LoggingWidget->doSomething(),睡眠时间:248
6线程执行LoggingWidget->doSomething(),睡眠时间:121
7线程执行LoggingWidget->doSomething(),睡眠时间:395
8线程执行LoggingWidget->doSomething(),睡眠时间:454
9线程执行LoggingWidget->doSomething(),睡眠时间:457
10线程执行LoggingWidget->doSomething(),睡眠时间:181
11线程执行LoggingWidget->doSomething(),睡眠时间:170
12线程执行LoggingWidget->doSomething(),睡眠时间:470
13线程执行LoggingWidget->doSomething(),睡眠时间:444
14线程执行LoggingWidget->doSomething(),睡眠时间:114
15线程执行LoggingWidget->doSomething(),睡眠时间:4
16线程执行LoggingWidget->doSomething(),睡眠时间:40
17线程执行LoggingWidget->doSomething(),睡眠时间:320
18线程执行LoggingWidget->doSomething(),睡眠时间:416
19线程执行LoggingWidget->doSomething(),睡眠时间:148
可见,不同线程调用同一个对象的synchronized方法,是不会重进入的,而是产生互斥锁.
转自:http://blog.csdn.net/haydenwang8287/article/details/5837734
Java内部锁的可重用性(Reentrancy)的更多相关文章
- Java类锁和对象锁实践和内部私有锁关联
Java类锁和对象锁实践 感谢[jiehao]同学的投稿,投稿可将文章发送到tengfei@ifeve.com 类锁和对象锁是否会冲突?对象锁和私有锁是否会冲突?通过实例来进行说明. 一.相关约定 为 ...
- Java使用泛型类来提高方法的可重用性
我的技术博客经常被流氓网站恶意爬取转载.请移步原文:http://www.cnblogs.com/hamhog/p/3832268.html,享受整齐的排版.有效的链接.正确的代码缩进.更好的阅读体验 ...
- java的锁机制
一段synchronized的代码被一个线程执行之前,他要先拿到执行这段代码的权限,在Java里边就是拿到某个同步对象的锁(一个对象只有一把锁): 如果这个时候同步对象的锁被其他线程拿走了,他(这个线 ...
- Class撑起了OOP世界的天。Class类是OO的基本单元,OO的世界都是通过一个一个的类协作完成的,提高软件的重用性、灵活性和扩展性(转)
引言 在OO的工作中,我们一定会涉及到类,抽象类和接口.那么类和抽象类以及接口到底扮演的什么角色? 本文主要是从人类社会的角度阐述类与抽象类以及接口的“社会”关系,从而让我们抛弃书上的那些死记硬背的概 ...
- java的锁机制——synchronized
一段synchronized的代码被一个线程执行之前,他要先拿到执行这段代码的权限,在java里边就是拿到某个同步对象的锁(一个对象只有一把锁): 如果这个时候同步对象的锁被其他线程拿走了,他(这个线 ...
- Java - “JUC”锁
[Java并发编程实战]-----“J.U.C”:锁,lock 在java中有两种方法实现锁机制,一种是在前一篇博客中([java7并发编程实战]-----线程同步机制:synchronized) ...
- Java 各种锁的小结
一. synchronized 在 JDK 1.6 之前,synchronized 是重量级锁,效率低下. 从 JDK 1.6 开始,synchronized 做了很多优化,如偏向锁.轻量级锁.自旋锁 ...
- Java悲观锁和乐观锁
悲观的并发策略——Synchronized互斥锁 互斥锁是最常见的同步手段,在并发过程中,当多条线程对同一个共享数据竞争时,它保证共享数据同一时刻只能被一条线程使用,其他线程只有等到锁释放后才能重新进 ...
- 深入理解JVM(③)Java的锁优化
前言 从JDK5到JDK6HotSpot虚拟机开发团队花费了大量的资源实现了各种锁优化技术,如适应性自旋(Adaptive Spinning).锁消除(Lock Elimination).锁膨胀(Lo ...
随机推荐
- Parenthesis(前缀和+线段树)
1809: Parenthesis Time Limit: 5 Sec Memory Limit: 128 Mb Submitted: 2291 Solved: 622 Des ...
- Python 网络编程——socket
一 客户端/服务器架构 客户端(Client)服务器(Server)架构,即C/S架构,包括 1.硬件C/S架构(打印机) 2.软件C/S架构(web服务) 理想/目标状态—— 最常用的软件服务器是 ...
- Linux中的欢迎信息
本地终端欢迎信息 /etc/issue \d 显示当前系统日期 \s 显示操作系统名称 \l 显示终端的终端号,这个比较常用 \m 显示硬件体系结构,如i386.i68 ...
- 004-mysql explain详解
一.使用 使用explain + 查询语句 二.解释说明 1)id列[执行顺序] id列数字越大越先执行,如果说数字一样大,那么就从上往下依次执行,id列为null的就表是这是一个结果集,不需要使用它 ...
- 001-unity3d简介以及界面说明
一.简介 学习路线1.C#.网络[http,socket]io2.GUI.NGUI.2DToolKit3.3D控制.物理引擎.角色控制4.粒子系统.音频等5.android.IOS开发基础6.sock ...
- Numpy用于数组的文件输入输出
这一章比较简单,内容也比较少.而且对于文件的读写,还是使用pandas比较好.numpy主要是读写文本数据和二进制数据的. 将数组以二进制的格式保存到硬盘上 主要的函数有numpy.save和nump ...
- root用户无法修改文件权限(lsattr/chattr: i 和 a 属性含义)
今天想在实验室分配的服务器上添加一个普通用户, 所以用root身份登录服务器后执行useradd命令,却提示无法读写 /etc/shadow文件; ls -l /etc/shadow发现什么权限都没有 ...
- Ubuntu16.04下编译android6.0源码
http://blog.csdn.net/cnliwy/article/details/52189349 作为一名合格的android开发人员,怎么能不会编译android源码呢!一定要来一次说编译就 ...
- Nginx配置指令的执行顺序
rewrite阶段 rewrite阶段是一个比较早的请求处理阶段,这个阶段的配置指令一般用来对当前请求进行各种修改(比如对URI和URL参数进行改写),或者创建并初始化一系列后续处理阶段可能需要的Ng ...
- Sublime Text3 打开文档乱码
一.安装包管理器使用Ctrl+~快捷键或者通过View->Show Console菜单打开命令行,粘贴如下代码 import urllib.request,os; pf = 'Package C ...