35-多线程--多线程JDK1.5新特性(Lock+Condition)+使用JDK1.5新特性解决多生产者多消费者问题+总结+范例(Lock+Condition-多生产者多消费者问题实际开发代码)

java.util.concurrent.locks包中提供了几个接口:Lock、Condition......

一、java.util.concurrent.locks.Lock

1、interface Lock:Lock实现提供了比使用synchronized方法(同步函数)和语句(同步代码块)可获得的更广泛的锁定操作

2、JDK1.5以后,将同步和锁封装成了对象,并将操作锁的隐式方法定义到了该对象中,将隐式动作变成了显式动作

注:Lock出现的目的:替代synchronized,包括:同步代码块和同步函数。因为同步代码块和同步函数只是一个封装体,封装体中自带锁(对锁的操作是隐式的:获取和释放锁都是底层自动完成的)。而Lock是一个锁对象,具备着操作锁的方法(对锁的操作是显式的:获取和释放锁都是通过调用方法完成的)

3、Lock接口的实现允许锁在不同的作用范围内获取和释放,并允许以任何顺序获取和释放多个锁

(synchronized方法或语句的使用提供了对与每个对象相关的隐式监视器锁的访问,但却强制所有锁获取和释放均要出现在一个块结构中:当获取了多个锁时,它们必须以相反的顺序释放,且必须在与所有锁被获取时相同的词法范围内释放所有锁)

4、锁定和取消锁定出现在不同作用范围中时,必须谨慎地确保保持锁定时所执行的所有代码用 try/finally 或 try/catch 加以保护,以确保在必要时释放锁

(使用结构块锁,就具备使用synchronized方法和语句时会出现的锁自动释放功能)

5、方法

(1)void lock():获取锁。如果锁不可用,出于线程调度目的,将禁用当前线程,并且在获得锁之前,该线程将一直处于休眠状态

(2)void unlock():释放锁。释放锁的动作一定要执行,所以,unlock()方法应该放在finally代码块中

(3)Condition newCondition():返回绑定到此Lock实例的新Condition实例。在等待条件前,锁必须由当前线程保持。调用Condition.await()将在等待前以原子方式释放锁,并在等待返回前重新获取锁

注:Condition实例的具体操作依赖于Lock实现,并且该实现必须对此加以记录

(4)boolean tryLock():仅在调用时锁为空闲状态才获取该锁

(5)boolean tryLock(long time, TimeUnit unit) throws InterruptedException:如果锁在给定的等待时间内空闲,并且当前线程未被中断,则获取锁

(6)void lockInterruptibly() throws InterruptedException:如果当前线程未被中断,则获取锁

6、代码演变

    //最开始的同步代码。Object obj是锁
    Object obj = new Object();

    public void show() {
        synchronized (obj) {
            //code...
        }
    }
    //JDK1.5后,将同步和锁封装成了锁对象Lock
    //ReentrantLock:一个可重入的互斥锁Lock,是Lock接口的子类
    Lock lock = new ReentrantLock();

    //用Lock替代synchronized,所以,不用synchronized做同步了
    public void show() {
        //获取锁
        lock.lock();

        //code...

        //释放锁
        lock.unlock();
    }

问题:获取锁后,在执行code代码时发生异常,程序跳转,释放锁的代码执行不到,导致其他线程都获取不到锁

解决方法:释放锁的动作一定要执行。将释放锁的 lock.unlock() 方法放在finally代码块中

    //JDK1.5后,将同步和锁封装成了锁对象Lock
    //ReentrantLock:一个可重入的互斥锁Lock,是Lock接口的子类
    Lock lock = new ReentrantLock();

    //用Lock替代synchronized,所以,不用synchronized做同步了
    public void show() {
        //获取锁
        lock.lock();
        try {
            //将可能会发生异常的代码放在try代码块中
            //code...

        } finally {
            //异常是否被处理catch不一定
            //但一定要将锁释放,写在finally中
            //释放锁
            lock.unlock();
        }
    }

7、用Lock改写多生产者多消费者问题的代码

/**
 * 此段代码有问题
 */

//导包
import java.util.concurrent.locks.Lock;
import java.util.concurrent.locks.ReentrantLock;

class Resource {
    private String name;
    private int count = 1;
    private boolean flag = false;

    //创建一个锁对象
    Lock lock = new ReentrantLock();

    //用Lock替代synchronized,所以,不用synchronized做同步了
//    public synchronized void set(String name) {
    public void set(String name) {

        //获取锁
        lock.lock();

        try {
            //使用while替换if
            while (flag) {
                try {
                    this.wait();
                } catch (InterruptedException e) {
                    e.printStackTrace();
                }
            }

            this.name = name + count;
            count++;
            System.out.println(Thread.currentThread().getName() + "...生产者..." + this.name);

            this.flag = true;
            //使用notifyAll()全唤醒
            this.notifyAll();
        } finally {
            //释放锁
            lock.unlock();
        }
    }

    //用Lock替代synchronized,所以,不用synchronized做同步了
//    public synchronized void out() {
    public void out() {

        //获取锁
        lock.lock();

        try {
            //使用while替换if
            while (!flag) {
                try {
                    this.wait();
                } catch (InterruptedException e) {
                    e.printStackTrace();
                }
            }

            System.out.println(Thread.currentThread().getName() + "......消费者......" + this.name);

            this.flag = false;
            //使用notifyAll()全唤醒
            this.notifyAll();
        } finally {
            //释放锁
            lock.unlock();
        }
    }
}

二、java.util.concurrent.locks.Condition

1、之前同步中的锁是自定义的this、obj等,锁操作锁上的线程this.wait()等。现在的锁是自己创建的,没有this锁,不能使用this.wait()了,要使用 new Lock() 锁上的方法

2、interface Condition:将Object监视器方法(wait()、notify()、notifyAll())分解成截然不同的对象,以便通过将这些对象与任意Lock实现组合使用,为每个对象提供多个等待set(wait-set)。其中,Lock替代了synchronized方法和语句的使用,Condition替代了Object监视器方法的使用

3、之前的同步代码,一个锁上只能有一组监视器方法wait()、notify()、notifyAll()。因为每个锁都是Object的子类,只能拿到一组方法。而Condition将Object中的方法进行了单独的封装,封装成一个Condition对象。一个Lock锁上可以挂多个Condition,每个Condition都有一组监视器

(可以将监视器方法封装成Condition对象,一个Condition对象中封装一组监视器方法。有多个Condition对象,它们都可以属于同一个锁Lock,这些Condition的方法都可以作用到Lock锁的线程上)

4、获取Condition

(1)Condition newCondition():返回绑定到此Lock实例的新Condition实例(返回新的监视器方法实例)

注:Condition实例实质上被绑定到一个锁上(因为监视器必须得监视锁上的线程)。要为特定Lock实例获得Condition实例,请使用其newCondition()方法

    //创建一个锁对象
    Lock lock = new ReentrantLock();
    
    //通过已有的锁,获取该锁上的监视器对象
    //一个Lock锁上可以有多个Condition监视器对象
    Condition con1 = lock.newCondition();
    Condition con2 = lock.newCondition();

5、方法

(1)void await() throws InterruptedException:造成当前线程在接到信号或被中断之前一直处于等待状态

(2)boolean await(long time, TimeUnit unit) throws InterruptedException:造成当前线程在接到信号、被中断或到达指定等待时间之前一直处于等待状态。此方法在行为上等效于:awaitNanos(unit.toNanos(time)) > 0

(3)long awaitNanos(long nanosTimeout) throws InterruptedException:造成当前线程在接到信号、被中断或到达指定等待时间之前一直处于等待状态

(4)void awaitUninterruptibly():造成当前线程在接到信号之前一直处于等待状态

(5)boolean awaitUntil(Date deadline) throws InterruptedException:造成当前线程在接到信号、被中断或到达指定最后期限之前一直处于等待状态

(6)void signal():唤醒一个等待线程。如果所有的线程都在等待此条件,则选择其中的一个唤醒。在从await()返回之前,该线程必须重新获取锁

(7)void signalAll():唤醒所有等待线程。如果所有的线程都在等待此条件,则唤醒所有线程。在从await()返回之前,每个线程都必须重新获取锁

注:Object中的监视器方法和Condition中的监视器方法之间的对应关系

(1)wait() --> await()

(2)notify() --> signal()

(3)notifyAll() --> signalAll()

6、一个Lock锁上可以有多组Condition监视器(监视器所属于锁),每个Condition监视器上都有await()、signal()、signalAll()方法。所以,一个Lock锁上可以有多组Condition监视器方法

三、使用JDK1.5新特性解决多生产者多消费者问题

1、以前一个锁上只有一组监视器,这组监视器既监视着生产者,又监视着消费者。意味着这组监视器能将生产者和消费者全都wait()或唤醒(notify()、notifyAll())。而现在线程有了分类,一组负责生产,一组负责消费。希望生产者/消费者可以唤醒消费者/生产者。使用两个监视器,一组监视生产者,一组监视消费者(四个线程共用同一把锁,但是监视器不同)

2、锁的替换

(1)Lock <-- 同步synchronized

(2)Condition <-- Object中的监视器方法

3、监视器方法替换

(1)await() <-- wait()

(2)signal() <-- notifyAll()

注:生产者线程和消费者线程共用一个锁Lock,但可以根据Lock锁获取两组Condition监视器对象,一组监视生产者线程,一组监视消费者线程。这样,await()和signal()时,可以分别指定操作哪组线程。使用signal()替代notifyAll(),而不用signalAll()

//导包
import java.util.concurrent.locks.Condition;
import java.util.concurrent.locks.Lock;
import java.util.concurrent.locks.ReentrantLock;

class Resource {
    private String name;
    private int count = 1;
    private boolean flag = false;

    //创建一个锁对象
    //ReentrantLock:是Lock接口的一个子类
    Lock lock = new ReentrantLock();

    //通过已有的锁,获取该锁上的两组监视器对象。一组监视生产者,一组监视消费者
    //一个Lock锁上可以有多个Condition监视器对象
    Condition producer_condition = lock.newCondition();
    Condition consumer_condition = lock.newCondition();

    //有Lock就不使用synchronized
    public void set(String name) {
        //获取锁
        lock.lock();

        try {
            //使用while替换if
            while (flag) {
                try {
                    //生产者等待
                    producer_condition.await();
                } catch (InterruptedException e) {
                    e.printStackTrace();
                }
            }

            this.name = name + count;
            count++;
            System.out.println(Thread.currentThread().getName() + "...生产者 5.0..." + this.name);

            this.flag = true;
            //唤醒消费者
            //因为唤醒的是消费者线程中的内容,所以,使用signal()唤醒任意一个即可
            //不用全唤醒,全唤醒也只能有一个执行
            consumer_condition.signal();
        } finally {
            //释放锁
            lock.unlock();
        }
    }

    //有Lock就不使用synchronized
    public void out() {
        //获取锁
        lock.lock();

        try {
            //使用while替换if
            while (!flag) {
                try {
                    //消费者等待
                    consumer_condition.await();
                } catch (InterruptedException e) {
                    e.printStackTrace();
                }
            }

            System.out.println(Thread.currentThread().getName() + "......消费者 5.0......" + this.name);

            this.flag = false;
            //唤醒生产者
            //因为唤醒的是生产者线程中的内容,所以,使用signal()唤醒任意一个即可
            //不用全唤醒,全唤醒也只能有一个执行
            producer_condition.signal();
        } finally {
            //释放锁
            lock.unlock();
        }
    }
}

class Producer implements Runnable {
    private Resource r;

    Producer(Resource r) {
        this.r = r;
    }

    @Override
    public void run() {
        while (true) {
            r.set("烤鸭");
        }
    }
}

class Consumer implements Runnable {
    private Resource r;

    Consumer(Resource r) {
        this.r = r;
    }

    @Override
    public void run() {
        while (true) {
            r.out();
        }
    }
}

public class Test {
    public static void main(String[] args) {
        Resource r = new Resource();

        Producer producer = new Producer(r);
        Consumer consumer = new Consumer(r);

        Thread t0 = new Thread(producer);
        Thread t1 = new Thread(producer);
        Thread t2 = new Thread(consumer);
        Thread t3 = new Thread(consumer);

        t0.start();
        t1.start();
        t2.start();
        t3.start();
    }
}

四、总结

1、Lock接口:Lock接口的出现替代了同步代码块或同步函数,将同步的隐式锁操作变成了显式锁操作。同时更为灵活,可以一个锁上加多组监视器

(1)lock():获取锁

(2)unlock():释放锁。通常需要定义在finally代码块中

2、Condition接口:Condition接口的出现替代了Object中的wait()、notify()、notifyAll()方法,将这些监视器方法单独进行了封装,变成了Condition监视器对象,可以和任意的锁进行组合

(1)await():wait()

(2)signal():notify()

(3)signalAll():notifyAll()

五、范例

1、Lock

        Lock l = ...;
        l.lock();
        try {
            // access the resource protected by this lock
        } finally {
            l.unlock();
        }

2、Condition -- 多生产者多消费者问题实际开发代码

class BoundedBuffer {
    //锁
    final Lock lock = new ReentrantLock();
    //监视器
    //非满
    final Condition notFull = lock.newCondition();
    //非空
    final Condition notEmpty = lock.newCondition();

    //数组容器,存的是对象
    final Object[] items = new Object[100];
    //用来操作数组的变量:存,取,计数器
    //有数组就得有指针,存和取的指针得分别定义putptr、takeptr,同时还得记录数组中元素的个数
    int putptr, takeptr, count;

    //await()方法抛出异常,因为方法内没有做catch处理,想让调用者处理。也可以在方法内处理
    public void put(Object x) throws InterruptedException {
        //获取锁。此时,take()不能取,因为用的是同一个锁,互斥
        lock.lock();
        try {
            //判断标记时一定要用while。因为每次醒来都先判断标记,安全
            //while是必须的
            while (count == items.length) {
                //存满了,生产者等待
                notFull.await();
            }
            //生产一个存一个
            items[putptr] = x;
            if (++putptr == items.length) {
                //生产到最后一个后,继续从0开始存
                putptr = 0;
            }
            ++count;
            //signal()、signalAll()用哪个不一定。如果signal()能实现唤醒对方,就不需要signalAll()
            notEmpty.signal();
        } finally {
            lock.unlock();
        }
    }

    public Object take() throws InterruptedException {
        lock.lock();
        try {
            //判断标记时一定要用while。因为每次醒来都先判断标记,安全
            //while是必须的
            while (count == 0) {
                //取完了,消费者等待
                notEmpty.await();
            }
            //从默认的0角标开始取
            Object x = items[takeptr];
            if (++takeptr == items.length) {
                //取到最后一个后,继续从0开始取
                takeptr = 0;
            }
            --count;
            //signal()、signalAll()用哪个不一定。如果signal()能实现唤醒对方,就不需要signalAll()
            notFull.signal();
            return x;
        } finally {
            lock.unlock();
        }
    }
}

 

  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值