内置锁(三)synchronized的几个要注意的对象监视器

前言

?? 经过前面的两篇文章的介绍,可以清楚知道,synchronized可以用于修饰一个方法 或者 代码块,线程要访问这些临界区代码,则要先获取对应的 对象监视器 ,从而使多个线程互斥访问临界区。

?? 然而,区别是不是同一个对象监视器,是根据对象监视器的内存地址是否一样。这就意味着,想要某些线程在同一个 对象监视器 上竞争临界区代码,那么就必须保证他们获取的对象监视器是同一个。

?? 如果使用以下的几个类作为对象监视器,那么你要注意了,你的对象监视器可能在你没察觉的情况下改变了,出现了意想不到的结果。

一、几个需要注意的类:

1、String 类

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

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

自动装箱(autoboxing):把一个基本数据类型直接赋给对应的包装类变量, 或者赋给 Object 变量;

自动拆箱:把包装类对象直接赋给一个对应的基本类型变量;

同样,JVM 为了节省内存,当满足以下情况,包装类所对应的基本类型的值相同,将使用同一个对象

,如果包装类对应的基本类型的值不相同,则是不同的对象:

1) 是通过装箱创建的对象,而非构造方法创建的;

2)在int 及 int 以下的 基本类型 ,且 其值实际存储空间 不能超过 一个字节。这样的基本类型所对应的包装类型;

换句话说:就是Byte 、Boolean、Char(0~127范围的字符)、Short (基本类型值:-128~127)、Integer(基本类型值:-128~127) 五种有限制的包装类型;

注意:

  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 修饰,保证一直都是同一个对象监视器。

原文地址:https://www.cnblogs.com/jinggod/p/8491074.html

时间: 2024-08-29 16:35:41

内置锁(三)synchronized的几个要注意的对象监视器的相关文章

012 内置锁和synchronized

一 . 概述 在前面我们说到线程安全性问题解决的核心就是同步,同步的核心就是保证原子性. 在java之中最早就支持语法层面的同步解决了,并且提供了synchronized的方式解决问题. 二 .内置锁 在java之中每一个对象都是一个内置锁,这个在JVM的体系之中就规定好了. 内置锁的规定也就决定我们可以拿任意的对象进行同步操作. 内置锁常常配合synchronized使用. 三 .synchronized 该关键词的作用是同步,需要配合内置锁进行使用. 常见的synchronized的使用方式

Java 并发:内置锁 Synchronized

摘要: 在多线程编程中,线程安全问题是一个最为关键的问题,其核心概念就在于正确性,即当多个线程访问某一共享.可变数据时,始终都不会导致数据破坏以及其他不该出现的结果.而所有的并发模式在解决这个问题时,采用的方案都是序列化访问临界资源 .在 Java 中,提供了两种方式来实现同步互斥访问:synchronized 和 Lock.本文针对 synchronized 内置锁 详细讨论了其在 Java 并发 中的应用,包括它的具体使用场景(同步方法.同步代码块.实例对象锁 和 Class 对象锁).可重

内置锁(二)synchronized下的等待通知机制

一.等待/通知机制的简介 线程之间的协作: ??为了完成某个任务,线程之间需要进行协作,采取的方式:中断.互斥,以及互斥上面的线程的挂起.唤醒:如:生成者--消费者模式.或者某个动作完成,可以唤醒下一个线程.管道流已准备等等: 等待/通知机制: ? ?等待/通知机制 是线程之间的协作一种常用的方式之一,在显示锁Lock 和 内置锁synchronized都有对应的实现方式. 等待/通知机制 经典的使用方式,便是在生产者与消费者的模式中使用: 1.生产者负责生产商品,并送到仓库中存储: 2.消费者

jvm内置锁synchronized不能被中断

很久没看技术书籍了,今天看了一下<七周七并发模型>前面两章讲的java,写的还是有深度的.看到了一个有demo,说jvm内置锁synchronized是不能被中断的.照着书上写了个demo,验证了一下,是不能被中断 /** * @Author: * @Description: jdk内置锁不能被中断 * @Date: Created in : 2018/10/4 下午11:34 **/ public class Uninterruptible { private static final Ob

java 并发——内置锁

坚持学习,总会有一些不一样的东西. 一.由单例模式引入 引用一下百度百科的定义-- 线程安全是多线程编程时的计算机程序代码中的一个概念.在拥有共享数据的多条线程并行执行的程序中,线程安全的代码会通过同步机制保证各个线程都可以正常且正确的执行,不会出现数据污染等意外情况. 文字定义总是很含糊,举个反例就很清楚了,想起之前总结过单例模式,就从单例模式开始吧.如果不清楚单例模式的新同学,可以看一下这篇总结: java中全面的单例模式多种实现方式总结 单例模式中,懒汉式的实现方案如下: public c

转:【Java并发编程】之一:可重入内置锁

每个Java对象都可以用做一个实现同步的锁,这些锁被称为内置锁或监视器锁.线程在进入同步代码块之前会自动获取锁,并且在退出同步代码块时会自动释放锁.获得内置锁的唯一途径就是进入由这个锁保护的同步代码块或方法. 当某个线程请求一个由其他线程持有的锁时,发出请求的线程就会阻塞.然而,由于内置锁是可重入的,因此如果摸个线程试图获得一个已经由它自己持有的锁,那么这个请求就会成功."重入"意味着获取锁的操作的粒度是"线程",而不是调用.重入的一种实现 方法是,为每个锁关联一个

《java并发编程实战》读书笔记1--线程安全性,内置锁,重入,状态

什么是线程安全? 当多个线程访问某个类时,不管这些的线程的执行顺序如何,并且在主调代码中不需要任何额外的同步或协同,这个类都能表现出正确的行为,那么就称这个类是线程安全的. 哈哈书上的解释,还是翻译过来的,看了半天还是觉得有点奇怪.比如说 "类都能表现出正确的行为" 是毛线意思?在网上搜了一番 "线程安全就是说多线程访问同一代码,不会产生不确定的结果" 这样反而跟容易理解,果然读书要么读原版中文书要么读原版英文书,看英文的翻译版真的是蛋疼无比.说到这里,我以前貌似把

Java并发编程(1):可重入内置锁

每个Java对象都可以用做一个实现同步的锁,这些锁被称为内置锁或监视器锁.线程在进入同步代码块之前会自动获取锁,并且在退出同步代码块时会自动释放锁.获得内置锁的唯一途径就是进入由这个锁保护的同步代码块或方法. 当某个线程请求一个由其他线程持有的锁时,发出请求的线程就会阻塞.然而,由于内置锁是可重入的,因此如果摸个线程试图获得一个已经由它自己持有的锁,那么这个请求就会成功."重入"意味着获取锁的操作的粒度是"线程",而不是调用.重入的一种实现方法是,为每个锁关联一个获

使用ReentrantLock和Condition来代替内置锁和wait(),notify(),notifyAll()

使用ReentrantLock可以替代内置锁,当使用内置锁的时候,我们可以使用wait() nitify()和notifyAll()来控制线程之间的协作,那么,当我们使用ReentrantLock的时候,我们怎么来处理线程之间的写作呢? JDK5.0为我们提供了Condition对象来替代内置锁的 wait(),notify()和notifyAll()方法 内置锁的话,就只能有一个等待队列,所有的在某个对象上执行wait()方法的线程都会被加入到该对象的等待队列中去(线程会被挂起),需要其他的线