【JavaSE】多线程之Lock体系

1.Lock简介

        JDK1.5之后,增加了lock接口,它提供了与synchronized一样的锁功能。拥有锁获取和释放的可操作性,可中断的获取锁以及超时获取锁等多种synchronized关键字不具备的同步特性。

使用lock的形式如下:

Lock lock = new ReentrantLock();
lock.lock();
try{
    lock.lock();  //此时其他线程会进入自选状态而不是被阻塞,用synchronized其他线程则会被阻塞
    //以下代码只有一个线程可以运行
    ......
}finally{
    lock.unlock();
}

例:

class MyThread implements Runnable{
    private int ticket = 200;
    private Lock lock = new ReentrantLock();
    @Override
    public void run() {
        for(int i=0; i<200; i++){
            try {
                lock.lock();
                if(ticket > 0){
                    try {
                        Thread.sleep(20);
                    } catch (InterruptedException e) {
                        e.printStackTrace();
                    }
                    System.out.println(Thread.currentThread().getName()+"还剩下"+ticket--+"票");
                }
            } finally {
                lock.unlock();
            }
        }
    }
}
public class Test2{
    public static void main(String[] args) {
        MyThread myThread = new MyThread();
        Thread thread1 = new Thread(myThread,"黄牛A");
        Thread thread2 = new Thread(myThread,"黄牛B");
        Thread thread3 = new Thread(myThread,"黄牛C");
        thread1.start();
        thread2.start();
        thread3.start();
    }
}

2.lock常用API

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

void lock();  //获取锁
void lockInterruptibly() throws InterruptedException();  //响应中断锁
boolean tryLock();  //获取锁返回true,反之返回false
boolean tryLock(long time, TimeUnit unit);  //超时获取锁,在规定时间内未获取到锁返回false
Condition newCodition();  //获取与lock绑定的等待通知组件
void unlock();  //释放锁

        ReentrantLock中 所有方法实际上都是调用了其静态内部类Sync中的方法,而Sync继承了AbstractQueueSynchronizer (AQS-简称同步器)

3.AQS-同步器

        同步器是用来构建锁以及其他同步组件的基础框架,它的实现主要是依赖一个int状态变量以及通过一个FIFO队列共同构成同步队列。

        子类必须重写AQS的用protected(继承权限)修饰的用来改变同步状态的方法,其方法主要是实现了排队与阻塞机制。int状态的更新使用getState()、setState()以及compareAndSetState()。

        子类推荐使用静态内部类来继承AQS来实现自己的同步语义。同步既支持独占锁,也支持共享锁。

4.AQS的模板模式

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

锁与AQS的关系:

锁面对使用者,定义了使用者与锁交互的接口

同步器面向锁的实现,简化了锁的实现方式,屏蔽了同步状态管理,线程排队。等待、唤醒等操作。

//自定义锁
class Mutex implements Lock{
    private Sync sync = new Sync();
    //自定义同步器
    static class Sync extends AbstractQueuedSynchronizer{
        //获取锁
        @Override
        protected boolean tryAcquire(int arg) {
            if(arg != 1){
                throw new RuntimeException("arg参数不为1!");
            }
            if(compareAndSetState(0,1)){
                //此时线程成功获取同步状态
                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 Test2{
    public static void main(String[] args) {
        Lock lock = new Mutex();
        for(int i=0; i<10; i++){
            Thread thread = new Thread(()->{
                try {
                    lock.lock();
                    try {
                        Thread.sleep(5000);
                    } catch (InterruptedException e) {
                        e.printStackTrace();
                    }
                } finally {
                    lock.unlock();
                }
            });
            thread.start();
        }
    }
}

5.AQS详解

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

        AQS实现了对同步状态的管理,以及对阻塞线程进行排队、等待通知等底层实现。

        AQS核心组成:同步队列、独占锁的获取与释放、共享锁的获取与释放、可中断锁、超时锁。这一系列功能的实现依赖于AQS提供的模板方法。

5.1独占锁

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

5.2共享式锁

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

5.3同步队列

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

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

节点中有如下属性:

int waitStatus:节点状态

Node prev:前驱节点

Node next:后继节点

Thread thread:当前节点包装的线程对象

Node nextWaiter:等待队列中的下一个节点

节点状态值如下:

int INITIAL = 0;  //初始状态

int CANCELLED = 1; //当前节点从同步队列中取消

int SIGNAL = -1; //后继节点处于阻塞(但还在同步队列中)。如果当前节点释放同步状态会通知后继节点,使后继节点继续运行。

int CONDITION = -2; //节点处于等待队列中。但其他线程对Condition调用signal()方法后,该节点会从等待队列中移到同步队列中

int PROPAGATE = -3; //共享式同步状态会无条件的传播

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

5.4独占锁的获取:acquire(int arg)

public void lock() {
    sync.lock();
}
----------------------------------------------------------
final void lock() {
    //判断当前CAS的状态,如果为0则更新为1
    if (compareAndSetState(0, 1))
        //将当前线程置为持有线程
        setExclusiveOwnerThread(Thread.currentThread());
    else
        acquire(1);
}

获取锁失败后,调用AQS提供的acquire(int arg)模板方法

public final void acquire(int arg) {
    if (!tryAcquire(arg) && acquireQueued(addWaiter(Node.EXCLUSIVE), arg))
        selfInterrupt();
}

tryAcquire(arg):再次尝试获取同步状态,成功直接方法退出,失败调用addWaiter();

protected final boolean tryAcquire(int acquires) {
    return nonfairTryAcquire(acquires);
}
final boolean nonfairTryAcquire(int acquires) {
    final Thread current = Thread.currentThread();
    int c = getState();
    if (c == 0) {
        if (compareAndSetState(0, acquires)) {
            setExclusiveOwnerThread(current);
            return true;
        }
    }
    else if (current == getExclusiveOwnerThread()) {
        int nextc = c + acquires;
        if (nextc < 0) // overflow
            throw new Error("Maximum lock count exceeded");
        setState(nextc);
        return true;
    }
    return false;
}

addWaiter(Node.EXCLUSIVE):将当前线程以指定模式(独占式,共享式)封装为Node节点后置入同步队列

private Node addWaiter(Node mode) {
    //将线程以指定模式(独占式或共享式)封装为Node节点
    Node node = new Node(Thread.currentThread(), mode);
    //获取当前队列的尾节点
    Node pred = tail;
    //若尾节点不为空
    if (pred != null) {
        node.prev = pred;
        //使用CAS将当前节点尾插到同步队列中
        if (compareAndSetTail(pred, node)) {
            pred.next = node;
            //CAS尾插成功,返回当前Node节点
            return node;
        }
    }
    //尾节点为空 || CAS尾插失败
    enq(node);
    return node;
}

问题:为什么会尾插失败?

CAS有V,N,O

当期望值与实际存储的值不相等时,就会尾插失败

enq(Node node):当前队列为空或者CAS尾插失败调用此方法来初始化队列或不断尾插

private Node enq(final Node node) {
    //直到将当前节点插入到同步队列成功为止
    for (;;) {
        Node t = tail;
        //初始化同步队列
        if (t == null) { // Must initialize
            if (compareAndSetHead(new Node()))
            tail = head;
        } else {
            //不断CAS将当前节点尾插到同步队列中
            node.prev = t;
            if (compareAndSetTail(t, node)) {
                t.next = node;
                return t;
            }
        }
    }
}

acquireQueued()获取锁成功的条件:当前节点前驱为头结点并且再次获取同步状态成功。

final boolean acquireQueued(final Node node, int arg) {
    boolean failed = true;
    try {
        boolean interrupted = false;
        //自旋
        for (;;) {
            //获取当前节点的前驱节点
            final Node p = node.predecessor();
            //获取同步状态成功的条件
            //前驱节点为头结点并且获取同步状态成功
            if (p == head && tryAcquire(arg)) {
                //将当前节点设置为头结点
                setHead(node);
                //删除原来的头结点
                p.next = null; // help GC
                failed = false;
                return interrupted;
            }
            if (shouldParkAfterFailedAcquire(p, node) && parkAndCheckInterrupt())
                interrupted = true;
        }
    } finally {
        //获取失败将当前节点取消
        if (failed)
        cancelAcquire(node);
    }
}

        节点在同步队列中获取锁失败后调用shouldParkAfterFailedAcquire(Node prev, Node node),此方法主要逻辑是使用CAS将前驱状态置为SIGNAL,表示需要将当前节点阻塞。如果CAS失败,不断自旋直到前驱节点状态置为SIGNAL为止。

private static boolean shouldParkAfterFailedAcquire(Node pred, Node node) {
    //获取前驱节点的节点状态
    int ws = pred.waitStatus;
    if (ws == Node.SIGNAL)
        return true;
    //前驱节点已被取消
    if (ws > 0) {
        //不断重试直到找到前驱节点状态不为取消状态
        do {
            node.prev = pred = pred.prev;
        } while (pred.waitStatus > 0);
        pred.next = node;
    } else {
        //前驱节点状态不是取消状态时,将前驱节点的状态置为-1,表示后继节点应该处于等待状态
        compareAndSetWaitStatus(pred, ws, Node.SIGNAL);
    }
    return false;
}

acquireQueued():

1.如果当前的前驱节点为头结点并且能够成功获取同步状态,当前线程获得锁成功,方法执行结束。

2.如果获取锁失败,先不断自旋将前驱节点状态置为SIGNAL,而后调用LockSupport.park()方法将当前线程阻塞。

5.5独占锁的释放:release()

        unlock()方法实际调用AQS提供的release()模板方法。

        release()方法是unlock()方法的具体实现。首先获取头结点的后继节点,当后继节点不为null,会调用LockSupport.unpark()方法唤醒后继节点包装的线程。因此,每一次释放后就会唤醒队列中该节点的后继节点所包装的线程

public final boolean release(int arg) {
    //释放状态成功后
    if (tryRelease(arg)) {
        //获取当前同步队列的头结点
        Node h = head;
        if (h != null && h.waitStatus != 0)
            //唤醒后继节点
            unparkSuccessor(h);
        return true;
    }
    return false;
}
protected final boolean tryRelease(int releases) {
    int c = getState() - releases;
    if (Thread.currentThread() != getExclusiveOwnerThread())
        throw new IllegalMonitorStateException();
    boolean free = false;
    if (c == 0) {
        free = true;
        setExclusiveOwnerThread(null);
    }
    setState(c);
    return free;
}

5.6独占式锁获取与释放总结:

1.线程获取锁失败,将线程调用addWaiter()封装成Node节点进行入队操作。addWaiter中方法enq()完成对同步队列的头结点初始化以及CAS尾插失败后的重试处理。

2.入队之后排队获取锁的核心方法acquireQueued(),节点排队获取锁是一个自旋过程。当且仅当当前节点的前驱节点是头结点并且获取同步状态时,节点出队并且该节点引用的线程获取锁。否则不满足条件时会不断自旋将前驱节点的状态置为SIGNAL而后调用LockSupport.park()将当前线程阻塞。

3.释放锁时会唤醒后继节点(后继节点不为null)

独占锁特性:

        获取锁时响应中断,原理与acquire()几乎一样,唯一区别在于当parkAndCheckInterrupt()返回true时表示线程阻塞时被中断,抛出中断异常后线程退出。

public final void acquireInterruptibly(int arg)
        throws InterruptedException {
    if (Thread.interrupted())
        throw new InterruptedException();
    if (!tryAcquire(arg))
        doAcquireInterruptibly(arg);
}
private void doAcquireInterruptibly(int arg)
    throws InterruptedException {
    final Node node = addWaiter(Node.EXCLUSIVE);
    boolean failed = true;
    try {
        for (;;) {
            final Node p = node.predecessor();
            if (p == head && tryAcquire(arg)) {
                setHead(node);
                p.next = null; // help GC
                failed = false;
                return;
            }
            if (shouldParkAfterFailedAcquire(p, node) &&
                parkAndCheckInterrupt())
                throw new InterruptedException(); //区别在这
        }
    } finally {
        if (failed)
            cancelAcquire(node);
    }
}

超时等待获取锁,tryAcquireNanos(),该方法在三种情况下回返回 :

I.在超时时间内,当前线程成功获取到锁

II.当前线程在超时时间内被中断

III.超时时间结束,仍未获取到锁,线程退出返回false。

public final boolean tryAcquireNanos(int arg, long nanosTimeout) throws InterruptedException {
    if (Thread.interrupted())
        throw new InterruptedException();
    return tryAcquire(arg) ||
    // 实现超时等待的效果
    doAcquireNanos(arg, nanosTimeout);
}

private boolean doAcquireNanos(int arg, long nanosTimeout) throws InterruptedException {
    if (nanosTimeout <= 0L)
    return false;
    // 1.根据超时时间和当前时间计算出截止时间
    final long deadline = System.nanoTime() + nanosTimeout;
    final Node node = addWaiter(Node.EXCLUSIVE);
    boolean failed = true;
    try {
        for (;;) {
            final Node p = node.predecessor();
            // 2.当前线程获得锁出队列
            if (p == head && tryAcquire(arg)) {
                setHead(node);
                p.next = null; // help GC
                failed = false;
                return true;
            }
            // 3.1 重新计算超时时间
            nanosTimeout = deadline - System.nanoTime();
            // 3.2 已经超时返回false
            if (nanosTimeout <= 0L)
                return false;
            // 3.3 线程阻塞等待
            if (shouldParkAfterFailedAcquire(p, node) && nanosTimeout > spinForTimeoutThreshold)
                LockSupport.parkNanos(this, nanosTimeout);
            // 3.4 线程被中断抛出被中断异常
            if (Thread.interrupted())
                throw new InterruptedException();
        }
    } finally {
        if (failed)
            cancelAcquire(node);
    }
}

        超时获取锁逻辑与可中断获取锁基本一致,唯一区别在于获取锁失败后,增加了一个时间处理,如果当前时间超过截止时间,线程不在等待,直接退出,返回false。否则将线程阻塞置为等待状态排队获取锁。

6.再次理解ReentrantLock-独占式重入锁(使用频率最高的一种Lock实现)

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

6.1重入如何实现?

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

final boolean nonfairTryAcquire(int acquires) {
    final Thread current = Thread.currentThread();
    int c = getState();
    if (c == 0) {
        if (compareAndSetState(0, acquires)) {
            setExclusiveOwnerThread(current);
            return true;
        }
    }
    //判断当前线程是否是持有锁线程
    else if (current == getExclusiveOwnerThread()) {
        int nextc = c + acquires;
        if (nextc < 0) // overflow
            throw new Error("Maximum lock count exceeded");
        setState(nextc);
        return true;
    }
    return false;
}

 释放过程:

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

        IllegalMoniyorStateException():非持有锁线程调用tryRelease()方法抛出此异常

protected final boolean tryRelease(int releases) {
    int c = getState() - releases;
    if (Thread.currentThread() != getExclusiveOwnerThread())
        throw new IllegalMonitorStateException();
    boolean free = false;
    if (c == 0) {
        free = true;
        setExclusiveOwnerThread(null);
    }
    //将当前的c在仍回去,在重试释放
    setState(c);
    return free;
}

6.2公平锁与非公平锁

公平锁:锁的获取顺序符合时间上的顺序,即等待时间最长的线程最先获取锁。

public ReentrantLock() {
    //默认为非公平锁
    sync = new NonfairSync();
}

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

public ReentrantLock(boolean fair) {
    sync = fair ? new FairSync() : new NonfairSync();
}

非公平锁获取锁实现:上来先CAS,自旋一次,获取失败则进入等待队列

if (c == 0) {
    if (compareAndSetState(0, acquires)) {
        setExclusiveOwnerThread(current);
        return true;
    }
}

公平锁获取锁实现:上来就直接进入等待队列

获取同步状态之前先判断县当前节点是否有前驱节点,如果有,获取失败

if (c == 0) {
    if (!hasQueuedPredecessors() &&
        compareAndSetState(0, acquires)) {
        setExclusiveOwnerThread(current);
        return true;
    }
}

        公平锁保证了获取到锁的线程一定是等待时间最长的线程,保证了请求资源时间上的绝对顺序,需要频繁的进行上下文交换,性能开销较大。

        非公平锁保证了系统有更大的吞吐量(效率较高),但是会造成线程“饥饿现象”(有的线程可能永远无法获取到锁)

        release()方法唤醒当前节点最近的一个非空节点,最大可能保证公平性。

7.ReentrantReadWriterLock详解(可重入读写锁)

内建锁默认都是独占锁

读写锁:允许同一时刻被多个读线程访问,但是在写线程访问时,所有的读线程以及其他写线程均会被阻塞。

写线程能够获取到锁的前提条件:没有任何读、写线程拿到锁。在任何写线程在写的时候,所有的读线程全被阻塞。

7.1写锁获取-WriterLock-独占锁

7.1.1写锁获取-模板方法tryAcquire()

protected final boolean tryAcquire(int acquires) {
    Thread current = Thread.currentThread();
    //判断当前同步状态
    int c = getState();
    //判断写锁的获取次数
    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位表示读锁次数。

写锁获取逻辑:

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

写锁释放逻辑:

        同独占锁的释放(release)逻辑

7.2读锁-共享式锁-tryAcquireShared()

protected final int tryAcquireShared(int unused) {
    Thread current = Thread.currentThread();
    int c = getState();
    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;
    }
    return fullTryAcquireShared(current);
}

7.3锁降级

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

8.Condition接口的await、signal机制

8.1与内建锁wait、notify的区别

I.Object类提供的wait、notify方法(都是native方法)是与对象监视器monitor配合完成线程的等待与通知机制,属于JVM底层实现;而Condition与lock配合完成的等待通知机制属于java语言级别,具有更高的控制与扩展性。

II.Condition独有特性:

1.支持不响应中断,而Object不支持

2.支持多个等待队列,而Object只有一个

3.支持截止时间设置(在截止时间之前都有效),而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上的线程全部转移到同步队列中。

8.2等待队列

等待队列与同步队列共享了Node节点类,等待队列是一个单向的带有头尾节点的队列

public class Test3{
    public static void main(String[] args) {
        Lock lock = new ReentrantLock();
        Condition condition = lock.newCondition();
        for(int i=0; i<5; i++){
            Thread thread = new Thread(()->{
                try {
                    lock.lock();
                    try {
                        condition.await();
                    } catch (InterruptedException e) {
                        e.printStackTrace();
                    }
                } finally {
                    lock.unlock();
                }
            });
            thread.start();
        }
    }
}

经过调试可看Node属性:

8.3Condition机制实现生产-消费者模型

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;
    }

    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();
                }
            }
            try {
                Thread.sleep(200);
            } catch (InterruptedException e) {
                e.printStackTrace();
            }
            this.name = name;
            count++;
            System.out.println(Thread.currentThread().getName()+"生产"+toString());
            //唤醒处于消费者队列的线程
            consumerCondition.signalAll();
        } finally {
            lock.unlock();
        }
    }

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

    public void getGoods(){
        try {
            lock.lock();
            while(count == 0){
                System.out.println(Thread.currentThread().getName()+"还没有商品");
                try {
                    consumerCondition.await();
                } catch (InterruptedException e) {
                    e.printStackTrace();
                }
            }
            try {
                Thread.sleep(200);
            } catch (InterruptedException e) {
                e.printStackTrace();
            }
            count--;
            System.out.println(Thread.currentThread().getName()+"消费"+toString());
        } finally {
            lock.unlock();
        }
    }
}

class Producer implements Runnable{
    private Goods goods;

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

    @Override
    public void run() {
        while(true){
            this.goods.setGoods("生产口红一套");
        }
    }
}

class Consumer implements Runnable{
    private Goods goods;

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

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

public class Test3{
    public static void main(String[] args) {
        Goods goods = new Goods(20);
        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(consumer,"消费者"+i);
            list.add(thread);
        }
        //启动5个生产者
        for(int i=0; i<5; i++){
            Thread thread = new Thread(producer,"生产者"+i);
            list.add(thread);
        }
        for(Thread th : list){
            th.start();
        }
    }
}

 

 

 

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

打赏作者

gx1500291

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

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

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

打赏作者

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

抵扣说明:

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

余额充值