[笔记][Java7并发编程实战手册]2.5使用Lock实现同步二

[笔记][Java7并发编程实战手册]系列目录


概要

接上一篇文章,练习修改锁的公平性,和在所中使用条件。

修改锁的公平性ReentrantLock

    /**
     *构造一个锁对象,默认为非公平锁
     */
    public ReentrantLock(boolean fair) {
        sync = fair ? new FairSync() : new NonfairSync();
    }

根据ReentrantLock的构造可以看出来,默认会构造非公平锁;


公平锁与非公平锁有什么区别

  1. 公平锁 :有多个线程并发访问公平锁的临界区,这些锁会等待上一个锁释放锁之后被公平策略选择一个线程获取锁(该策略就是在CLH选择一个等待时间最长的线程来访问临界区)
  2. 非公平锁:没有任何条件约束
  3. 公平和非公平策略只适用于lock() 和unlock()方法。而Lock接口的tryLock方法没有将线程置于休眠,fair属性并不影响这个方法。

详细解说请参考 Java多线程系列–“JUC锁”03之 公平锁(一)


示例演示公平策略

编写一个打印方法。里面有两个临界区,为了方便测试当前线程被执行后,第二个临界区等待锁的时间。才能看出效果

public class Client {
    public static void main(String[] args) throws InterruptedException {
        final FairSyncDemo fsd = new FairSyncDemo();
        Thread[]  arr = new Thread[10];
        for (int i = 0;i < 10 ;i++){
            arr[i] = new Thread(new Runnable() {
                @Override
                public void run() {
                    fsd.print();
                }
            });
        }
        for (int i = 0;i < arr.length ;i++){
            arr[i].start();
            Thread.sleep(100);  //让线程启动的时间增加。让公平锁来选择谁等待最久
        }

    }
}
class FairSyncDemo {
    private ReentrantLock lock = new ReentrantLock(true);  //构造公平锁
    public void print(){
        lock.lock();
        Long time = (long)(Math.random() * 1000);
        System.out.println(Thread.currentThread().getName() + ",打印耗时" + time  + "毫秒");
        try {
            Thread.sleep(time);
        } catch (InterruptedException e) {
            e.printStackTrace();
        }
        lock.unlock();

        lock.lock();
        Long time2 = (long)(Math.random() * 1000);
        System.out.println(Thread.currentThread().getName() + ",stp2打印耗时" + time2  + "毫秒");
        try {
            Thread.sleep(time);
        } catch (InterruptedException e) {
            e.printStackTrace();
        }
        lock.unlock();
    }
}

某一次的运行结果:

Thread-0,打印耗时821毫秒
Thread-1,打印耗时23毫秒
Thread-2,打印耗时19毫秒
Thread-3,打印耗时455毫秒
Thread-4,打印耗时951毫秒
Thread-5,打印耗时857毫秒
Thread-6,打印耗时680毫秒
Thread-7,打印耗时36毫秒
Thread-8,打印耗时116毫秒
Thread-0,stp2打印耗时107毫秒
Thread-1,stp2打印耗时960毫秒
Thread-2,stp2打印耗时598毫秒
Thread-9,打印耗时236毫秒
Thread-3,stp2打印耗时969毫秒
Thread-4,stp2打印耗时774毫秒
Thread-5,stp2打印耗时657毫秒
Thread-6,stp2打印耗时766毫秒
Thread-7,stp2打印耗时83毫秒
Thread-8,stp2打印耗时782毫秒
Thread-9,stp2打印耗时745毫秒

结果说明:

我们启动十个线程的时候,每启动一个线程,睡眠100毫秒,

从Thread-0,打印耗时821毫秒 到 Thread-0,stp2打印耗时107毫秒,这段时间中。stp2等待的时间为线程1-8所运行的时间,等待时间大概是3000多毫秒了。(这里的一个策略我不太清楚)所以就被公平策略给选中了继续执行临界区代码。

下面的结果是非公平锁的运行结果:

修改为非公平锁: private ReentrantLock lock = new ReentrantLock(false); //构造公平锁

Thread-0,打印耗时178毫秒
Thread-0,stp2打印耗时492毫秒
Thread-1,打印耗时534毫秒
Thread-1,stp2打印耗时951毫秒
Thread-2,打印耗时422毫秒
Thread-2,stp2打印耗时882毫秒
Thread-3,打印耗时353毫秒
Thread-3,stp2打印耗时487毫秒
Thread-4,打印耗时130毫秒
Thread-4,stp2打印耗时74毫秒
Thread-5,打印耗时900毫秒
Thread-5,stp2打印耗时90毫秒
Thread-6,打印耗时568毫秒
Thread-7,打印耗时676毫秒
Thread-7,stp2打印耗时220毫秒
Thread-8,打印耗时511毫秒
Thread-8,stp2打印耗时998毫秒
Thread-9,打印耗时171毫秒
Thread-9,stp2打印耗时425毫秒
Thread-6,stp2打印耗时88毫秒

结果说明:

  很明显能看出来公平与飞公平的效果相差有多大。


在锁中使用多条件condition

在lock中提供了与之关联的条件,一个锁可能关联一个或则多个条件,这些条件通过condition接口声明。目的是运行线程获取锁并且查看等待某一个条件是否满足,如果不满足则挂起直到某个线程唤醒它们。condition接口提供了挂起线程和唤起线程的机制;


示例演示条件的使用

ok,介绍下下面一堆代码所做的事情:使用了5个线程设置(生产)一个数值,使用5个线程移除(消费)这个数值;

在add 和 remoe中使用Condition来代替监视器锁的wait操作。和唤醒操作。

值得注意的是:

1. ConDemo 这个内部类修改成Con 启动抛出找不到类的错误异常(不知道con是关键字还是什么原因)

2. 读写线程数量如果不对等,将会出现死锁。

3. 所有的条件Condition必须使用对等的锁对象来创建lock.newCondition();

4. 条件必须用在lock() 和 unlock() 方法之间。

5. 在判定条件是否满足,需要在循环中判定,未满足条件的不能离开循环体,否则数据将得不到我们想要的结果

6. 调用await()方法进入休眠的线程可能会被中断,所以必须处理InterruptedException 异常

public class Client {
    public static void main(String[] args) throws InterruptedException {
        final ConDemo con = new ConDemo();
        for (int i = 0; i < 5; i++) {
            new Thread(new Runnable() {
                @Override
                public void run() {
                    con.add();
                }
            }).start();
        }
        for (int i = 0; i < 5; i++) {
            new Thread(new Runnable() {
                @Override
                public void run() {
                    con.remove();
                }
            }).start();
        }
    }
}

class ConDemo {
    private ReentrantLock lock = new ReentrantLock(); //构造锁
    private Long depot = null;   //充当仓库,当有值的时候,会被remove走,并且设置为null。表示被取走了,等待add一个数
    private Condition removeCon;
    private Condition addCon;

    public ConDemo() {
        this.removeCon = lock.newCondition();
        this.addCon = lock.newCondition();
    }

    /**
     * 设置
     */
    public void add() {
        lock.lock();
        try {
            while (depot != null) {  //有值,则等待 被取走。后再设置值
                addCon.await();  //让当前线程操作等待
            }
            depot = (long) (Math.random() * 1000);
            System.out.println(Thread.currentThread().getName() + ",----设置了:" + depot);
            //唤醒条件,让等待取走值的线程操作
            removeCon.signalAll();
        } catch (InterruptedException e) {
            e.printStackTrace();
        } finally {
            lock.unlock();
        }
    }

    /**
     * 移除设置的数
     */
    public void remove() {
        lock.lock();
        try {
            while (depot == null) {  //没有值可取,则让条件等待
                removeCon.await();  //让移除操作等待
            }
            System.out.println(Thread.currentThread().getName() + ",拿走了:" + depot);
            depot = null;
            //唤醒条件,让等待设置值的线程。赶快设置值
            addCon.signalAll();
        } catch (InterruptedException e) {
            e.printStackTrace();
        } finally {
            lock.unlock();
        }

    }
}

某一次的运行结果:

Thread-2,----设置了:274
Thread-8,拿走了:274
Thread-1,----设置了:358
Thread-5,拿走了:358
Thread-0,----设置了:407
Thread-7,拿走了:407
Thread-3,----设置了:761
Thread-9,拿走了:761
Thread-4,----设置了:250
Thread-6,拿走了:250

结果说明:可以看出以上的结果:有值了,才能被取走,被取走了,才能被生产一个值。


condition中的其他api

await(long time, TimeUnit unit)

boolean await(long time, TimeUnit unit) throws InterruptedException;

该方法直到以下情况之一之前,线程将一直处于休眠状态:

1. 其他某个线程中断当前线程

2. 其他某个线程调用了将当前线程挂起的条件的singal()或singalAll()方法

3. 指定的等待时间已经过去


awaitUninterruptibly()

它是不可中断的。这个线程将休眠直到其他某个线程调用了将他挂起的条件的singal()或则signalAll()方法。

awaitUntil(Date date)

直到发生以下情况之一之前,线程将一直处于休眠状态。

1. 其他某个线程中断当前线程

2. 其他某个线程调用了将他挂起的条件的dingal()或signalAll()方法

3. 指定的最后期限到了

也可以将条件与读写锁一起使用。

版权声明:本文为博主原创文章,未经博主允许不得转载。

时间: 2024-07-31 14:31:34

[笔记][Java7并发编程实战手册]2.5使用Lock实现同步二的相关文章

[笔记][Java7并发编程实战手册]2.5使用Lock实现同步

lock详细解说请参考:Java多线程系列–"JUC锁"01之 框架 lock接口 是同步代码块的另一种机制,比synchronized关键字更强大也更灵活 提供了许多新功能,例如:tryLock()方法,这个方法试图获取锁,如果锁已被其他线程获取,它将返回false并往下继续执行代码.而使用synchronized,如果线程A试图执行一个同步代码块,如果B线程已经在执行这个代码块了,线程A将被挂起.等待B线程执行完 lock接口允许读写分离,允许多个读线程和只有一个写线程,当写操作的

[笔记][Java7并发编程实战手册]系列目录

Java7并发编程实战手册 这一本实战的书籍.本笔记记录是看了该书.并且简化了书中的示例.的一些随笔记录 我觉得能给我更好的感觉.我觉得先看博客中转载的多线程系列 Java多线程系列-目录源码分析和理论.有时候真的觉得好烦躁.可是,没有这些理论实战中又觉得太多的未知. 所以本人觉得.先粗略的过一遍理论和源码分析.再来看学习实战,在写代码的过程中,去回想和联想理论就能更好的把知识串联起来了: 也可以看到本人在记录这些笔记的时候也会引用到博客中转载的多线程系列文章. [笔记][Java7并发编程实战

[笔记][Java7并发编程实战手册]3.2 资源的并发访问控制Semaphore信号量

[笔记][Java7并发编程实战手册]系列目录 简介 本文学习信号量Semaphore机制. Semaphore 本质是一个共享锁 内部维护一个可用的信号集,获取信号量之前需要先申请获取信号数量:用完之后,则需要释放信号量:如果不释放,那么其他等待线程则一直阻塞直到获取信号量或则被中断为止 本人的理解是:互斥锁是同一时间只能一个线程访问,而在这里,是同一时间允许获取到了信号量的线程并发访问,而没有获取到信号量的则必须等待信号量的释放: 将信号量初始化为 1,使得它在使用时最多只有一个可用的许可,

[笔记][Java7并发编程实战手册]4.3 创建固定的线程执行器newFixedThreadPool线程池

[笔记][Java7并发编程实战手册]系列目录 简介 newFixedThreadPool(int nThreads, ThreadFactory threadFactory) 创建一个可重用固定线程数的线程池,以共享的无界队列方式来运行这些线程,在需要时使用提供的 ThreadFactory 创建新线程. newCachedThreadPool()创建的线程池的特性是:自动回收不使用的线程(终止并从缓存中移除那些已有 60 秒钟未被使用的线程),(在无可用线程的情况下)自动的为新来的task创

[笔记][Java7并发编程实战手册]3.3 资源的多副本并发访问控制Semaphore

[笔记][Java7并发编程实战手册]系列目录 简介 本文继续学习信号量Semaphore机制. 在3.2中其实已经讲解完了,之前对于信号量并发的使用场景不知道,看了本章节才想到一些: 下面就以 租车为列子来讲解并发访问的控制.(示例都很简单或许不符合现实逻辑) 信号量(非二进制信号量)是不保证同步的,需要额外的同步 示例 场景:有一个出租车公司,有三台车,有十个司机,每个司机工作的时间不一致,可以说是司机等待着别人还车后,接着租用汽车. /** * Created by zhuqiang on

[笔记][Java7并发编程实战手册]第三章-线程同步辅助类-概要

[笔记][Java7并发编程实战手册]系列目录 有点着急了,没有太注重质量,自己也没有理解透,从本章起,读书和随笔笔记的质量会更好. 第三章 在本章中,我们将学习: 资源的并发访问控制 资源的多副本的并发访问控制 等待多个并发事件的完成 在集合点的同步 并发阶段任务的运行 并发阶段任务中的阶段交换 并发任务间的数据交换 回顾 在第二章中主要学习了以下接口 synchronized关键字 Lock接口以及实现类,如ReentrantLock.ReentrantReadWriteLock中的Read

[笔记][Java7并发编程实战手册]4.4 在执行器中执行任务并返回结果Callable、Future

[笔记][Java7并发编程实战手册]系列目录 简介 执行框架(Executor Framework)的优势之一就是,可以在运行并发任务的时候返回结果.但是需要以下两个类来实现功能: 1. 接口 Callable<V> 返回结果并且可能抛出异常的任务.实现者定义了一个不带任何参数的叫做 call 的方法. Callable 接口类似于 Runnable,两者都是为那些其实例可能被另一个线程执行的类设计的.但是 Runnable 不会返回结果,并且无法抛出经过检查的异常. Executors 类

[笔记][Java7并发编程实战手册]4.5 运行多个任务并处理第一个结果ThreadPoolExecutor

[笔记][Java7并发编程实战手册]系列目录 简介 看到这个标题的时候,我也很纳闷,因为没有明白所表达的意思. ok,并发编程比较常见的一个问题是:当采用多个并发任务来解决一个问题的时候,往往只需要关心这个任务的第一个结果,例如:验证一个算法的时候,假如一个执行5个算法,那么最先返回结果的,就是最快的. 在本章将会学习,如何使用ThreadPoolExecutor来实现类似场景: 本章ThreadPoolExecutor使用心得 使用 ThreadPoolExecutor.invokeAny(

[笔记][Java7并发编程实战手册]2.4在同步代码中使用条件-生产者与消费者

说明 在并发编程中一个典型的问题是生产者–消费者问题.在程序中,有可能会需要用到两个线程通信的情况,比如生产者消费者中,获取一个共享数据,有就消费.没有就等待着生产者生产之后再继续消费.那么这个实现过程就可以使用wait();notify();notifyAll()来达到效果: 以上方法详细解说请查看: Java多线程系列–"基础篇"05之 线程等待与唤醒 例子 /** * Created by zhuqiang on 2015/8/8 0008. */ public class Cl