跳至主要內容

java基础

HeChuangJun约 1494 字大约 5 分钟

死锁?实例?产生条件?避免?悲观锁和乐观锁?场景?实现?问题及解决方案?

两个或多个进程在执行过程中因争夺资源而相互等待,导致无法继续执行的状态

例子
class DeadLockDemo {
    private static final Object lock1 = new Object();
    private static final Object lock2 = new Object();

    public static void main(String[] args) {
        new Thread(() -> {
            synchronized (lock1) {
                System.out.println("线程1获取到了锁1");
                try {
                    Thread.sleep(1000);
                } catch (InterruptedException e) {
                    e.printStackTrace();
                }
                synchronized (lock2) {
                    System.out.println("线程1获取到了锁2");
                }
            }
        }).start();

        new Thread(() -> {
            synchronized (lock2) {
                System.out.println("线程2获取到了锁2");
                try {
                    Thread.sleep(1000);
                } catch (InterruptedException e) {
                    e.printStackTrace();
                }
                synchronized (lock1) {
                    System.out.println("线程2获取到了锁1");
                }
            }
        }).start();
    }
}

互斥:资源每次只能由一个线程使用。其他请求该资源的线程必须等待,直到资源被释放
请求和等待:一个线程至少已经持有至少一个资源的同时还请求被其他线程占有的资源
不可剥夺:资源不能被强制从一个线程中抢占过来,只能由持有资源的线程主动释放
循环等待:存在一个进程等待资源的循环,进程集合中进程A等待进程B的资源,……,最后进程Z等待进程A的资源,形成环形依赖

破坏一个死锁产生条件
破坏互斥:不可行,因为加锁就是为了互斥
破坏持有并等待:要求线程在开始执行前一次性地申请所有需要的资源
破坏非抢占条件:占用部分资源的线程进一步申请其他资源时,申请不到则释放它占有的资源
破坏循环等待条件:对所有资源类型进行排序,强制每个线程按顺序申请资源

悲观锁PessimisticConcurrencyControl:认为每次访问共享资源时会发生冲突,所以~时都会加锁,保证同一时刻只有一个线程能~。适用于读少写多

数据库的行锁,表锁,读锁,写锁等.synchronized和ReentrantLock。CAS自旋的概率大,消耗CPU资源,效率低于synchronized

乐观锁Optimistic~:认为每次访问共享资源时不会发生冲突,所以不会加锁,只有在提交时才进行冲突检测。如果没有冲突,操作执行成功;如果有冲突则重试直到成功。适用于读多写少

数据库的write_condition机制,atomic包的原子变量类

版本号机制:数据表加上版本号version字段表示数据被修改次数,当数据被修改时version加1。当更新数据时前读取version值,在更新时version值与数据库中的version值相等时才成功,否则重试更新操作直到成功

CAS算法compare and swap比较与交换,原子操作,非阻塞同步。包括三个操作数:内存值V;预期值A;新值B;当V=A时,通过原子方式V更新为新值B,否则不会执行任何操作

CAS不需要进入内核、切换线程,操作自旋几率少,因此高性能,高吞吐量。通过处理器的指令来保证操作的原⼦性。Unsafe由C++实现的。Linux的X86通过cmpxchg指令,在多处理器情况下用lock指令加锁完成

ABA问题:如果变量V初次读取时是A,赋值时仍是A,在这段时间内可能被改为其他值,然后又改回A
用带有时间戳的对象引用AtomicStampedReference类,compareAndSet方法会比较前引用是否等于预期引用并且印戳是否等于预期印戳,如果全部相等,则以原子方式将该引用和该标志的值设置为给定的更新值
用版本号~
循环性能开销:自旋(不成功就一直循环执行直到成功)如果长时间不成功会给CPU带来非常大的执行开销。限制自旋次数超过一定次数停止自旋
只能保证一个共享变量的原子操作:用锁或者AtomicReference类把多个共享变量合并成一个共享变量操作

重入锁、互斥锁、读写锁、公平锁、非公平锁、自旋锁、适应自旋锁

可重入锁:允许一个线程二次请求自己持有对象锁的临界资源
互斥锁:没有获取到锁的线程进入阻塞,线程阻塞涉及到用户态和内核态切换的问题,性能可能很差
读写锁:读取锁允许多个reader线程同时持有,而写入锁最多只能有一个writer线程持有
公平锁:先请求锁的人,在锁被释放时,优先获得锁,需要频繁的上下文切换
不公平锁:线程调度器随机给某个线程锁,不分先后,上下文切换较少,有可能刚释放锁的线程能再次获取到锁,导致其他线程永远无法获取到锁,造成“饥饿”现象
自旋锁:没有获取到锁的线程不进入阻塞,一直循环等待释放锁。不能替代阻塞,虽然可以避免线程切换带来的开销,但是它占用了处理器的时间。如果持有锁的线程很快就释放了锁,那么效率高,反之浪费处理器的资源,所以自旋超过了等待的时间(次数)仍然没有获取到锁,则应该被挂起
自适应:由前一次在同一个锁上的自旋时间及锁的拥有者的状态来决定自旋次数。自旋成功则下次自旋次数会加多,因为虚拟机认为下次自旋很有可能会再次成功。反之,自旋的次数会减少,以免浪费处理器资源