Java并发编程(juc理论篇)

68 阅读4分钟

CAS

  • CAS(Compare And Swap),处理器会比较当前内存地址上的值 和 线程中变量的值(预期值)是否相等,如果相等则替换内存地址上的值为新值,如果不相等则不更新,硬件层面上支持使用一条指令来实现上面的操作,x86中是cmpxchgl指令
  • CAS操作不涉及线程上下文切换,并且不使用锁,会提高系统的并发性

1.png

  • ABA问题:一个线程将变量A改为了变量B,之后又改为了变量A,另一个线程在执行CAS操作时会认为该变量没有发生变化,可能会导致错误。解决方案:添加版本号进行对比,可以使用AtomicStampedReference类

Synchorized

  • synchorized修饰实例方法时,锁对象是当前的实例对象;synchorized修饰静态方法时,锁对象是当前的class类对象;synchorized作用于代码块时,锁对象是括号里面的对象
  • synchorized是可重入锁,一个线程可以重复获取到该锁,每获取一次锁,计数器加一,释放锁时,计数器减一,直到计数器为0,锁才会真正释放

锁升级过程:

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

锁栈帧.jpg

锁膨胀

Synchorized和ReentrantLock

  • synchorized和ReentrantLock 都是可重入锁
  • synchorized是非公平锁,不支持条件判断,不支持设置超时时间,不需要手动释放锁
  • ReentrantLock既可以是非公平锁也可以是公平锁(默认是非公平锁),允许设置获取锁的超时时间

ThreadLocal

  • 基本使用

    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对象,ThreadLocalMap以ThreadLocal为key,线程变量副本为value

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

Thread.jpg

Volatile

  • volatile可以保证可见性,当一个线程修改了volatile变量的值后,新值会立即被刷新到主内存中,其他线程在读取该变量时可以立即获取到最新的值,避免由于缓存一致性问题导致 ”看见“旧值的现象
  • volatile底层会加一个lock前缀指令进行总线锁定,总线监听到修改了共享数据后,会让其他处理器中的缓存更新或失效
  • volatile可以禁止 jvm 指令重排,保证在多线程环境下也能正常运行。正常情况:1. 为对象分配内存,2. 初始化,3. 将对象引用指向对应内存地址。指令重排可能变成1 3 2,这样可能会发生问题

1.png

其他内容

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

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

线程安全的方式

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

原子性、可见性、有序性

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

相关文章