java并发编程(juc理论篇)

CAS

  • CAS(Compare And Swap),处理器会比较当前内存地址上的值 和 线程中变量的值(预期值)是否相等,如果相等则替换内存地址上的值为新值,如果不相等则不更新,硬件层面上支持使用一条指令来实现上面的操作,x86中是cmpxchgl指令
  • CAS操作不涉及线程上下文切换,并且不使用锁,会提高系统的并发性
  • ABA问题:一个线程将变量A改为了变量B,之后又改为了变量A,另一个线程在执行CAS操作时会认为该变量没有发生变化,可能会导致错误。解决方案:添加版本号进行对比,可以使用AtomicStampedReference

    java 复制代码
    private static AtomicStampedReference<Integer> atomicStampedReference = new AtomicStampedReference<>(100, 66);
    
    public static void main(String[] args) throws InterruptedException {
        // 获取当前的引用值和时间戳
        int stamp = atomicStampedReference.getStamp();
        Integer value = atomicStampedReference.getReference();
    
        // 修改引用值
        boolean result = atomicStampedReference.compareAndSet(value, value + 10, stamp, stamp + 1);
        System.out.println("result = " + result);
    
        // 输出最终的值
        System.out.println("Final value = " + atomicStampedReference.getReference());
        System.out.println("Final stamp = " + atomicStampedReference.getStamp());
    }

Synchorized

  • synchorized修饰实例方法时,锁对象是当前的实例对象;synchorized修饰静态方法时,锁对象是当前的class类对象;synchorized作用于代码块时,锁对象是括号里面的对象
  • synchorized是可重入锁,一个线程可以重复获取到该锁,每获取一次锁,计数器加一,释放锁时,计数器减一,直到计数器为0,锁才会真正释放
  • synchronized修饰方法时,会在方法的访问标志位中增加一个ACC_SYNCHORIZED标志,当线程访问该方法时,如果包含该标志位,就要获得该方法对应的对象的监视器锁才能执行该方法
  • synchronized修饰代码块时,会在代码块的前后插入monitorentermonitorexit字节码指令

锁升级过程:

  • 在没有锁竞争的情况下,一个线程来加锁,JVM会向锁对象的对象头中写入该线程id (这时候的锁叫偏向锁),后续该线程再进入该锁时,无需进行额外的同步操作
  • 在锁轻度竞争的时候,会升级为轻量级锁,JVM会在当前线程中创建一个栈帧,通过CAS操作将这个栈帧的地址写入对象头中,如果成功表示该线程获取到了锁,如果失败则表示其他线程已经持有该锁,此时会升级为重量级锁
  • 当锁竞争激烈时,JVM会升级为重量级锁,synchorized修改代码块时,线程会尝试获取monitor对象的所有权,抢到了就是成功获取到锁,抢不到线程就会阻塞,等待重新获取锁

Synchorized和ReentrantLock

  • synchorizedReentrantLock 都是可重入锁
  • synchorized是非公平锁,不支持设置超时时间,不支持条件判断,不需要手动释放锁
  • ReentrantLock既可以是非公平锁也可以是公平锁(默认是非公平锁),允许设置获取锁的超时时间,可以通过lock.Condition对象将不满足条件的线程加入到阻塞队列中

ThreadLocal

  • 基本使用

    java 复制代码
    public class UserContext {
        private static ThreadLocal<User> CONTEXT = new ThreadLocal<User>();
    
        public static User getUser() {
            return CONTEXT.get();
        }
    
        public static void setUser(User user) {
            CONTEXT.set(user);
        }
        
        public static void remove() {
            CONTEXT.remove();
        }
    }
  • 每个线程内部维护了一个ThreadLocalMap对象,ThreadLocalMapThreadLocal为key,线程变量副本为value

  • ThreadLocalMap中的key是弱引用,之所以不是强引用是因为所使用的线程往往是线程池中的线程,线程一般不会被销毁,对ThreadLocal的引用就会一直存在,导致内存释放不掉

  • 最佳实践:

    • 存储用户登录信息,让每个线程有独立的用户上下文
    • 管理数据库链接,将一个数据库连接放入ThreadLocal中,在需要使用数据库连接的地方直接从ThreadLocal获取

Volatile

  • volatile可以保证可见性,当一个线程修改了volatile变量的值后,新值会立即被刷新到主内存中,其他线程在读取该变量时可以立即获取到最新的值,避免由于缓存一致性问题导致 "看见"旧值的现象

  • volatile底层会加一个lock前缀指令进行总线锁定,总线监听到修改了共享数据后,会让其他处理器中的缓存更新或失效

  • volatile可以禁止 jvm 指令重排,保证在多线程环境下也能正常运行

    正常情况:1. 为对象分配内存,2. 初始化,3. 将对象引用指向对应内存地址

    指令重排可能变成1 3 2,这样可能会发生问题

AQS

  • AQS是一个抽象类,内部封装了线程 加锁、解锁、入队、出队的一些方法,提供给其他juc锁使用
  • 它内部通过维护一个共享变量state和先进先出FIFO等待队列来控制并发线程,在独占锁中,state为0表示未被占用,1表示已被占用,当线程获取资源失败时,会被加入到AQS的等待队列中
  • 基于AQS实现的类有ReentrantLock, CountDownLatch, Semaphore

其他

死锁:两个线程都想获取对方已经拥有的锁,避免死锁的方法:

  • 避免在一个锁的代码块中再次尝试获取另一个锁
  • 使用尝试获取锁的方法,比如ReentrantLocktrylock方法,SemaphoretryAcquire方法,如果获取不到使用其他方案
  • 减小锁的控制范围

保证线程安全的方式

  • 同步锁:synchorizedReentrantLock
  • 原子操作类:AtomicIntegerAtomicReference
  • 线程安全的容器:ConcurrentHashMap等,避免手动加锁
  • 使用局部变量

一个线程被启动两次,会发生什么:

  • 线程一旦运行就不会回到初始状态,会抛出IllegalThreadStateException异常

上下文切换:

  • 暂停一个线程的处理,并将CPU寄存器中该线程的数据存储在内存中
  • 从内存中获取下一个线程的上下文,并在CPU的寄存器中恢复它
  • 返回到程序计数器指示的位置 继续执行

并发和并行:

  • 并发:线程轮流使用cpu的做法称为并发
  • 并行:多个cpu同时在调度线程执行任务

原子性、可见性、有序性

  • 原子性:一组操作要么全部执行成功,要么全部不执行
  • 可见性:一个线程修改共享变量的值后,其他线程能够立即看到这个修改后的值
  • 有序性:程序的执行顺序和代码的先后顺序一致,但是多线程环境下,编译器会对指令进行重排序,进而出现并发问题

指令重排:

  • 编译器和处理器为了优化性能,对指令执行顺序进行调整,指令重排类型包括:编译器重排和cpu重排。为了避免多线程之间操作出现不同步现象,可以使用volatilesynchorized等来限制这种行为

相关文章

相关推荐
伯明翰java2 小时前
【无标题】springboot项目yml中使用中文注释报错的解决方法
java·spring boot·后端
码界奇点2 小时前
基于Spring Boot和Vue.js的视频点播管理系统设计与实现
java·vue.js·spring boot·后端·spring·毕业设计·源代码管理
程序员根根2 小时前
MySQL 事务全解析:从 ACID 特性到实战落地(部门 - 员工场景)
数据库·后端
回家路上绕了弯2 小时前
分布式事务本地消息表详解:中小团队的低侵入落地方案
分布式·后端
武子康2 小时前
大数据-196 scikit-learn KNN 实战:KNeighborsClassifier、kneighbors 与学习曲线选最优 案例1红酒 案例2乳腺
大数据·后端·机器学习
L Jiawen2 小时前
【Golang基础】基础知识(上)
开发语言·后端·golang
张登杰踩2 小时前
django后台管理配置教程
后端·python·django
卜锦元3 小时前
Golang后端性能优化手册(第四章:异步处理与消息队列)
开发语言·后端·docker·容器·性能优化·golang·团队开发
BD_Marathon3 小时前
Spring是什么
java·后端·spring