1. Lock 的简介及使用

Lock是java 1.5中引入的线程同步工具,它主要用于多线程下共享资源的控制。本质上Lock仅仅是一个接口(位于源码包中的java\util\concurrent\locks中),它包含以下方法

  1. //尝试获取锁,获取成功则返回,否则阻塞当前线程
  2. void lock();
  3.  
  4. //尝试获取锁,线程在成功获取锁之前被中断,则放弃获取锁,抛出异常
  5. void lockInterruptibly() throws InterruptedException;
  6.  
  7. //尝试获取锁,获取锁成功则返回true,否则返回false
  8. boolean tryLock();
  9.  
  10. //尝试获取锁,若在规定时间内获取到锁,则返回true,否则返回false,未获取锁之前被中断,则抛出异常
  11. boolean tryLock(long time, TimeUnit unit)
  12. throws InterruptedException;
  13.  
  14. //释放锁
  15. void unlock();
  16.  
  17. //返回当前锁的条件变量,通过条件变量可以实现类似notify和wait的功能,一个锁可以有多个条件变量
  18. Condition newCondition();

Lock有三个实现类,一个是ReentrantLock,另两个是ReentrantReadWriteLock类中的两个静态内部类ReadLock和WriteLock。

使用方法:多线程下访问(互斥)共享资源时, 访问前加锁,访问结束以后解锁,解锁的操作推荐放入finally块中。

  1. Lock l = ...; //根据不同的实现Lock接口类的构造函数得到一个锁对象
  2. l.lock(); //获取锁位于try块的外面
  3. try {
  4. // access the resource protected by this lock
  5. } finally {
  6. l.unlock();
  7. }

注意:加锁位于对资源访问的try块的外部,特别是使用lockInterruptibly方法加锁时就必须要这样做,这为了防止线程在获取锁时被中断,这时就不必(也不能)释放锁。

  1. try {
  2. l.lockInterruptibly();//获取锁失败时不会执行finally块中的unlock语句
  3. try{
  4. // access the resource protected by this lock
  5. }finally{
  6. l.unlock();
  7. }
  8. } catch (InterruptedException e) {
  9. // TODO Auto-generated catch block
  10. e.printStackTrace();
  11. }

2. 实现Lock接口的基本思想

需要实现锁的功能,两个必备元素:

  • 一个是表示(锁)状态的变量(我们假设0表示没有线程获取锁,1表示已有线程占有锁),该变量必须声明为voaltile类型;
  • 另一个是队列,队列中的节点表示因未能获取锁而阻塞的线程。

为了解决多核处理器下多线程缓存不一致的问题,表示状态的变量必须声明为voaltile类型,并且对表示状态的变量和队列的某些操作要保证原子性和可见性。原子性和可见性的操作主要通过Atomic包中的方法实现。

线程获取锁的大致过程(这里没有考虑可重入和获取锁过程被中断或超时的情况)

1. 读取表示锁状态的变量

2. 如果表示状态的变量的值为0,那么当前线程尝试将变量值设置为1(通过CAS操作完成),当多个线程同时将表示状态的变量值由0设置成1时,仅一个线程能成功,其它线程都会失败:

2.1 若成功,表示获取了锁,

2.1.1 如果该线程(或者说节点)已位于在队列中,则将其出列(并将下一个节点则变成了队列的头节点)

2.1.2 如果该线程未入列,则不用对队列进行维护

然后当前线程从lock方法中返回,对共享资源进行访问。

2.2 若失败,则当前线程将自身放入等待(锁的)队列中并阻塞自身,此时线程一直被阻塞在lock方法中,没有从该方法中返回(被唤醒后仍然在lock方法中,并从下一条语句继续执行,这里又会回到第1步重新开始)

3. 如果表示状态的变量的值为1,那么将当前线程放入等待队列中,然后将自身阻塞(被唤醒后仍然在lock方法中,并从下一条语句继续执行,这里又会回到第1步重新开始)

          注意: 唤醒并不表示线程能立刻运行,而是表示线程处于就绪状态,仅仅是可以运行而已

      线程释放锁的大致过程

1. 释放锁的线程将状态变量的值从1设置为0,并唤醒等待(锁)队列中的队首节点,释放锁的线程从就从unlock方法中返回,继续执行线程后面的代码

2. 被唤醒的线程(队列中的队首节点)和可能和未进入队列并且准备获取的线程竞争获取锁,重复获取锁的过程

注意:可能有多个线程同时竞争去获取锁,但是一次只能有一个线程去释放锁,队列中的节点都需要它的前一个节点将其唤醒,例如有队列A<-B-<C ,即由A释放锁时唤醒B,B释放锁时唤醒C

3. 公平锁和非公平锁

锁可以分为公平锁和不公平锁,重入锁和非重入锁(关于重入锁的介绍会在ReentrantLock源代码分析中介绍),以上过程实际上是非公平锁的获取和释放过程。

公平锁严格按照先来后到的顺去获取锁,而非公平锁允许插队获取锁。

公平锁获取锁的过程上有些不同,在使用公平锁时,某线程想要获取锁,不仅需要判断当前表示状态的变量的值是否为0,还要判断队列里是否还有其他线程,若队列中还有线程则说明当前线程需要排队,进行入列操作,并将自身阻塞;若队列为空,才能尝试去获取锁。而对于非公平锁,当表示状态的变量的值是为0,就可以尝试获取锁,不必理会队列是否为空,这样就实现了插队获取锁的特点。通常来说非公平锁的吞吐率比公平锁要高,我们一般常用非公平锁。

这里需要解释一点,什么情况下才会出现,表示锁的状态的变量的值是为0而且队列中仍有其它线程等待获取锁的情况。

假设有三个线程A、B、C。A线程为正在运行的线程并持有锁,队列中有一个C线程,位于队首。现在A线程要释放锁,具体执行的过程操作可分为两步:

1. 将表示锁状态的变量值由1变为0,

2. C线程被唤醒,这里要明确两点:

              (1)C线程被唤醒并不代表C线程开始执行,C线程此时是处于就绪状态,要等待操作系统的调度

              (2)C线程目前还并未出列,C线程要进入运行状态,并且通过竞争获取到锁以后才会出列。

如果C线程此时还没有进入运行态,同时未在队列中的B线程进行获取锁的操作,B就会发现虽然当前没有线程持有锁,但是队列不为空(C线程仍然位于队列中),要满足先来后到的特点(B在C之后执行获取锁的操作),B线程就不能去尝试获取锁,而是进行入列操作。

4. 实现Condition接口的基本思想

Condition 本质是一个接口,它包含如下方法

  1. // 让线程进入等通知待状态
  2. void await() throws InterruptedException;
  3. void awaitUninterruptibly();
  4.  
  5. //让线程进入等待通知状态,超时结束等待状态,并抛出异常
  6. long awaitNanos(long nanosTimeout) throws InterruptedException;
  7. boolean await(long time, TimeUnit unit) throws InterruptedException;
  8. boolean awaitUntil(Date deadline) throws InterruptedException;
  9.  
  10. //将条件队列中的一个线程,从等待通知状态转换为等待锁状态
  11. void signal();
  12.  
  13. //将条件队列中的所有线程,从等待通知阻塞状态转换为等待锁阻塞状态
  14. void signalAll();

一个Condition实例的内部实际上维护了一个队列,队列中的节点表示由于(某些条件不满足而)线程自身调用await方法阻塞的线程。Condition接口中有两个重要的方法,即 await方法和 signal方法。线程调用这个方法之前该线程必须已经获取了Condition实例所依附的锁。这样的原因有两个,(1)对于await方法,它内部会执行释放锁的操作,所以使用前必须获取锁。(2)对于signal方法,是为了避免多个线程同时调用同一个Condition实例的singal方法时引起的(队列)出列竞争。下面是这两个方法的执行流程。

await方法:

1. 入列到条件队列(注意这里不是等待锁的队列

2. 释放锁

3. 阻塞自身线程

------------被唤醒后执行-------------

4. 尝试去获取锁(执行到这里时线程已不在条件队列中,而是位于等待(锁的)队列中,参见signal方法)

4.1 成功,从await方法中返回,执行线程后面的代码

4.2 失败,阻塞自己(等待前一个节点释放锁时将它唤醒)

注意:await方法时自身线程调用的,线程在await方法中阻塞,并没有从await方法中返回,当唤醒后继续执行await方法中后面的代码(也就是获取锁的代码)。可以看出await方法释放了锁,又尝试获得锁。当获取锁不成功的时候当前线程仍然会阻塞到await方法中,等待前一个节点释放锁后再将其唤醒。

signal方法:

1. 将条件队列的队首节点取出,放入等待锁队列的队尾

2. 唤醒该节点对应的线程

注意:signal是由其它线程调用

Lock和Condition的使用例程

下面这个例子,就是利用lock和condition实现B线程先打印一句信息后,然后A线程打印两句信息(不能中断),交替十次后结束

  1. public class ConditionDemo {
  2. volatile int key = 0;
  3. Lock l = new ReentrantLock();
  4. Condition c = l.newCondition();
  5.  
  6. public static void main(String[] args){
  7. ConditionDemo demo = new ConditionDemo();
  8. new Thread(demo.new A()).start();
  9. new Thread(demo.new B()).start();
  10. }
  11.  
  12. class A implements Runnable{
  13. @Override
  14. public void run() {
  15. int i = 10;
  16. while(i > 0){
  17. l.lock();
  18. try{
  19. if(key == 1){
  20. System.out.println("A is Running");
  21. System.out.println("A is Running");
  22. i--;
  23. key = 0;
  24. c.signal();
  25. }else{
  26. c.awaitUninterruptibly();
  27. }
  28.  
  29. }
  30. finally{
  31. l.unlock();
  32. }
  33. }
  34. }
  35.  
  36. }
  37.  
  38. class B implements Runnable{
  39. @Override
  40. public void run() {
  41. int i = 10;
  42. while(i > 0){
  43. l.lock();
  44. try{
  45. if(key == 0){
  46. System.out.println("B is Running");
  47. i--;
  48. key = 1;
  49. c.signal();
  50. }else{
  51. c.awaitUninterruptibly();
  52. }
  53.  
  54. }
  55. finally{
  56. l.unlock();
  57. }
  58. }
  59. }
  60. }
  61. }

5. Lock与synchronized的区别

1. Lock的加锁和解锁都是由java代码配合native方法(调用操作系统的相关方法)实现的,而synchronize的加锁和解锁的过程是由JVM管理的

2. 当一个线程使用synchronize获取锁时,若锁被其他线程占用着,那么当前只能被阻塞,直到成功获取锁。而Lock则提供超时锁和可中断等更加灵活的方式,在未能获取锁的     条件下提供一种退出的机制。

3. 一个锁内部可以有多个Condition实例,即有多路条件队列,而synchronize只有一路条件队列;同样Condition也提供灵活的阻塞方式,在未获得通知之前可以通过中断线程以    及设置等待时限等方式退出条件队列。

4. synchronize对线程的同步仅提供独占模式,而Lock即可以提供独占模式,也可以提供共享模式

Android控件之GridView的更多相关文章

  1. Android控件之GridView探究

    GridView是一项显示二维的viewgroup,可滚动的网格.一般用来显示多张图片. 以下模拟九宫图的实现,当鼠标点击图片时会进行相应的跳转链接. 目录结构 main.xml布局文件,存放Grid ...

  2. Android控件Gridview实现仿支付宝首页,Fragment底部按钮切换和登录圆形头像

    此案例主要讲的是Android控件Gridview(九宫格)完美实现仿支付宝首页,包含添加和删除功能:Fragment底部按钮切换的效果,包含四个模块,登录页面圆形头像等,一个小项目的初始布局. 效果 ...

  3. Android基本控件之GridView

    我们在使用手机的过程中,会看到一些图片配上文字的一些情况,今天我们就来介绍一下安卓控件的GridView GridView组件用来以网格方式排列视图,与矩阵类似,当屏幕上有很多元素(文字.图片或其他元 ...

  4. Android控件GridView之仿支付宝钱包首页带有分割线的GridView九宫格的完美实现

    Android控件GridView之仿支付宝钱包首页带有分割线的GridView九宫格的完美实现 2015-03-10 22:38 28419人阅读 评论(17) 收藏 举报  分类: Android ...

  5. Android控件Gridview实现多个menu模块,可添加可删除

    此案例主要讲的是Android控件Gridview(九宫格)完美实现仿支付宝首页,包含添加和删除功能:Fragment底部按钮切换的效果,包含四个模块,登录页面圆形头像等,一个小项目的初始布局. 效果 ...

  6. Android控件介绍

    1. 介绍 Android控件大多位于android.widget, android.view.View为他们的父类对于Dialog系列, android.app.Dialog为父类 Android的 ...

  7. Android控件RecyclerView的基本用法

    Android控件RecyclerView的基本用法 转 https://www.jianshu.com/p/e71a4b73098f   github: https://github.com/Cym ...

  8. [Android Pro] android控件ListView顶部或者底部也显示分割线

    reference to  :  http://blog.csdn.net/lovexieyuan520/article/details/50846569 在默认的Android控件ListView在 ...

  9. Android 控件架构及View、ViewGroup的测量

    附录:示例代码地址 控件在Android开发的过程中是必不可少的,无论是我们在使用系统控件还是自定义的控件.下面我们将讲解一下Android的控件架构,以及如何实现自定义控件. 1.Android控件 ...

随机推荐

  1. asp.net后台获取路径的各种方法归纳

    asp.net后台获取路径的各种方法归纳   1.Request.CurrentExecutionFilePath    获取当前请求的虚拟路径,不同于 FilePath,差别在于如果请求已在服务器代 ...

  2. http://www.aboutyun.com/thread-8792-1-1.html

    http://www.aboutyun.com/thread-8792-1-1.html

  3. floodlight StaticFlowPusher 基于网段写flow,通配

    flow1 = { "switch":"00:00:00:00:00:00:00:03", "name":"flow-mod-1& ...

  4. cojs 疯狂的求和问题 解题报告

    QAQ 好久不在cojs上出题了 最近学了点新科技,于是就做成题来分享了 这道题是要求simga(i^k) 那么就先说说部分分的算法吧: 10分: 直接暴力就可以了,时间复杂度O(nlogk) 30分 ...

  5. lintcode:Valid Sudoku 判断数独是否合法

    题目: 判断数独是否合法 请判定一个数独是否有效.该数独可能只填充了部分数字,其中缺少的数字用 . 表示. 样例 下列就是一个合法数独的样例. 注意 一个合法的数独(仅部分填充)并不一定是可解的.我们 ...

  6. iOS开发网络篇-NSURLSession介绍

    NSURLSession: 作用: 和NSURLConnection一样 1.负责发送请求,建立客户端和服务器的连接发送数据给服务器 2.并收集来自服务器的响应数据 步骤: 1.创建NSURLSess ...

  7. React Jest测试

    一. var jest = require('jest'); jest.dontMock('../CheckboxWithLabel.js'); describe('CheckboxWithLabel ...

  8. 编写自己的TRACE函数

    TRACE函数是MFC里面的一个宏,是对OutputDebugString的封装. OutputDebugString的作用是输出调试信息,不要以为这个函数只有在Debug版本才会打日志,即使是Rel ...

  9. 253. Meeting Rooms II

    题目: Given an array of meeting time intervals consisting of start and end times [[s1,e1],[s2,e2],...] ...

  10. 网站TCP链接暴增

    昨天上线后,TCP链接暴增,红点增多. 问题在查.其中有一部分,多线程修改,突破了线程数 64的限制.线程内,会发起网络请求. 怀疑是热点之一.其他的部分也有修改,也被怀疑.准备下次,2部分分开上线. ...