并发笔记

并发

synchronized

同一时刻只能一个线程执行代码块

可以修饰方法和代码块

如何实现可见性问题

  • 实现可见性的过程

    • 获取互斥锁

    • 清空本地代码,将主内存中的最新拷贝到本地内存

    • 将更改后共享变量值刷新到主内存

    • 释放互斥锁

如何实现同步

  • 都是使用mointorenter和monitorexit两个JVM指令实现

  • 什么是管程

    • 管理共享变量以及对共享变量操作过程,使得支持并发

    • 线程可以对monitor执行lock和unlock操作进行加锁和释放锁

    • 解决互斥问题的思路:将共享变量及对共享变量的操作统一封装起来

锁优化

  • 同步锁的四种状态:无锁、偏向锁、轻量级锁、重量级锁

  • 偏向锁:一个线程加锁

  • 轻量级锁:两个线程交替自旋

  • 同步锁锁定资源是对象

volatile

可见性:对变量的修改对所有线程可见

  • 可见性

    • volatile在写操作的时候,JVM会发一条lock前缀的指令,将这个缓存的变量会写到系统主存中;其他的使用的时候会从主存读取最新的数据。所以可见

内存屏障(Memory Barrier)是CPU的一种指令,用于控制特定条件下的重排序和内存可见性问题。

Java编译器会根据内存屏障的规则禁止重排序

  • 为了保障volatile变量的可见性和禁止指令重排序,java在字节码中插入内存屏障实现

    • 内存屏障解决指令重排

有序性:禁止指令重排,遵循happens-before原则

双重检验锁必须加volatile,因为内存屏障

  • 双重校验锁实现一个单例

  • 否则会出现空指针

存在问题

  • 不满足原子性

    • 解决方法

      • 使用syn

      • 使用可重入锁

      • 使用原子类

与syn的区别

  • volatile不需要加锁,不会阻塞线程

  • volatile是一种简单的同步机制

JUC

锁的分类

CAS:一条CPU的原子指令,可以保证共享变量修改的原子性

  • 使用unsafe类实现,其中都是native方法

  • 根据内存偏移量找到待更新的原值的准确内存地址,使用compareAndSwaplant将待更新的值和预期值进行比较

  • CAS缺陷

    • 循环时间太长

    • 只能保证一个共享变量原子操作

    • ABA问题

      • 解决方案:AtomicStampedReference(加个时间作为版本号)

问题:Thread 的join方法

与syn的比较

  • syn在以下情况下释放锁

    • 线程执行完释放

    • 线程执行时发生异常,JVM会自动释放

    • 锁方法执行了wait方法,进行释放锁

  • syn的问题

    • 无法控制阻塞时长——>JUC trylock()解决

    • 阻塞不可中断——>lockInterruptibly解决

    • syn不支持读写锁分离——>JUC的ReentrantReadWriteLock锁

AQS

  • 先进先出队列+CAS+volatile

    • 维护一个volatile的int类型的state变量,state=1是获取到锁;state的值变化是由CAS完成的
  • 实现

    • CAS操作提供原子性避免锁

    • volatile确保修改的可见性和内存操作的有序性

ThreadLocal

解决并发问题,在线程中传递数据

通过为每一个线程创建一份共享变量的副本保证各个线程之间的变量访问和修改互不影响

内存泄漏问题

  • 为什么

    • key ThreadLocal的引用缘

      • 栈上的ThreadLocal引用

      • ThreadLocalMap中的key对他的引用

    • value

      • 引用只有一条,从Thread过来的引用
    • 出现的问题

      • ThreadLocal栈上的引用不见了,但是Threadlocal对象因为还有一个引用,索引无法回收

        • 解决方法:ThreadLocal的key改成弱引用
      • Thread对象一直被使用,无法释放

        • 解决方法:对于value,Thread一直没有释放,只有在一个ThreadLocal用完的时候,手动调用一下remove方法
  • 解决

 wechat
天生我才必有用