Java多线程核心技术2 - 对象和变量的并发访问

本文深入探讨Java多线程中对象和变量的并发访问问题,重点讲解synchronized和volatile关键字。synchronized确保方法或代码块的互斥访问,具备可重入性并自动释放锁,但不保证原子性。volatile则保证变量的可见性,防止指令重排序,适用于多线程共享读取场景,但不保证原子操作。理解这些概念对于优化并发程序至关重要。
摘要由CSDN通过智能技术生成

来自阅读Java多线程编程核心技术的读书笔记,按照自己思路写了一些整理
历史内容:Java多线程核心技术1 - 多线程技能

2. 对象和变量的并发访问

2.1 为什么产生线程安全问题

非线程安全是发生在多个线程同一个对象中的实例变量的访问。若多个线程对同一个对象的实例变量访问,可能产生脏读的后果,即读到的数据是被修改过的。但是,不是所有变量和对象访问都有问题

  • 方法内的变量(私有变量)不存在线程安全问题
  • 实例变量(对象中变量)存在线程安全问题

2.2 synchronized 同步方法

  • 同步方法写法:synchronized public void methodB() { ... }
    • 相当于对拥有此方法的对象本身加锁,锁是这个对象(称为object)
    • A线程先持有object对象的锁,B线程若想持有则需要等待

synchronized 可重入、继承可重入

  • synchronized关键字具有可重入功能:即使用synchronized后,当一个线程得到对象锁,其再次请求对象锁一定可以获得

  • 即在一个synchronized方法/块内部调用本类其他synchronized方法/块,永远可以得到锁

    • 若不可锁重入,则引发死锁
  • 可重入锁对父子类继承有效:

    • 当父子类存在继承关系时,子类完全可以通过可重入锁调用父类的同步方法

      // 父类
      public class Main {
        public int i = 10;
        synchronized public void operateIMainMethod() {
          i--; System.out.println("main print i="+i);
          Thread.sleep(100);
        }
      }
      // 子类
      public class Sub extends Main {
        synchronized public void operateISubMethod() {
          i--; System.out.println("sub print i="+i);
          Thread.sleep(100);
          this.operateIMainMethod();	// 可以重入
        }
      }
      

      运行结果:

      image-20210122235633484

异常自动释放

  • 当一个线程出现异常时,锁被自动释放

同步不具有继承性

  • 父类某方法synchronized,若子类Override这个方法且不加synchronized,则不会具有同步性。必须显式在子类方法中添加才行!

2.3 synchronized 同步语句块

  • synchronized声明同步方法的弊端:

    • 调用同步方法执行一个长时间的任务,整个执行方法的过程中都将占用锁
    • 使用synchronized语句块,减少同步块体积
  • 使用格式

    try {
      synchronized (this) {
    		// ...
      } catch (InterruptedException e) {
        e.printStackTrace();
      }
    }
    

对象监视器

  • 之前使用synchronized(this)作为同步代码块,则使用this作为对象监视器
  • Java同样还支持对“任意对象”作为监视器实现同步功能;同样的,同一时间内,只有一个线程可以进入synchronized(x) { ... }包裹的临界区中
  • 3个结论
    • 当多个线程同时执行synchronized(x) {}同步代码块时呈同步效果
    • 当其他线程执行x对象synchronized同步方法呈同步效果
    • 当其他线程执行x**对象方法**里面synchronized (this)代码块时也就呈现同步效果

静态同步 synchronized 方法 & synchronized(class) 方法块

  • synchronzied用在static静态方法上时,加锁对象是对应的class对象

    public class Service {
      // 对类对象加锁,不会影响到其他锁
      synchronized public static void printA() {
        try {
          // ...
        } catch (InterruptedException e) {
          e.printStackTrace();
        }
      }
    }
    

String 常量池特性与synchronized

  • 要注意的是,Java中String使用常量池,相同值的String引用指向的可能是常量池中相同的对象

    // 同步方法
    public static void print(String stringParam) {
      try {
        // 对stringParam - synchronzied
        // 在不同的线程中调用 print("aa") 
        // 可能被锁到线程池中的同一个对象
        synchronized (stringParam) {
          while (true) { 
          	// ...
          }
        }
      } catch (InterruptedException e) {
        e.printStackTrace();
      }
    }
    

2.4 volatile 关键字

  • volatile关键字作用:保证变量可见性;禁止指令重排序

    img
    • 保证此变量对所有的线程的可见性,这里的“可见性”,如本文开头所述,当一个线程修改了这个变量的值,volatile 保证了新值能立即同步到主内存,以及每次使用前立即从主内存刷新。但普通变量做不到这点,普通变量的值在线程间传递均需要通过主内存(详见:Java内存模型)来完成。

    • 禁止指令重排序优化。有volatile修饰的变量,赋值后多执行了一个“load addl $0x0, (%esp)”操作,这个操作相当于一个内存屏障(指令重排序时不能把后面的指令重排序到内存屏障之前的位置),只有一个CPU访问内存时,并不需要内存屏障;(什么是指令重排序:是指CPU采用了允许将多条指令不按程序规定的顺序分开发送给各相应电路单元处理)。

保证变量可见性

  • -server环境中,私有堆栈中的值和公共堆栈的值不同步(为了提升线程运行效率,读取将预先读取工作内存中的值)

  • 启动volatile关键字后,将强制从公共内存中读取变量的值

    image-20210123135456140

非原子性特征

  • volatile关键字增加了实例变量在多个线程之间的可见性,但是不支持原子性

  • 如果在方法/代码块中声明了synchronzied,则不再需要volatile

    • volatile的主要使用场合是:多个线程**共享读取**变量,希望获取到最新的值
  • 下图展示变量在内存中的工作过程:

    image-20210123144301884
    • 可以得出结论:
      1. read和load阶段:主存复制变量到当前工作内存
      2. use和assign阶段:执行代码、改变共享变量值
      3. store和write阶段:用工作内存的数据刷新主存对应变量值
    • 对于Volatile修饰的变量,JVM虚拟机只保证从主内存加载到线程工作内存的值是最新的

volatile vs synchronized

  • 比较synchronizedvolatile关键字

    1. 关键字volatile是线程同步的轻量级实现,只能修饰变量;synchronized可以修饰方法和代码块;
    2. 多线程访问volatile不会发生阻塞;但synchronzied会阻塞
    3. volatile保证数据的可见性、不保证数据的原子性
      synchronized既保证原子性、又保证可见性,因为会在私有内存与公共内存之间做数据同步
  • synchronized代码块有volatile同步的功能

  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值