zoukankan      html  css  js  c++  java
  • 内置锁(三)synchronized的几个要注意的对象监视器

    前言

       经过前面的两篇文章的介绍,可以清楚知道,synchronized可以用于修饰一个方法 或者 代码块,线程要访问这些临界区代码,则要先获取对应的 对象监视器 ,从而使多个线程互斥访问临界区。
       然而,区别是不是同一个对象监视器,是根据对象监视器的内存地址是否一样。这就意味着,想要某些线程在同一个 对象监视器 上竞争临界区代码,那么就必须保证他们获取的对象监视器是同一个。
       如果使用以下的几个类作为对象监视器,那么你要注意了,你的对象监视器可能在你没察觉的情况下改变了,出现了意想不到的结果。

    一、几个需要注意的类:

    1、String 类

    大家都知道,为了节省内存,JVM中为String类维持了一个常量池,一旦String的对象值改变时,就会替换成新的对象实例,这个不多说。

    2、五个基本类型的自动装箱的情况:

    自动装箱(autoboxing):把一个基本数据类型直接赋给对应的包装类变量, 或者赋给 Object 变量;
    自动拆箱:把包装类对象直接赋给一个对应的基本类型变量;
    同样,JVM 为了节省内存,当满足以下情况,包装类所对应的基本类型的值相同,将使用同一个对象
    ,如果包装类对应的基本类型的值不相同,则是不同的对象:
    1) 是通过装箱创建的对象,而非构造方法创建的;
    2)在int 及 int 以下的 基本类型 ,且 其值实际存储空间 不能超过 一个字节。这样的基本类型所对应的包装类型;
    换句话说:就是Byte 、Boolean、Char(0~127范围的字符)、Short (基本类型值:-128127)、Integer(基本类型值:-128127) 五种有限制的包装类型;

    注意:

    1. String 常量池的由编译时字面常量生成的,如果想直接创建一个不是维护在常量池的新对象,使用构造方法new String()便可!
    2. 当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 修饰,保证一直都是同一个对象监视器。



  • 相关阅读:
    第四单元博客总结——暨OO课程总结
    OO--第三单元规格化设计 博客作业
    关于博客园主——他死了
    编译错误总集
    密码是我QQ签名
    P1600 天天爱跑步
    天气之子——天空上是另一个世界
    可持久化01trie树——模板
    P1270 “访问”美术馆——不太一样的树形DP
    P1099 树网的核——模拟+树形结构
  • 原文地址:https://www.cnblogs.com/jinggod/p/8491074.html
Copyright © 2011-2022 走看看