内置锁(三)synchronized的几个要注意的对象监视器
前言
经过前面的两篇文章的介绍,可以清楚知道,synchronized可以用于修饰一个方法 或者 代码块,线程要访问这些临界区代码,则要先获取对应的 对象监视器 ,从而使多个线程互斥访问临界区。
然而,区别是不是同一个对象监视器,是根据对象监视器的内存地址是否一样。这就意味着,想要某些线程在同一个 对象监视器 上竞争临界区代码,那么就必须保证他们获取的对象监视器是同一个。
如果使用以下的几个类作为对象监视器,那么你要注意了,你的对象监视器可能在你没察觉的情况下改变了,出现了意想不到的结果。
一、几个需要注意的类:
1、String 类
大家都知道,为了节省内存,JVM中为String类维持了一个常量池,一旦String的对象值改变时,就会替换成新的对象实例,这个不多说。
2、五个基本类型的自动装箱的情况:
自动装箱(autoboxing):把一个基本数据类型直接赋给对应的包装类变量, 或者赋给 Object 变量;
自动拆箱:把包装类对象直接赋给一个对应的基本类型变量;
同样,JVM 为了节省内存,当满足以下情况,包装类所对应的基本类型的值相同,将使用同一个对象
,如果包装类对应的基本类型的值不相同,则是不同的对象:
1) 是通过装箱创建的对象,而非构造方法创建的;
2)在int 及 int 以下的 基本类型 ,且 其值实际存储空间 不能超过 一个字节。这样的基本类型所对应的包装类型;
换句话说:就是Byte 、Boolean、Char(0~127范围的字符)、Short (基本类型值:-128127)、Integer(基本类型值:-128127) 五种有限制的包装类型;
注意:
- String 常量池的由编译时字面常量生成的,如果想直接创建一个不是维护在常量池的新对象,使用构造方法new String()便可!
- 当Short、Integer类的对象所对应的基本类型的值 超过一个字节范围,如 129,那么 无论是装箱,还是用构造方法,将会是独立的新对象,不是维护在常量池中。
Integer a = 2;
Integer b = 2;
//a 与 b 都是经过自动装箱的机制得到的,所对应的基本类型值为2,值相等,所以都是指向同一个对象
if(a==b){
System.out.println("a与b的内存地址相等,是同一个对象");
}
//c 是通过构造方法得到的,所以是直接创建一个对象,而不是使用常量池中的值
Integer c = new Integer(2);
if(a != c){
System.out.println("a与c的内存地址不想等,不是同一个对象");
}
二、异常情况举例
欸!说了这么多,该进入正题,如果使用了以上所说的类作为对象监视器,可能出现哪些意外的情况呢?下面列举两种常见情况:
例一:导致在不是对象监视器上调用 wait、notify方法
Integer in = 3;
synchronized (in) {
//自动装箱得到新的值,即in指向了新的对象了,不是指向对象监视器
in += 3;
try {
//in 已经是新的对象,而调用wait()方法前,必须获取对象监视器,否则抛出异常
in.wait(1000);
} catch (InterruptedException e) {
e.printStackTrace();
}
}
运行结果:
例二:预期协作的几个线程不是使用同一个对象监视器,导致协作失败
public static void main(String[] args) {
Integer b = 2;
Thread thread_1 = new Thread("thread_1"){
@Override
public void run() {
synchronized (b) {
if(b<10){
try {
b.wait();
} catch (InterruptedException e) {
e.printStackTrace();
}
}
System.out.println("线程thread_1运行结束,b的值是:"+b);
}
}
};
thread_1.start();
Thread thread_2 = new Thread(new MyRunable(b),"thread_1");
thread_2.start();
}
class MyRunable implements Runnable{
Integer b;
public MyRunable(Integer b){
//再次自动装箱来修该值,那么b就指向新的对象
this.b = b+10;
}
@Override
public void run() {
//线程1、2的对象监视器已经不一样了,所以,线程2将无法按照预期唤醒线程1
synchronized (b) {
b.notify();
System.out.println("线程thread_2运行结束!");
}
}
}
运行结果:
线程2正常结束,线程1无法被唤醒
总结
- 这些类比较特殊,需要谨慎使用的原因,就是因为JVM为它们维护了一个常量池,在你以为是改变值的时候,其实已经换了一个新的对象,导致对象监视器前后不一致;
- 安全使用这些类的方法,便是设为常量,用 final 修饰,保证一直都是同一个对象监视器。
内置锁(三)synchronized的几个要注意的对象监视器的更多相关文章
- 深入理解Java内置锁和显式锁
synchronized and Reentrantlock 多线程编程中,当代码需要同步时我们会用到锁.Java为我们提供了内置锁(synchronized)和显式锁(ReentrantLock)两 ...
- 七 内置锁 wait notify notifyall; 显示锁 ReentrantLock
Object中对内置锁进行操作的一些方法: Java内置锁通过synchronized关键字使用,使用其修饰方法或者代码块,就能保证方法或者代码块以同步方式执行. 内置锁使用起来非常方便,不需要显式的 ...
- Java内置锁synchronized的实现原理及应用(三)
简述 Java中每个对象都可以用来实现一个同步的锁,这些锁被称为内置锁(Intrinsic Lock)或监视器锁(Monitor Lock). 具体表现形式如下: 1.普通同步方法,锁的是当前实例对象 ...
- 内置锁(一)synchronized 介绍与用法
一.synchronized 的介绍 synchronized 是 Java语言的关键字,当它用来修饰一个方法或者一个代码块的时候,能够保证在同一时刻最多只有一个线程执行该段代码,而这段代码也被称 ...
- 内置锁(二)synchronized下的等待通知机制
一.等待/通知机制的简介 线程之间的协作: 为了完成某个任务,线程之间需要进行协作,采取的方式:中断.互斥,以及互斥上面的线程的挂起.唤醒:如:生成者--消费者模式.或者某个动作完成,可以唤醒下一 ...
- Java 并发:内置锁 Synchronized
摘要: 在多线程编程中,线程安全问题是一个最为关键的问题,其核心概念就在于正确性,即当多个线程訪问某一共享.可变数据时,始终都不会导致数据破坏以及其它不该出现的结果. 而全部的并发模式在解决问题时,採 ...
- 深入理解java内置锁(synchronized)和显式锁(ReentrantLock)
多线程编程中,当代码需要同步时我们会用到锁.Java为我们提供了内置锁(synchronized)和显式锁(ReentrantLock)两种同步方式.显式锁是JDK1.5引入的,这两种锁有什么异同呢? ...
- synchronized内置锁
synchronized内置锁,如果发生阻塞,无法被中断,除非关闭jvm.因此不能从死锁中恢复.
- java synchronized内置锁的可重入性和分析总结
最近在读<<Java并发编程实践>>,在第二章中线程安全中降到线程锁的重进入(Reentrancy) 当一个线程请求其它的线程已经占有的锁时,请求线程将被阻塞.然而内部锁是可重 ...
- Java内置锁synchronized的实现原理
简述Java中每个对象都可以用来实现一个同步的锁,这些锁被称为内置锁(Intrinsic Lock)或监视器锁(Monitor Lock). 具体表现形式如下: 1.普通同步方法,锁的是当前实例对象 ...
随机推荐
- GET_DDL提取建表语句:ddl
创建对象的语句就是了 提取表 set line 200 pages 50000 wrap on long 999999 serveroutput on SQL> select dbms_meta ...
- ZOJ2401 Zipper 双塔式 DP(双塔DP)
第二次遇到双塔DP,再写一下. (flag是为了避免memset多次导致的时间浪费) #include<cstdio> #include<cstdlib> #include&l ...
- Mongodb $setOnInsert操作符 和upsert:true
upsert:true:如果要更新的文档不存在的话会插入一条新的记录 $setOnInsert操作符会将指定的值赋值给指定的字段,如果要更新的文档存在那么$setOnInsert操作符不做任何处理: ...
- jboss 5.1 启动问题解决
在安装好后启动时可能遇到这样的情况: ERROR [AbstractKernelController] Error installing to Instantiated: name=Attachmen ...
- BZOJ3757: 苹果树【树上莫队】
Description 神犇家门口种了一棵苹果树.苹果树作为一棵树,当然是呈树状结构,每根树枝连接两个苹果,每个苹果都可以沿着一条由树枝构成的路径连到树根,而且这样的路径只存在一条.由于这棵苹果树 ...
- PHP处理大数据导出Excel方法
在日常的工作中,很多时候都需要导出各种各样的报表,但是如果导出的数据一旦比较大,很容易就导致超时,对于这种问题,有很多的解决方法,例如网上说的分批导出.采用CSV.还有就采用JAVA.甚至是C++和C ...
- Python集成开发环境搭建
===================== 开始学习Python的开发,首先得搭建好集成开发的环境! 分为下面几个步骤: 操作系统平台:Windows XP/7/10 都可以 1.安装并配置JDK运行 ...
- ZH奶酪:隐马尔可夫模型学习小记——forward算法+viterbi算法+forward-backward算法(Baum-welch算法)
网上关于HMM的学习资料.博客有很多,基本都是左边摘抄一点,右边摘抄一点,这里一个图,那里一个图,公式中有的变量说不清道不明,学起来很费劲. 经过浏览几篇博文(其实有的地方写的也比较乱),在7张4开的 ...
- 应用解决告诉你什么时候该用ajax
第一.请求的提交是为了页面数据的显示,这时候用户一般不希望看到页面的刷新,是使用AJAX的一个最佳时候. 第二.如果请求提交后,用户能从页面感觉到提交结果,这时候,也最好不要有页面刷新,推荐使用AJA ...
- HIVE之 Sqoop 1.4.6 安装、hive与oracle表互导
1. sqoop数据迁移 1.1 概述 sqoop是apache旗下一款“Hadoop和关系数据库服务器之间传送数据”的工具. 导入数据:MySQL,Oracle导入数据到Hadoop的HDFS.HI ...