Java中各种锁的总结

Java中锁的分类
在这里插入图片描述

1. 悲观锁(互斥同步锁)

代表:synchronized、Lock相关类、数据库
示例:

  • 数据库悲观锁修改
-- 锁表修改
select * from x_user for update;

缺点:

  • 阻塞和唤醒带来的性能劣势。
  • 永久阻塞,如果持有锁的线程无限循环、死锁等活跃性问题,那么等待该线程释放锁的其它线程,将永远也得不到执行。
  • 优先级反转,比如优先级低的线程阻塞了,导致优先级高的线程得不到执行

优点:

  • 一劳永逸:正常执行时效率高、消耗低

总结:并发写入多,读取少的场景。适用于临界区持锁时间比较长的情况,悲观锁可以避免大量的无用自旋等消耗,典型情况:

  • 临界区的IO操作
  • 临界区代码复杂或者循环量大
  • 临界区竞争非常激烈

2. 乐观锁(非互斥同步锁)

代表:利用CAS算法来实现,比如各种原子类(Atomic)、并发容器(ConcurrentHashMap)
示例:

  • 数据库乐观锁版本号修改
update x_user set name = '张三', version = version + 1 where version = 1 and id = 3;

缺点:

  • 异常执行时,如自旋时间长、重试多的情况消耗资源也越来越多

优点:

  • 效率高

总结:写入少,读取多的场景。不加锁的能让读取性能大幅提高。

3. 可重入锁

当前线程多次调用lock()方法并不需要解锁,该特性称之为可重入性。该特性原因是内部维持了一个状态值(state)变量来记录当前线程持有锁的次数,只有该状态值(state)为0才表示该线程真正的释放了锁。

代表:synchronized关键字、ReentrantLock类
场景:递归调用

    private static final ReentrantLock lock = new ReentrantLock();

    private static void recursion() {
        System.out.println("调用lock方法之前Hold Count:" + lock.getHoldCount());
        try {
            lock.lock();
            System.out.println("调用lock方法之后Hold Count:" + lock.getHoldCount());
            if (lock.getHoldCount() < 2) {
                recursion();
            }
        } finally {
            System.out.println("---调用unlock方法之后Hold Count:" + lock.getHoldCount());
            lock.unlock();
            System.out.println("---调用unlock方法之后Hold Count:" + lock.getHoldCount());
        }
    }
调用lock方法之前Hold Count:0
调用lock方法之后Hold Count:1
调用lock方法之前Hold Count:1
调用lock方法之后Hold Count:2
---调用unlock方法之后Hold Count:2
---调用unlock方法之后Hold Count:1
---调用unlock方法之后Hold Count:1
---调用unlock方法之后Hold Count:0

ReentrantLock的其它方法介绍:
isHeldByCurrentThread可以获取锁是否被当前线程持有
getQueueLength可以返回当前正在等待这包所的队列有多长

4. 公平锁和非公平锁

公平指的是按照线程请求的顺序,来分配锁;非公平指的是,不完全按照请求的顺序,在一定的情况下,可以插队。
代表:ReentrantLock,其构造参数true表示公平,false表示非公平。


/**
 * 描述:     演示公平和不公平两种情况
 */
public class FairLock {

    public static void main(String[] args) {
        PrintQueue printQueue = new PrintQueue();
        Thread thread[] = new Thread[6];
        for (int i = 0; i < 10; i++) {
            thread[i] = new Thread(new Job(printQueue));
        }
        for (int i = 0; i < 10; i++) {
            thread[i].start();
            try {
                Thread.sleep(100);
            } catch (InterruptedException e) {
                e.printStackTrace();
            }
        }
    }
}

class Job implements Runnable {

    PrintQueue printQueue;

    public Job(PrintQueue printQueue) {
        this.printQueue = printQueue;
    }

    @Override
    public void run() {
        System.out.println(Thread.currentThread().getName() + "开始打印");
        printQueue.printJob(new Object());
        System.out.println(Thread.currentThread().getName() + "打印完毕");
    }
}

class PrintQueue {

    private Lock queueLock = new ReentrantLock(true);

    public void printJob(Object document) {
        queueLock.lock();
        try {
            int duration = new Random().nextInt(10) + 1;
            System.out.println(Thread.currentThread().getName() + "正在打印,需要" + duration);
            Thread.sleep(duration * 1000);
        } catch (InterruptedException e) {
            e.printStackTrace();
        } finally {
            queueLock.unlock();
        }
		
		// 这个地方存在公平、非公平锁的情况
        queueLock.lock();
        try {
            int duration = new Random().nextInt(10) + 1;
            System.out.println(Thread.currentThread().getName() + "正在打印,需要" + duration+"秒");
            Thread.sleep(duration * 1000);
        } catch (InterruptedException e) {
            e.printStackTrace();
        } finally {
            queueLock.unlock();
        }
    }

需要注意的是,ReentrantLock的tryLock方法是个特例。例如当有线程执行tryLock的时候,一旦有线程释放了锁,那么tryLock的线程就能获取到锁,即使在它之前已经有其它在等待的队列。

优势劣势
公平锁各线程公平等待,每个线程在等待一会儿后都能获得执行的机会更慢,吞吐量小
非公平锁更快,吞吐量更大有可能产生线程饥饿,也就是某些线程长时间得不到执行

5. 共享锁和排它锁

  • 共享锁:又称为读锁,获得共享锁之后,可以查看但无法修改和删除数据,其它线程此时也可以获取到共享锁,也可以查看但无法修改和删除数据。
  • 排它锁:又称为独占锁、独享锁、写锁

代表:ReentrantReadWriteLock(其中读锁是共享锁,写锁是独享锁)。

读写锁规则:一个或多个线程同时可以有读锁,或者一个线程有写锁,但两者不会同时出现。即要么多读,要么一写。

共享锁插队策略
  • 公平锁
    不允许插队
  • 非公平锁
    • 写锁可以随时插队
    • 读锁仅在等待队列头节点不是写锁的线程可以插队
    public class CinemaReadWriteLock {
        private static final ReentrantReadWriteLock reentrantReadWriteLock = new ReentrantReadWriteLock();
        /**
         * 读锁
         */
        private static final ReentrantReadWriteLock.ReadLock readLock = reentrantReadWriteLock.readLock();
        /**
         * 写锁
         */
        private static final ReentrantReadWriteLock.WriteLock writeLock = reentrantReadWriteLock.writeLock();
    
        /**
         * 读锁:只能多读
         */
        public static void read() {
            readLock.lock();
            System.out.println(Thread.currentThread().getName() + ":获取到了读锁");
            try {
                TimeUnit.SECONDS.sleep(2);
            } catch (InterruptedException e) {
                e.printStackTrace();
            } finally {
                System.out.println(Thread.currentThread().getName() + ":释放了读锁");
                readLock.unlock();
            }
        }
    
        /**
         * 写锁:只能一写
         */
        public static void write() {
            writeLock.lock();
            System.out.println(Thread.currentThread().getName() + ":获取到了写锁");
            try {
                TimeUnit.SECONDS.sleep(3);
            } catch (InterruptedException e) {
                e.printStackTrace();
            } finally {
                System.out.println(Thread.currentThread().getName() + ":释放了写锁");
                writeLock.unlock();
            }
        }
    
        public static void main(String[] args) {
            new Thread(CinemaReadWriteLock::read,"1").start();
    
            new Thread(CinemaReadWriteLock::read,"2").start();
    
            new Thread(CinemaReadWriteLock::write,"3").start();
    
            new Thread(CinemaReadWriteLock::write,"4").start();
        }
    }
    

6. 自旋锁

代表:Atmoic原子类基本都是自旋锁的实现(死循环),其原理是CAS。

7. 可中断锁

如果某一线程a正在执行锁的代码,另一个线程b正在等待获取该锁,可能由于等待世间过长,这个时候线程b可以中断先处理其它的代码。

代表:Lock是可中断锁,而synchronized就是不可中断。

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包

打赏作者

养-乐多

你的鼓励将是我创作的最大动力

¥1 ¥2 ¥4 ¥6 ¥10 ¥20
扫码支付:¥1
获取中
扫码支付

您的余额不足,请更换扫码支付或充值

打赏作者

实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值