多线程(四)

目录

一. 初识Lock与AbstractQueuedSynchronizer(AQS)

1. Lock简介

2. Lock常用API

3. 初识AQS(队列同步器)

4. AQS的模板模式

5. AQS详解

二. ReentrantLock(独占式重入锁)

1. 重入的实现

2. 公平锁OR非公平锁

三. 可重入读写锁---ReentrantReadWriteLock详解

1. 写锁---WriteLock(独占锁)

 

2. ReadLock---读锁(共享式锁)

3. 缓存的实现应用读写锁

4. 锁降级

四. Condition接口的await、signal机制

1. 与内建锁wait、notify的区别

2. 等待队列

3. 应用


 

 

一. 初识Lock与AbstractQueuedSynchronizer(AQS)


1. Lock简介

在Lock接口出现之前,java程序主要靠synchronized关键字实现锁的功能,而在JDK5之后,并发包中增加了lock接口,他提供了与synchronized一样的锁功能。

lock在juc包下。(java.util.concurrent)

与内建锁不同的是,内建锁是隐式的加减锁,而lock是显式的进行锁的获取和消除。

通常使用lock的情况如下:

Lock lock = new ReentrantLock();
        lock.lock();
        try{
            ......
        }finally {
            lock.unlock();
        }

 

2. Lock常用API

lock体系拥有可中断的获取锁超时获取锁以及共享锁等内建锁不具备的特性。

常用API:

  • void lock();   //获取锁
  • void lockInterruptibly() throws InterruptedException();  //响应中断锁
  • boolean tryLock();  //只有在调用是才可以获取锁,获取到锁返回true,否则返回false
  • boolean tryLock(long time, TimeUnit unit);  //超时获取锁,在规定时间内未获取到锁返回false
  • Condition newCondition();  //获取与lock绑定的等待通知组件
  • void unlock();  //释放锁

 

3. 初识AQS(队列同步器)

同步器是用来构建锁以及其他同步组件的基础框架,它的实现主要是依赖一个int状态变量以及通过一个FIFO队列共同构成同步队列。它的子类必须重写AQS的protected修饰的用来改变同步状态的方法,其它方法主要是实现了排队与阻塞机制。int状态的更新使用getState(),setState()以及compareAndSetState()方法。

同步器是实现锁的关键,在锁的实现中聚合同步器,利用同步器实现锁的语义。可以这样理解二者的关系:

lock:面向使用者,定义了使用者与锁交互的接口。

AQS:面向锁的实现者,屏蔽了同步状态的管理,线程排队,线程等待与唤醒等等底层操作。

 

4. AQS的模板模式

AQS使用模板方法模式,将一些与状态相关的核心方法开放给子类重写,而后AQS会使用子类重写的关于状态的方法进行线程的排队,阻塞以及唤醒等操作。

例:自己实现简易的Lock锁

package www.like.java;


import com.sun.corba.se.impl.orbutil.concurrent.Sync;

import java.sql.Time;
import java.util.concurrent.TimeUnit;
import java.util.concurrent.locks.AbstractQueuedSynchronizer;
import java.util.concurrent.locks.Condition;
import java.util.concurrent.locks.Lock;


//自定义锁
class Mutex implements Lock{
    private Sync sync = new Sync();

    //自定义同步器
    static class Sync extends AbstractQueuedSynchronizer {
        @Override
        protected boolean tryAcquire(int arg) {

            //0表示无锁状态,更新为1表示当前线程拿到锁
            if (arg != 1){
                throw new RuntimeException("arg参数不为1");
            }
            //将0状态改为1状态
            if (compareAndSetState(0,1)){
                //此时线程成功获取同步状态,将当前线程ID扔进去
                setExclusiveOwnerThread(Thread.currentThread());
                return true;
            }
            return false;
        }

        @Override
        protected boolean tryRelease(int arg) {
            if(getState() == 0){
                throw new IllegalMonitorStateException();
            }
            //当前线程置空
            setExclusiveOwnerThread(null);
            setState(0);
            return true;
        }

        @Override
        protected boolean isHeldExclusively() {
            return getState() == 1;
        }
    }

    //-------------------------------------------------------------------


    @Override
    public void lock() {
        sync.acquire(1);
    }

    @Override
    public void lockInterruptibly() throws InterruptedException {
        sync.acquireInterruptibly(1);
    }

    @Override
    public boolean tryLock() {
        return sync.tryAcquire(1);
    }

    @Override
    public boolean tryLock(long time, TimeUnit unit) throws InterruptedException {
        return sync.tryAcquireNanos(1,time);
    }

    @Override
    public void unlock() {
        sync.release(1);
    }

    @Override
    public Condition newCondition() {
        return null;
    }

    //------------------------------------------------------------------------

}

public class Test {
    public static void main(String[] args)  {
        Lock lock = new Mutex();
        for (int i = 0; i < 10; i++){
            Thread thread = new Thread(()->{
               lock.lock();
                try {
                    Thread.sleep(5000);
                } catch (InterruptedException e) {
                    e.printStackTrace();
                }finally {
                    lock.unlock();
                }
            });
            thread.start();
        }
    }
}

结果表明,同一时间只有一个线程获取到锁,这也印证了我们的锁实现是正确的。

(如果有些方法不明白的,会在下面进行讲解)

 

5. AQS详解

在同步组件(锁)中,AQS是最核心的部分。同步组件依赖AQS提供的模板方法来实现同步语义。

AQS核心组成:同步队列、独占锁的获取与释放、共享锁的获取与释放、可中断锁、超时锁。

同步队列:

在AQS内部有一个静态内部类Node,这是同步队列中每个具体的节点。

节点中有如下属性:

  • int waitStatus:节点状态
  • Node prev:同步队列中前驱节点
  • Node next:同步队列中后继结点
  • Thread thread:当前节点包装的线程对象
  • Node nextWaiter:等待队列中下一个节点

节点状态值如下:

  • int INITIAL = 0;  //初始状态
  • int CANCELLED = 1;  //当前节点从同步队列中取消
  • int SIGNAL = -1;  //后继节点处于阻塞(wait)状态。如果当前节点释放同步状态会通知后继节点,使后继节点继续运行
  • int CONDITION = -2;  //节点处于等待队列中。当其他线程对Condition调用signal()方法后,该节点会从等待队列移到同步队列中
  • int PROPAGATE = -3;  //共享式同步状态会无条件的传播

AQS同步队列采用带有头尾节点的双向链表。

独占式锁:

  1. void acquire(int arg):独占式获取同步状态,如果获取失败,插入同步队列进行等待
  2. void acquireInterruptibly(int arg):在1的基础上,此方法可以在同步队列中响应中断
  3. boolean tryAcquireNanos(int arg, long nanosTimeOut):在2的基础上增加了超时等待功能,到了预计时间还未获得锁就直接返回
  4. boolean tryAcquire(int arg):获取锁成功返回true,失败返回false
  5. boolean release(int arg):释放同步状态,该方法会唤醒同步队列中的下一个节点

共享式锁:

  1. void acquireShared(int arg):共享获取同步状态,同一时刻多个线程获取同步状态
  2. void acquireSharedInterruptibly(int arg):在1的基础上增加相应中断
  3. boolean tryAcquireSharedNanons(int arg, long nanosTimeOut):在2的基础上增加超时等待功能
  4. boolean releaseShared(int arg):共享式释放同步状态

 

二. ReentrantLock(独占式重入锁)


1. 重入的实现

重入:表示能够对共享资源重复加锁,即当前线程再次获取锁时不会被阻塞。

我们来看一下源码:

c代表的是获取当前同步状态。如果当前同步状态不为0,表示此时同步状态已被线程获取,就执行else语句,else语句是判断持有同步状态的线程是否为当前线程,如果是,同步状态再次加1并返回true,表示持有线程重入同步块。

释放过程:

 当且仅当持有线程为当前线程并且同步状态减为0时,表示锁被正确释放,否则调用setState()将减1后的状态设置回去。

 

2. 公平锁OR非公平锁

ReentrantLock支持两种锁,公平锁和非公平锁。那么,什么是公平性呢?如果锁的获取顺序符合时间上的顺序,即等待时间最长的线程最先获取锁。那么就是公平锁,反之,即为非公平锁。ReentrantLock默认使用非公平锁

如果要想使用公平锁,需要调用ReentrantLock的有参构造,传入true,获取内置的公平锁。

对比:

  • 公平锁保证了获取到锁的线程一定是等待时间最长的线程,保证了请求资源时间上的绝对顺序,需要频繁的进行上下文切换,性能开销较大。
  • 非公平锁保证系统有更大的吞吐量(效率较高),但是会造成线程的饥饿现象(有的线程永远获取不到锁)。

 

三. 可重入读写锁---ReentrantReadWriteLock详解


读写锁:允许同一时刻被多个读线程访问,但是在写线程访问时,读线程和其他写线程都会被阻塞。

写线程能够获取到锁的条件:没有任何读写线程拿到锁。

注意:读线程与写线程互斥,写线程与读线程和写线程都互斥。

1. 写锁---WriteLock(独占锁)

在同一时刻写锁不能被多个线程获取,很显然,它是独占锁,而实现锁的同步语义是通过重写AQS的tryAcquire()方法实现的,源码及分析如下:

protected final boolean tryAcquire(int acquires) {
    /*
     * Walkthrough:
     * 1. If read count nonzero or write count nonzero
     *    and owner is a different thread, fail.
     * 2. If count would saturate(饱和), fail. (This can only
     *    happen if count is already nonzero.)
     * 3. Otherwise, this thread is eligible(合格的) for lock if
     *    it is either a reentrant acquire or
     *    queue policy allows it. If so, update state
     *    and set owner.
     */
    Thread current = Thread.currentThread();
    //判断当前同步状态
    int c = getState();
    //判断写锁的获取次数(exclusive---独占式)
    int w = exclusiveCount(c);
    if (c != 0) {
        // (Note: if c != 0 and w == 0 then shared count != 0)
        if (w == 0 || current != getExclusiveOwnerThread())
            return false;
        if (w + exclusiveCount(acquires) > MAX_COUNT)
            throw new Error("Maximum lock count exceeded");
        // Reentrant acquire
        setState(c + acquires);
        return true;
    }
    if (writerShouldBlock() ||
        !compareAndSetState(c, c + acquires))
        return false; 
    //获取写锁成功
    setExclusiveOwnerThread(current);
    return true;
}

***重要:同步状态的低16位表示写锁获取次数,高16位表示读锁获取次数。***

写锁的获取逻辑:当读锁已被读线程或写锁已被其他线程获取,则写锁获取失败,否则,当前同步状态没有被任何读写线程获取,当前线程获取写锁成功并且支持重入。

 

2. ReadLock---读锁(共享式锁)

源码及分析如下:

protected final int tryAcquireShared(int unused) {
    /*
     * Walkthrough:
     * 1. If write lock held by another thread, fail.
     * 2. Otherwise, this thread is eligible for
     *    lock wrt state, so ask if it should block
     *    because of queue policy. If not, try
     *    to grant by CASing state and updating count.
     *    Note that step does not check for reentrant
     *    acquires, which is postponed to full version
     *    to avoid having to check hold count in
     *    the more typical non-reentrant case.
     * 3. If step 2 fails either because thread
     *    apparently not eligible or CAS fails or count
     *    saturated, chain to version with full retry loop.
     */
    Thread current = Thread.currentThread();
    int c = getState();
    //如果写锁已经被获取并且获取写锁的线程不是当前线程
    //线程获取读锁失败并返回-1
    if (exclusiveCount(c) != 0 &&
        getExclusiveOwnerThread() != current)
        return -1;
    int r = sharedCount(c);
    if (!readerShouldBlock() &&
        r < MAX_COUNT &&
        //当前线程获取读锁
        compareAndSetState(c, c + SHARED_UNIT)) {
        //当前获取锁的次数
        if (r == 0) {
            firstReader = current;
            firstReaderHoldCount = 1;
        } else if (firstReader == current) {
            firstReaderHoldCount++;
        } else {
            HoldCounter rh = cachedHoldCounter;
            if (rh == null || rh.tid != getThreadId(current))
                cachedHoldCounter = rh = readHolds.get();
            else if (rh.count == 0)
                readHolds.set(rh);
            rh.count++;
        }
        return 1;
    }
    //CAS失败或者已经获取读锁的线程再次重入
    return fullTryAcquireShared(current);
}

 

3. 缓存的实现应用读写锁

import java.util.HashMap;
import java.util.Map;
import java.util.concurrent.locks.Lock;
import java.util.concurrent.locks.ReentrantReadWriteLock;

/**
 * 读写锁实现缓存
 */

public class Test {
    static Map<String,Object> map = new HashMap<>();
    static ReentrantReadWriteLock rwl = new ReentrantReadWriteLock();
    static Lock readLock = rwl.readLock();
    static Lock writeLock = rwl.writeLock();

    /**
     * 线程安全的根据一个key获取value
     * @param key
     * @return
     */

    public static final Object get(String key){
        readLock.lock();
        try{
            return map.get(key);
        }finally {
            readLock.unlock();
        }
    }

    /**
     * 线程安全的根据key设置value,并返回旧的value
     * @param key
     * @param value
     * @return
     */

    public static Object put(String key,Object value){
        writeLock.lock();
        try {
            return map.put(key,value);
        }finally {
            writeLock.unlock();
        }
    }

    /**
     * 线程安全的清空所有value
     */
    
    public static final void clear(){
        writeLock.lock();
        try {
            map.clear();
        }finally {
            writeLock.unlock();
        }
    }

 

4. 锁降级

写锁可以降级为读锁,读锁不能升级为写锁。

 

四. Condition接口的await、signal机制


1. 与内建锁wait、notify的区别

  1. Object类提供的wait、notify方法是与对象监视器monitor配合完成线程的等待与通知机制,属于JVM底层实现。                                 而Condition与Lock配合完成的等待通知机制属于Java语言级别,具有更高的控制与扩展性。
  2. Condition独有特性:                                                                                                                                                                    
  •  支持不响应中断,而Object不支持。
  • 支持多个等待队列,而Object只有一个。
  • 支持截止时间设置,而Object不支持。

等待方法:

  1. void await() throws InterruptedException---同Object.wait(),直到被中断或唤醒。

  2. void awaitUninterruptibly()---不响应中断,直到被唤醒。

  3. boolean await(long time, TimeUnit unit) throws InterruptedException---同Object.wait(long timeout),多了自定义时间单位,中断、超时、被唤醒。

  4. boolean awaitUntil(Date deadline) throws InterruptedException---支持设置截止时间。

唤醒方法:

signal():唤醒一个等待在condition上的线程,将该线程由等待队列转移到同步队列中。

signalAll():将所有等待在condition上的线程全部转移到同步队列中。

 

2. 等待队列

等待队列与同步队列共享Node节点类(内部静态类),等待队列是一个单向的有头尾节点的队列

 

3. 应用

Condition实现的多生产多消费模型:

package www.like.java;

import java.util.ArrayList;
import java.util.List;
import java.util.concurrent.locks.Condition;
import java.util.concurrent.locks.Lock;
import java.util.concurrent.locks.ReentrantLock;

class Goods{
    private String name;
    private int count;
    private int maxCount;
    private Lock lock = new ReentrantLock();
    //消费者等待队列
    private Condition consumerCondition = lock.newCondition();
    //生产者等待队列
    private Condition producerCondition = lock.newCondition();
    public Goods(int maxCount) {
        this.maxCount = maxCount;
    }

    /**
     * 生产者方法
     * @param name  设置商品名称
     */

    public void setGoods(String name){
        lock.lock();
        try{
            //商品数量达到最大值,生产者线程进入生产者等待队列
            while(count == maxCount) {
                try {
                    System.out.println(Thread.currentThread().getName()+"还有很多商品,歇会~");
                    producerCondition.await();
                }catch (InterruptedException e) {
                    e.printStackTrace();
                }
            }
            this.name = name;
            count++;
            System.out.println(Thread.currentThread().getName()+"生产"+toString());
            //唤醒属于消费者队列的线程
            consumerCondition.signalAll();
        }finally {
            lock.unlock();
        }
    }

    /**
     * 消费者方法
     */

    public void getGoods(){
        try{
            lock.lock();
            while(count == 0){
                System.out.println(Thread.currentThread().getName()+"还没有商品");
                try {
                    consumerCondition.await();
                }catch (InterruptedException e) {
                    e.printStackTrace();
                }
            }
            count--;
            System.out.println(Thread.currentThread().getName()+"消费"+toString());
            //唤醒所有生产者线程
            producerCondition.signalAll();
        }finally {
            lock.unlock();
        }
    }

    @Override
    public String toString() {
        return "Goods{" +
                "name='" + name + '\'' +
                ", count=" + count +
                '}';
    }
}

class Producer implements Runnable{

    private Goods goods;

    public Producer(Goods goods) {
        this.goods = goods;
    }

    @Override
    public void run() {
        while(true){
            this.goods.setGoods("computer");
        }
    }
}

class Consumer implements Runnable{
    private Goods goods;

    public Consumer(Goods goods) {
        this.goods = goods;
    }

    @Override
    public void run() {
        while(true){
            this.goods.getGoods();}
    }
}


public class Test {

    public static void main(String[] args)  {
        Goods goods = new Goods(100);
        Producer producer = new Producer(goods);
        Consumer consumer = new Consumer(goods);
        List<Thread> list = new ArrayList<>();
        //启动10个生产者线程
        for(int i = 0; i < 10; i++){
            Thread thread = new Thread(producer,"生产者"+i);
            list.add(thread);
        }
        //启动15个消费者线程
        for(int i = 0; i < 15; i++){
            Thread thread = new Thread(consumer,"消费者"+i);
            list.add(thread);
        }
        //一键启动
        for(Thread th:list){
            th.start();
        }
    }
}

 

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值