多线程(五:Lock和semaphore)

JAVA SDK并发包通过lock和condition两个接口来实现管程,其中lock用于解决互斥,condition用于解决同步问题。

1.6之前的synchronized互斥锁,线程未获取到锁就进入阻塞状态。Lock针对这个问题做了一些设计来解决。如下:

再造管程的设计理念:

  1. 能够响应中断。synchronized 的问题是,持有锁 A 后,如果尝试获取锁 B 失败,那么线程就进入阻塞状态,一旦发生死锁,就没有任何机会来唤醒阻塞的线程。但如果阻塞状态的线程能够响应中断信号,也就是说当我们给阻塞的线程发送中断信号的时候,能够唤醒它,那它就有机会释放曾经持有的锁 A。这样就破坏了不可抢占条件了。
  2. 支持超时。如果线程在一段时间之内没有获取到锁,不是进入阻塞状态,而是返回一个错误,那这个线程也有机会释放曾经持有的锁。这样也能破坏不可抢占条件。
  3. 非阻塞地获取锁。如果尝试获取锁失败,并不进入阻塞状态,而是直接返回,那这个线程也有机会释放曾经持有的锁。这样也能破坏不可抢占条件。

   重入锁: ReentrantLock,指的是线程可以重复获取同一把锁。

   公平锁和非公平锁:ReentrantLock有两个构造器,一个无参,一个传入fair参数。fair代表是否公平策略,传入true是公平锁,传入false是非公平锁。公平锁的意义在于当有线程释放锁的时候,就需要从等待队列中唤醒一个等待的线程。如果是公平锁,唤醒的策略就是谁等待的时间长,就唤醒谁;如果是非公平锁,有可能等待时间短的线程反而先被唤醒。

    重入锁比较灵活,可以通过lock,unlock trylock等方法实现锁的细节控制。公平性是true的话,会倾向于把锁赋值给等待时间久的线程,这种事减少线程饥饿的方法。但是引入公平锁会产生一定的开销,会导致吞吐量下降。

 

   再从重入锁出发,分场景:

        读多写少的场景: 读写锁,readWriteLock。读写锁的特点:. 1 允许多个线程同时读共享变量; 2. 只允许一个线程写共享变量; 3. 如果一个写线程正在执行写操作,此时禁止读线程读共享变量。读写锁支持重入锁所有的方法。都实现了LOCK接口的方法。

 读写锁喝互斥锁的最大区别:读写锁允许多个线程同时读共享变量,互斥锁是不允许的。但是读写锁的写操作是互斥的,当一个线程在进行写操作时,是不允许其他线程来进行读操作和写操作的。

如下是读写锁来实现一个缓存工具类:

    final Map<String, String> m = new HashMap<>();
    final ReadWriteLock rwl = new ReentrantReadWriteLock();

    // 读锁
    final Lock r = rwl.readLock();
    // 写锁
    final Lock w = rwl.writeLock();

    String get(String key) {
        String v = null;
        // 读缓存
        r.lock();
        try {
            v = m.get(key);
        } finally {
            r.unlock();
        }
        // 缓存中存在,返回
        if (v != null) {
            return v;
        }
        // 缓存中不存在,查询数据库
        w.lock();
        try {
            // 避免高并发场景下其他线程重复查询数据库。
            v = m.get(key);
            if (v == null) {
                // 查询数据库
                m.put(key, v);
            }
        } finally {
            w.unlock();
        }
        return v;
    }

    // 写缓存
    String put(String key, String v) {
        w.lock();
        try {
            return m.put(key, v);
        } finally {
            w.unlock();
        }
    }

这边首先获取缓存初始化的数据。缓存的初始化,分为一次性加载和按需加载,即懒加载,按数据的量决定。

读写锁不支持锁的升级(读锁里嵌套了写锁),读锁还没有释放掉的情况下获取写锁,会导致读锁一直处于阻塞状态。但是读写锁支持锁的降级,获取读锁的时候线程还持有写锁。

 

   AQS  abstractQueuedSynchronieer  同步发生器。重入锁,读写锁都是基于AQS的。通过FIFO同步队列来实现线程争夺资源的。线程之间通过指针连接起来。前面指针指向后面指针的后继,后面指针指向前面的前驱。还有一个头结点同步器,主要是用来指向头部线程和尾部线程的。这就是CLH同步队列。    实际工作原理是   线程会先获取state值 state为0时没有线程占用获取锁,state大于0时有线程占用,线程进入同步队列中。state也是volatile修饰的。每个线程通过自旋的方式去获取锁。

 

semaphore

Semaphore,普遍翻译为“信号量”。

信号量模型还是很简单的,可以简单概括为:一个计数器,一个等待队列,三个方法。在信号量模型里,计数器和等待队列对外是透明的,所以只能通过信号量模型提供的三个方法来访问它们,这三个方法分别是:init()、down() 和 up()。

  • init():设置计数器的初始值。
  • down():计数器的值减 1;如果此时计数器的值小于 0,则当前线程将被阻塞,否则当前线程可以继续执行。
  • up():计数器的值加 1;如果此时计数器的值小于或者等于 0,则唤醒等待队列中的一个线程,并将其从等待队列中移除。

活锁:各个线程之间互相谦让,都让对方线程去获取资源。

线程饥饿: 线程A进入等待队列,获得锁的线程执行完任务后把CPU资源给了线程B,B释放锁后又给了C,就一直没有给到线程A。

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

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

余额充值