美文网首页
线程安全

线程安全

作者: jianshujoker | 来源:发表于2020-02-16 19:36 被阅读0次

内存模型

物理硬件和内存

一个计算器有多个CPU,每个CPU有多核,CPU在寄存器上执行速度远大于访问主存执行速度,所以有了高速缓存


物理内存.png

除了增加高速缓存,为了使处理器内部运算单元尽可能被充分利用,处理器还会对输入的代码进行乱序执行优化,但会保证结果与顺序执行的结果一致

Java内存模型

[1]是java虚拟机规范定义的,用来屏蔽掉java程序在各种不同的硬件和操作系统对内存的访问的差异,在各种不同的平台上都能达到内存访问的一致性。主要目标是定义程序中[2]的访问规则,即在虚拟机中将变量存储到主内存或者将变量从主内存取出这样的底层细节

内存映射.png

线程状态

线程运行状态.png

线程安全

当多个线程访问某个类时,这个类始终能表现出正确的行为

线程不安全原因

  • 可见性:一个变量修改了共享变量的值,其他线程能否立即知道这个修改
    • 原因:线程会拥有一份自己的从主内存复制的变量
  • 有序性:程序执行时,进行指令重排,重排后指令和原指令顺序未必一致
    • 原因:处理器会对代码执行乱序执行优化
  • 原子性:一个操作是不可中断的
    • 原因:代码中的一行代码,可能是分几步执行的,比如i++

编写线程安全的代码

其核心在于控制对共享、可变变量的访问。当多个线程访问可变的共享变量,特别有写操作时,必须采用同步机制协调对变量的访问

  • 不在线程间共享状态变量
  • 共享的状态变量为不可变
  • 访问可变共享变量时使用同步

同步机制

synchronized

内置锁(Intrinsic Lock)或监视锁(Minior Lock),他属于独占式的 [3],同时属于 [4]

作用范围

1.作用于实例方法:锁住的是对象实例
2.作用于静态方法:锁住的是Class实例(Class实例是共享的,相当于全局锁,会锁住所有调用该方法的线程)
3.指定加锁对象:锁住的是所有以该对象为锁的代码块

核心组件

1.Wait Set:调用wait方法阻塞的线程放置处
2.Contention List:竞争队列,所有通过synchronized请求锁的线程首先被放置在这里
3.Entry List:Contention List中有资格成为候选资源的移动到Entry List
4.OnDeck:竞争锁资源线程,只能有一个竞争锁线程
5.Owner:已经获取到锁资源的线程

实现

sync核心组件.png

1.JVM每次从队列尾部取一个用于OnDeck,但并发情况下Contention List会被大量CAS访问,为了降低对尾部元素竞争,JVM会将一部分线程移到Entry List作为候选竞争线程
2.在Owner unlock时,发现Entry List没有线程,会将Contention List部分线程移到Entry List,并制定Entry List中某个线程为OnDeck
3.处于 ContentionList、EntryList、WaitSet 中的线程都处于阻塞状态
4.Synchronized 是非公平锁。 Synchronized 在线程进入 ContentionList 时,会先尝试自旋获取锁,如果获取不到就进入 ContentionList

CAS

CAS(Compare And Swap/Set)比较并交换,它的算法过程是这样:CAS(V,E,N)包含三个参数

  • V:要更新的变量
  • E:旧值
  • N:新值
    仅当V=E的时候,将V更新为N,最后返回V的真实值

CAS抱着乐观的态度操作(乐观锁),总是认为自己能够成功。当多个线程操作一个变量时,只有一个会成功,其余均会失败,失败的线程可以进行下次操作也可以放弃。基于这样的操作,CAS即使没有锁也能排除其他线程影响,做出恰当的操作

AQS

AbstractQueuedSynchronizer抽象同步队列,它定义了一套多线程访问共享资源的同步器框架,许多同步类实现都依赖于它,比如CountDownLatch、Semaphore、ReentrantLock等

核心

aqs.png

它维护了volatile int state和一个FIFO的等待队列,其中对state的操作有三个

  • getState
  • setState
  • compareAndSetState

实现

AQS使用了模板方法模式,已在AQS实现了大部分功能,但具体资源的获取/释放方式需要由自定义同步器去实现
1.boolean isHeldExclusively():该线程是否在独占该资源,用到condition才需要去实现它
2.boolean tryAcquire(int arg):独占方式。尝试获取资源,如果成功返回true,失败返回false
3.boolean tryRelease(int arg):独占方式。尝试释放资源,如果成功返回true,失败返回false
4.int tryAcquireShared(int arg):共享方式。尝试获取资源,负数失败;0成功,但没有剩余资源;正数成功,且有剩余资源
5.boolean tryReleaseShared(int arg):共享方式。尝试释放资源,如果释放后允许唤醒后续等待节点true,否则false

ReentrantLock举例

重入锁类图.png

ReentrantLock为独占锁,他拥有一个Sync(继承AbstractQueuedSynchronizer)变量,而Sync又有两个实现,分别是FairSync(公平)、NonfairSync(非公平),下面就lock和unlock结合源码分析

lock
        //NonfairSync中lock实现,ReentrantLock中公平与非公平体现在lock的时候是否会先去CAS一次
        final void lock() {
            //CAS尝试获取资源,FairSync是直接调用acquire
            if (compareAndSetState(0, 1))
                setExclusiveOwnerThread(Thread.currentThread());
            else
                //AQS中实现方法
                acquire(1);
        }

AbstractQueuedSynchronizer中方法acquire

    //获取资源
    public final void acquire(int arg) {
        //第一步尝试获取资源,由同步器自己定义
        if (!tryAcquire(arg) &&
            //addWaiter先加入等待队列,再从等待队列中拿线程去获取资源
            acquireQueued(addWaiter(Node.EXCLUSIVE), arg))
            //在等待的队列有可能被调用了中断操作,告知线程调用了中断操作
            selfInterrupt();
    }

ReentrantLock->FairSync中方法tryAcquire

        protected final boolean tryAcquire(int acquires) {
            //获取当前线程
            final Thread current = Thread.currentThread();
            //获取资源状态
            int c = getState();
            //状态为0,表明没有线程占有资源
            if (c == 0) {
                //
                if (!hasQueuedPredecessors() &&
                    //CAS设置值
                    compareAndSetState(0, acquires)) {
                    //设置拥有线程为当前线程
                    setExclusiveOwnerThread(current);
                    //返回获取资源成功
                    return true;
                }
            }
            //当前线程为拥有线程
            else if (current == getExclusiveOwnerThread()) {
                //重入锁加上值
                int nextc = c + acquires;
                //检查不是无效的值
                if (nextc < 0)
                    throw new Error("Maximum lock count exceeded");
                //重入锁只有一个线程,直接设置值    
                setState(nextc);
                //返回获取资源成功
                return true;
            }
            //获取资源失败
            return false;
        }

AbstractQueuedSynchronizer中方法acquireQueued

    final boolean acquireQueued(final Node node, int arg) {
        //是否取消获取的标志位
        boolean failed = true;
        try {
            //是否有中断
            boolean interrupted = false;
            //循环尝试获取资源
            for (;;) {
                //获取前置节点
                final Node p = node.predecessor();
                //前置节点为头节点并且判断是否获取资源成功
                if (p == head && tryAcquire(arg)) {
                    //获取资源成功后,设置当前节点为头结点,当前线程已获取到资源,不需要在存在等待队列中
                    setHead(node);
                    p.next = null; // help GC
                    //标志位改为false
                    failed = false;
                    //返回是否有中断
                    return interrupted;
                }
                //判断是否应该park
                if (shouldParkAfterFailedAcquire(p, node) &&
                    //park阻塞,并且检查是否有其他线程中断当前线程
                    parkAndCheckInterrupt())
                    //有其他线程对当前线程发起中断
                    interrupted = true;
            }
        } finally {
            //取消
            if (failed)
                //取消获取资源
                cancelAcquire(node);
        }
    }
unlock

ReentrantLock解锁

    public void unlock() {
        //调用AQS的实现类的release
        sync.release(1);
    }

AbstractQueuedSynchronizer中方法release

    public final boolean release(int arg) {
        //同步器自己实现的tryRelease
        if (tryRelease(arg)) {
            //从等待队列中拿一个线程unpark
            Node h = head;
            if (h != null && h.waitStatus != 0)
                unparkSuccessor(h);
            return true;
        }
        return false;
    }

ReentrantLock->Sync中方法tryRelease

        protected final boolean tryRelease(int releases) {
            //减去release释放的资源,重入锁可以减多次
            int c = getState() - releases;
            //检测当前线程是占有资源线程
            if (Thread.currentThread() != getExclusiveOwnerThread())
                throw new IllegalMonitorStateException();
            //是否已没有线程占有资源    
            boolean free = false;
            //为0时表示没有线程占有资源
            if (c == 0) {
                free = true;
                setExclusiveOwnerThread(null);
            }
            //设置资源值为此次释放后的值
            setState(c);
            return free;
        }

参考

  • 多线程&线程安全

  1. JavaMemoryModel,JMM

  2. java程序中堆上的变量

  3. 总是假设会发生冲突

  4. 获得锁的线程可以再次获得锁,比如方法a、b都获得同一个锁,在方法a中调用b

相关文章

  • atomic & nonatomic

    什么是线程安全??? 线程安全:多线程操作共享数据不会出现想不到的结果就是线程安全的,否则,是线程不安全的。 at...

  • ConcurrentHashMap源码设计分析

    二、线程安全(Thread-safe)的集合对象:● Vector 线程安全● HashTable 线程安全● S...

  • HashMap 和 Hashtable 的区别

    线程安全: HashMap 是非线程安全的,而 Hashtable 是线程安全的,因为 Hashtable 内部的...

  • Java 的 StringBuffer 和 StringBuil

    区别就是:线程安全,StringBuffer 是线程安全的,StringBuilder 不是线程安全的。 他俩的实...

  • Java单例模式,线程安全

    懒汉式:线程安全,开销大 双重检查锁:线程安全,根据需求使用 静态内部类锁:线程安全,比较推荐 饿汗式:线程安全,...

  • 2018-06-12 第三十七天

    一、线程安全 线程安全的问题,是针对多线程的程序。单线程的情况下,是不存在线程安全问题。 产生线程安全问题的原因:...

  • 线程安全知多少

    1. 如何定义线程安全 线程安全,拆开来看: 线程:指多线程的应用场景下。 安全:指数据安全。 多线程就不用过多介...

  • JAVA 线程安全

    线程安全定义 一个类在可以被多个线程安全调用时就是线程安全的。 线程安全分类 线程安全不是一个非真即假的命题,可以...

  • synchronized锁

    一、线程安全的概念与synchronized 1、线程安全概念 并发程序开发的一大关注重点就是线程安全,线程安全就...

  • 线程安全的NSMutableDictionary

    NSDictionary是线程安全的,NSMutableDictionary是线程不安全的。利用锁来保证线程的安全...

网友评论

      本文标题:线程安全

      本文链接:https://www.haomeiwen.com/subject/haymfhtx.html