广西哪里有卖黄柏种子的?

小说:广西哪里有卖黄柏种子的?作者:帝华更新时间:2019-04-21字数:17228

      java.util.concurrent.locks包为锁和等待条件提供一个框架的接口和类,它不同于内置同步和监视器。该框架允许更灵活地使用锁和条件,但以更难用的语法为代价。 

        Lock 接口支持那些语义不同(重入、公平等)的锁规则,可以在非阻塞式结构的上下文(包括 hand-over-hand 和锁重排算法)中使用这些规则。主要的实现是 ReentrantLock。 

        ReadWriteLock 接口以类似方式定义了一些读取者可以共享而写入者独占的锁。此包只提供了一个实现,即 ReentrantReadWriteLock,因为它适用于大部分的标准用法上下文。但程序员可以创建自己的、适用于非标准要求的实现。 

   以下是locks包的相关类图:

 

        在之前我们同步一段代码或者对象时都是使用 synchronized关键字,使用的是Java语言的内置特性,然而 synchronized的特性也导致了很多场景下出现问题,比如:

        在一段同步资源上,首先线程A获得了该资源的锁,并开始执行,此时其他想要操作此资源的线程就必须等待。如果线程A因为某些原因而处于长时间操作的状态,比如等待网络,反复重试等等。那么其他线程就没有办法及时的处理它们的任务,只能无限制的等待下去。如果线程A的锁在持有一段时间后可自动被释放,那么其他线程不就可以使用该资源了吗?再有就是类似于数据库中的共享锁与排它锁,是否也可以应用到应用程序中?所以引入Lock机制就可以很好的解决这些问题。

  Lock提供了比 synchronized更多的功能。但是要注意以下几点:

   Lock不是Java语言内置的,synchronized是Java语言的关键字,因此是内置特性。Lock是一个类,通过这个类可以实现同步访问;

   Lock和synchronized有一点非常大的不同,采用 synchronized不需要用户去手动释放锁,当synchronized方法或者 synchronized代码块执行完之后,系统会自动让线程释放对锁的占用;而 Lock则必须要用户去手动释放锁,如果没有主动释放锁,就有可能导致出现死锁现象。

 

一、可重入锁 ReentrantLock

  想到锁我们一般想到的是同步锁即 Synchronized,这里介绍的可重入锁ReentrantLock的效率更高。IBM对于可重入锁进行了一个介绍:JDK 5.0 中更灵活、更具可伸缩性的锁定机制

  这里简单介绍下可重入锁的分类:(假设线程A获取了锁,现在A执行完成了,释放了锁同时唤醒了正在等待被唤醒的线程B。但是,A执行唤醒操作到B真正获取锁的时间里可能存在线程C已经获取了锁,造成正在排队等待的B无法获得锁)

  1) 公平锁: 

     由于B先在等待被唤醒,为了保证公平性原则,公平锁会先让B获得锁。

  2) 非公平锁

     不保证B先获取到锁对象。

  这两种锁只要在构造ReentrantLock对象时加以区分就可以了,当参数设置为true时为公平锁,false时为非公平锁,同时默认构造函数也是创建了一个非公平锁。

    private Lock lock = new ReentrantLock(true);

ReentrantLock的公平锁在性能和实效性上作了很大的牺牲,可以参考IBM上发的那篇文章中的说明。

二、条件变量 Condition

  Condition是java.util.concurrent.locks包下的一个接口,  Condition 接口描述了可能会与锁有关联的条件变量。这些变量在用法上与使用 Object.wait 访问的隐式监视器类似,但提供了更强大的功能。需要特别指出的是,单个 Lock 可能与多个 Condition 对象关联。为了避免兼容性问题,Condition 方法的名称与对应的 Object 版本中的不同。 

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

   Condition(也称为条件队列 或条件变量)为线程提供了一种手段,在某个状态条件下直到接到另一个线程的通知,一直处于挂起状态(即“等待”)。因为访问此共享状态信息发生在不同的线程中,所以它必须受到保护,因此要将某种形式的锁与 Condition相关联。

        Condition 实例实质上被绑定到一个锁上。

  这里不再对Locks包下的源码进行分析。

 

三、ReentrantLock和Condition设计多线程存取款

1. 存款的时候,不能有线程在取款 。取款的时候,不能有线程在存款。

2. 取款时,余额大于取款金额才能进行取款操作,否则提示余额不足。

3.  当取款时,如果金额不足,则阻塞当前线程,并等待2s(可能有其他线程将钱存入)。

    如果2s之内没有其它线程完成存款,或者还是金额不足则打印金额不足。

    如果其它存入足够金额则通知该阻塞线程,并完成取款操作。

/**
 * 普通银行账户,不可透支
 */
public class MyCount {
    private String oid; // 账号
    private int cash;   // 账户余额
    //账户锁,这里采用公平锁,挂起的取款线程优先获得锁,而不是让其它存取款线程获得锁
    private Lock lock = new ReentrantLock(true);
    private Condition _save = lock.newCondition(); // 存款条件
    private Condition _draw = lock.newCondition(); // 取款条件

    MyCount(String oid, int cash) {
        this.oid = oid;
        this.cash = cash;
    }

    /**
     * 存款
     * @param x 操作金额
     * @param name 操作人
     */
    public void saving(int x, String name) {
        lock.lock(); // 获取锁
        if (x > 0) {
            cash += x; // 存款
            System.out.println(name + "存款" + x + ",当前余额为" + cash);
        }
        _draw.signalAll(); // 唤醒所有等待线程。
        lock.unlock(); // 释放锁
    }

    /**
     * 取款
     * @param x  操作金额
     * @param name 操作人
     */
    public void drawing(int x, String name) {
        lock.lock(); // 获取锁
        try {
            if (cash - x < 0) {
                System.out.println(name + "阻塞中");
                _draw.await(2000,TimeUnit.MILLISECONDS); // 阻塞取款操作, await之后就隐示自动释放了lock,直到被唤醒自动获取
            }
            if(cash-x>=0){
                cash -= x; // 取款
                System.out.println(name + "取款" + x + ",当前余额为" + cash);
            }else{
                System.out.println(name+" 余额不足,当前余额为 "+cash+"   取款金额为 "+x);
            }
            // 唤醒所有存款操作,这里并没有什么实际作用,因为存款代码中没有阻塞的操作
            _save.signalAll();
        } catch (InterruptedException e) {
            e.printStackTrace();
        } finally {
            lock.unlock(); // 释放锁
        }
    }
}

这里的可重入锁也可以设置成非公平锁,这样阻塞取款线程可能后与其它存取款操作。

 /**
     * 存款线程类
     */
    static class SaveThread extends Thread {
        private String name; // 操作人
        private MyCount myCount; // 账户
        private int x; // 存款金额

        SaveThread(String name, MyCount myCount, int x) {
            this.name = name;
            this.myCount = myCount;
            this.x = x;
        }

        public void run() {
            myCount.saving(x, name);
        }
    }

    /**
     * 取款线程类
     */
    static class DrawThread extends Thread {
        private String name; // 操作人
        private MyCount myCount; // 账户
        private int x; // 存款金额

        DrawThread(String name, MyCount myCount, int x) {
            this.name = name;
            this.myCount = myCount;
            this.x = x;
        }

        public void run() {
            myCount.drawing(x, name);
        }
    }

    public static void main(String[] args) {
        // 创建并发访问的账户
        MyCount myCount = new MyCount("95599200901215522", 1000);
        // 创建一个线程池
        ExecutorService pool = Executors.newFixedThreadPool(3);
        Thread t1 = new SaveThread("S1", myCount, 100);
        Thread t2 = new SaveThread("S2", myCount, 1000);
        Thread t3 = new DrawThread("D1", myCount, 12600);
        Thread t4 = new SaveThread("S3", myCount, 600);
        Thread t5 = new DrawThread("D2", myCount, 2300);
        Thread t6 = new DrawThread("D3", myCount, 1800);
        Thread t7 = new SaveThread("S4", myCount, 200);
        // 执行各个线程
        pool.execute(t1);
        pool.execute(t2);
        pool.execute(t3);
        pool.execute(t4);
        pool.execute(t5);
        pool.execute(t6);
        pool.execute(t7);

        try {
            Thread.sleep(3000);
        } catch (InterruptedException e) {
            e.printStackTrace();
        }
        // 关闭线程池
        pool.shutdown();
    }
}

上述类中定义了多个存取款的线程,执行结果如下:

S1存款100,当前余额为1100
S3存款600,当前余额为1700
D2阻塞中
S2存款1000,当前余额为2700
D2取款2300,当前余额为400
D3阻塞中
S4存款200,当前余额为600
D3 余额不足,当前余额为 600 取款金额为 1800
D1阻塞中
D1 余额不足,当前余额为 600 取款金额为 12600

执行步骤如下:

  1. 初始化账户,有余额100。
  2. S1,S3完成存款。
  3. D2取款,余额不足,释放锁并阻塞线程,进入等待队列中。
  4.  S2完成存款操作后,会唤醒挂起的线程,这时D2完成了取款。
  5.  D3取款,余额不足,释放锁并阻塞线程,进入等待队列中。
  6.  S4完成存款操作后,唤醒D3,但是依然余额不足,D3 取款失败。
  7.  D1 进行取款,等待2s钟,无任何线程将其唤醒,取款失败。

这里需要注意的是,当Condition调用await()方法时,当前线程会释放锁(否则就和Sychnize就没有区别了)

将银行账户中的 锁改成非公平锁时,执行的结果如下:

1存款100,当前余额为1100
S3存款600,当前余额为1700
D2阻塞中
S2存款1000,当前余额为2700
D3取款1800,当前余额为900
D2 余额不足,当前余额为 900   取款金额为 2300
S4存款200,当前余额为1100
D1阻塞中
D1 余额不足,当前余额为 1100   取款金额为 12600

D2 取款出现余额不足后释放锁,进入等待状态。但是当S2线程完成存款后并没有立刻执行D2线程,而是被D3插队了。

通过执行结果可以看出 公平锁和非公平锁的区别,公平锁能保证等待线程优先执行,但是非公平锁可能会被其它线程插队。

 

四、ArrayBlockingQueue中关于ReentrantLock和Condition的应用

JDK源码中关于可重入锁的非常典型的应用是 BlockingQueue,从它的源码中的成员变量大概就能知道了(ArrayBlockingQueue为例):

 /** The queued items */
    final Object[] items;

    /** items index for next take, poll, peek or remove */
    int takeIndex;

    /** items index for next put, offer, or add */
    int putIndex;

    /** Number of elements in the queue */
    int count;

    /*
     * Concurrency control uses the classic two-condition algorithm
     * found in any textbook.
     */

    /** Main lock guarding all access */
    // 主要解决多线程访问的线程安全性问题
    final ReentrantLock lock;

    /** Condition for waiting takes */
    // 添加元素时,通过notEmpty 唤醒消费线程(在等待该条件)
    private final Condition notEmpty;

    /** Condition for waiting puts */
    // 删除元素时,通过 notFull 唤醒生成线程(在等待该条件)
    private final Condition notFull;

ArrayBlockingQueue 是一个典型的生产者消费者模型,通过一个数组保存元素。为了保证添加和删除元素的线程安全性,增加了可重入锁和条件变量。

可重入锁主要保证多线程对阻塞队列的操作是线程安全的,同时为了让被阻塞的消费者或者生产者能够被自动唤醒,这里引入了条件变量。

当队列已满时,Producer会被阻塞,此时如果Customer消费一个元素时,被阻塞的Producer就会被自动唤醒并往队列中添加元素。

 

上面的两个例子可见java.util.concurrent.locks包下的ReentrantLock和Condition配合起来的灵活性及实用性。

参考:

可重入锁介绍:https://blog.csdn.net/yanyan19880509/article/details/52345422

IBM关于Lock介绍:http://www.ibm.com/developerworks/cn/java/j-jtp10264/index.html

http://286.iteye.com/blog/2296249

当前文章:http://www.adsl66.com/ph8ex.html

发布时间:2019-04-21 01:24:39

2017年最新的樱花批发报价曝光了 月季工程苗价格哪里最低? 8月份可以栽植麦冬草吗? 内蒙古可以栽植马兰花吗? 哪里有卖新品种月季花? 猛一看是木瓜,近看才知道是贴梗海棠 苦荞麦和甜荞麦有什么区别 散尾葵种子多少钱一斤? 狗牙根属于什么草坪? 狗牙根有几个品种?

81052 45542 85896 30980 82861 12642 63149 70825 11883 94453 65201 11257 76237 67789 69663 89514 65584 77082 24257 20544 50091 19862 13736

我要说两句: (0人参与)

发布