Java多线程-4-LOCK

四、LOCK

1、锁的4种状态

【1】锁的优化

JDK6为了减少获得锁和释放锁带来的性能消耗,引入了“偏向锁”和“轻量级锁”

从而使得锁有了4种状态,并随着锁竞争的情况而升级。锁可以升级但不能降级,锁的升级被称为“锁膨胀”

【2】偏向锁状态

大多数情况下,锁不仅不存在多线程竞争,而且还总是由同一个线程多次获得

为了提升这种情况的性能,从而引入偏向锁

在对象头和栈帧中的锁记录里存储锁偏向的线程ID,当一个线程进入和退出同步块时,不需要进行CAS操作来加锁和解锁,直接判断对象头的Mark Word里是否存有指向当前线程的偏向锁即可

如果存在表示线程已经获取了锁,如果不存在则锁膨胀成轻量级锁

偏向锁在JDK6和JDK7中是默认开启的,但是需要在程序启动几秒钟后才会被激活

如果需要关闭这个延迟,使用JVM参数

-XX:BiasedLockingStartupDelay=0

如果关闭偏向锁,使用JVM参数

-XX:-UseBiasedLocking

【3】轻量级锁状态

加锁:JVM在当前线程的栈帧中创建用于存储锁标记的空间,并将对象头中的Displaced Mark Word复制到当中。如果成功表示当前线程获取锁成功,如果失败表示存在锁竞争,当前线程会尝试使用自旋来获取锁

解锁:使用原子的CAS操作,将Displaced Mark Word替换回对象头。如果成功表示没有锁竞争发生,如果失败表示有锁竞争发生,会导致锁膨胀成重量级锁

【4】对比

锁类型 优点 缺点 适用场景
偏向锁 加锁和解锁不需要额外等待,
和无锁状态执行方法相比存在纳秒级(1纳秒 = 1000 * 1000毫秒)的差别
如果存在锁竞争,
会带来额外的撤销锁的消耗
只有一个线程访问同步块
轻量级锁 竞争的线程不会阻塞,
提高了程序的响应速度
如果线程一直获取不到锁,
使用自旋会消耗CPU
追求响应速度,
同步块执行速度非常快
重量级锁 线程竞争不会使用自旋,不会消耗CPU 竞争的线程会阻塞,
会降低响应速度
追求吞吐量,同步块执行时间较长

2、公平锁和非公平锁

公平锁:每个线程在获取锁时,会先检查该锁维护的等待队列,如果队列为空,或者当前线程是队列当中的第一个(head),则该线程获取锁。以后会按照FIFO的原则从等待队列中取到自己(排队等待获取锁)

非公平锁:每个线程都可以尝试获取锁

synchronized是非公平锁

java.util.concurrent.locks.ReentrantLock,通过无参构造函数创建的是非公平锁。通过有参构造函数,且传入true,则该锁是一个公平锁

3、可重入锁

可重入锁又称之为递归锁,指的是已经获取锁的线程,当执行同步方法内的同步方法时,无需再次获取锁,即可直接执行方法

synchronized 和 java.util.concurrent.locks.ReentrantLock 都是可重入锁

4、自旋锁

线程通过自旋的方式,尝试获取锁,以减少线程上下文切换的开销

class CasLock {
    
    
    private final AtomicReference<Thread> reference = new AtomicReference<Thread>();

    public void lock() {
    
    
        for (;;) {
    
    
            if (tryAcquire()) {
    
    
                break;
            }
        }
    }

    public void unlock() {
    
    
        for (;;) {
    
    
            if (tryRelease()) {
    
    
                break;
            }
        }
    }

    private boolean tryAcquire() {
    
    
        Thread thread = Thread.currentThread();
        return reference.compareAndSet(null, thread);
    }

    private boolean tryRelease() {
    
    
        Thread thread = Thread.currentThread();
        return reference.compareAndSet(thread, null);
    }

}

5、共享锁和排他锁

共享锁:多个线程都可以获取该锁

排他锁:锁只能由一个线程所独享

java.util.concurrent.locks.ReentrantReadWriteLock的读锁是线程共享的,而写锁是线程独享的

synchronized 和 java.util.concurrent.locks.ReentrantLock 都是排他锁

6、读写锁

java.util.concurrent.locks.ReentrantReadWriteLock

读写锁,读锁和写锁共同存在,读锁共享,写锁独享

7、线程兼容和线程对立

线程兼容:要操作的对象本身是线程不安全的,但是可以在操作对象时,通过各种线程安全的手段,来保证在多线程的环境下,操作对象是线程安全的

线程对立:要操作的对象本身是线程安全的,但是由于在多线程的环境下操作对象时,多个线程的并发请求不是线程安全的,从而导致操作对象本身变成了线程不安全的

Java中线程对立的例子:Thread类的废弃方法(@Deprecated) suspendresume 可能导致死锁

猜你喜欢

转载自blog.csdn.net/adsl624153/article/details/103865288