Java多线程(三)之 Lock 锁_lock.unlock();-程序员宅基地

技术标签: java  Java SE  开发语言  

一、介绍

Jdk1.5以后,在java.util.concurrent.locks包下,有一组实现线程同步的接口和类,说到线程的同步,可能大家都会想到synchronized关键字,

这是java内置的关键字,用来处理线程同步的,但这个关键字有很多的缺陷,使用起来也不是很方便和直观,所以就出现了Lock,下面,我们

就来对比着讲解Lock。

通常我们在使用synchronized关键字的时候会遇到下面这些问题:

  1. 不可控性,无法做到随心的加锁和释放锁。
  2. 效率比较低下,比如我们现在并发的读两个文件,读与读之间是互不影响的,但如果给这个读的对象使用synchronized来实现同步的话,那么只要有一个线程进入了,那么其他的线程都要等待。
  3. 无法知道线程是否获取到了锁。

而上面synchronized的这些问题,Lock都可以很好的解决,并且jdk1.5以后,还提供了各种锁,例如读写锁,但有一点需要注意,使用 synchronized 关键时,无须手动释放锁,但使用 Lock 必须手动释放锁。下面我们就来学习一下Lock锁。

Lock是一个上层的接口,其原型如下,总共提供了6个方法:

public interface Lock {
  // 用来获取锁,如果锁已经被其他线程获取,则一直等待,直到获取到锁
   void lock();
  // 该方法获取锁时,可以响应中断,比如现在有两个线程,一个已经获取到了锁,另一个线程调用这个方法正在等待锁,但是此刻又不想让这个线程一直在这死等,可以通过
    调用线程的Thread.interrupted()方法,来中断线程的等待过程
  void lockInterruptibly() throws InterruptedException;
  // tryLock方法会返回bool值,该方法会尝试着获取锁,如果获取到锁,就返回true,如果没有获取到锁,就返回false,但是该方法会立刻返回,而不会一直等待
   boolean tryLock();
  // 这个方法和上面的tryLock差不多是一样的,只是会尝试指定的时间,如果在指定的时间内拿到了锁,则会返回true,如果在指定的时间内没有拿到锁,则会返回false
   boolean tryLock(long time, TimeUnit unit) throws InterruptedException;
  // 释放锁
   void unlock();
  // 实现线程通信,相当于wait和notify,后面会单独讲解
   Condition newCondition();
}

二、ReentrantLock

java.util.concurrent.lock 中的 Lock 框架是锁定的一个抽象,它允许把锁定的实现作为 Java 类,而不是作为语言的特性来实现。这就为 Lock 的多种实现留下了空间,各种实现可能有不同的调度算法、性能特性或者锁定语义。 ReentrantLock 类实现了 Lock ,它拥有与 synchronized 相同的并发性和内存语义,但是添加了类似轮询锁、定时锁等候和可中断锁等候的一些特性。此外,它还提供了在激烈争用情况下更佳的性能。(换句话说,当许多线程都想访问共享资源时,JVM 可以花更少的时候来调度线程,把更多时间用在执行线程上。)

reentrant 锁意味着什么呢?

简单来说,它有一个与锁相关的获取计数器,如果拥有锁的某个线程再次得到锁,那么获取计数器就加1,然后锁需要被释放两次才能获得真正释放。这模仿了 synchronized 的语义;如果线程进入由线程已经拥有的监控器保护的 synchronized 块,就允许线程继续进行,当线程退出第二个(或者后续) synchronized 块的时候,不释放锁,只有线程退出它进入的监控器保护的第一个 synchronized 块时,才释放锁。

ReentrantLock构造器(有两个)

public ReentrantLock()默认平策略锁

public ReentrantLock(Boolean):可选策略模式:true【公平策略】false【不公平策略】

非公平策略:由cpu调度指派(无法判断cpu调度顺序)无法保证等待线程获取锁的顺序

公平策略:等待锁的线程,按先到先得的方式公平用锁

三、lock,unlock的使用

lock.lock(); lock.unlock(); 包起来的代码,就和  synchronized 是一样的效果。 
同一个lock对象锁是同一把;代码中serviceA ,serviceB 中的lock是同一把锁哦,谁先获取谁就有控制权。

由于不像 synchronized(异常情况会自动释放锁),lock【必须要手动释放锁】,所以一定要使用【try,finally】将执行业务包起来,避免异常情况下无法释放锁导致死锁。

lock.lock();
try {
    //需要执行的业务代码
} finally {
    lock.unlock();
} 
package cn.thread.lock.reentrant;

import java.util.concurrent.locks.Lock;
import java.util.concurrent.locks.ReentrantLock;

class MyService {
    //使用ReentrantLock
    private Lock lock = new ReentrantLock();

    public void serviceA() {
        lock.lock();//serviceA ,serviceB 中的lock是同一把锁哦,谁先获取谁就有控制权。
        try {
            for (int i = 0; i < 3; i++) {
                Thread.sleep(100);
                System.out.println(Thread.currentThread().getName() + "----Ai=" + i);
            }
        } catch (InterruptedException e) {
            e.printStackTrace();
        } finally {
            lock.unlock();
        }
    }

    public void serviceB() {
        lock.lock();
        try {
            for (int i = 0; i < 3; i++) {
                Thread.sleep(100);
                System.out.println(Thread.currentThread().getName() + "----Bi=" + i);
            }
        } catch (InterruptedException e) {
            e.printStackTrace();
        } finally {
            lock.unlock();
        }
    }
}

public class ReentrantDome1 {

    public static void main(String orgs[]) {
        final MyService start = new MyService();
        new Thread(new Runnable() {
            public void run() {
                start.serviceA();
            }
        }).start();

        new Thread(new Runnable() {
            public void run() {
                start.serviceB();
            }
        }).start();

        new Thread(new Runnable() {
            public void run() {
                start.serviceA();
            }
        }).start();
    }
}

三、Condition接口实现线程通信 await()、signal()、signalAll

Condition Object 监视器方法(waitnotifynotifyAll)分解成截然不同的对象,以便通过将这些对象与任意 Lock 实现组合使用,为每个对象提供多个等待 set(wait-set)

Lock 替代了 synchronized 方法和语句的使用。

Condition 替代了 Object 监视器方法的使用

  • await 等价于 wait
  • signal 等价于 notify
  • signalAll 等价于 notifyAll

Condition 实例实质上被绑定到一个锁上。要为特定 Lock 实例获得 Condition 实例,请使用其 new Condition() 方法。

案例一

作为一个示例,假定有一个绑定的缓冲区,它支持 puttake 方法。如果试图在空的缓冲区上执行 take 操作,则在某一个项变得可用之前,线程将一直阻塞;如果试图在满的缓冲区上执行 put 操作,则在有空间变得可用之前,线程将一直阻塞。我们喜欢在单独的等待 set 中保存 put 线程和 take 线程,这样就可以在缓冲区中的项或空间变得可用时利用最佳规划,一次只通知一个线程。可以使用两个 Condition 实例来做到这一点。

注意:不用 if 而是使用 while 进行判断,目的是为了防止虚假唤醒问题(官方说明写法)

class BoundedBuffer {
   final Lock lock = new ReentrantLock();
   final Condition notFull  = lock.newCondition(); 
   final Condition notEmpty = lock.newCondition(); 

   final Object[] items = new Object[100];
   int putptr, takeptr, count;

   public void put(Object x) throws InterruptedException {
     lock.lock();
     try {
       while (count == items.length) 
         notFull.await();
       items[putptr] = x; 
       if (++putptr == items.length) putptr = 0;
       ++count;
       notEmpty.signal();
     } finally {
       lock.unlock();
     }
   }

   public Object take() throws InterruptedException {
     lock.lock();
     try {
       while (count == 0) 
         notEmpty.await();
       Object x = items[takeptr]; 
       if (++takeptr == items.length) takeptr = 0;
       --count;
       notFull.signal();
       return x;
     } finally {
       lock.unlock();
     }
   } 
 }

案例二

A线程先运行,获取到了锁,但是进入await之后,交出锁权限,线程挂起;B线程获取到锁,B线程唤醒A线程,B线程运行完毕,A线程接着运行。

package cn.thread.lock.reentrant;

import java.util.concurrent.locks.Condition;
import java.util.concurrent.locks.Lock;
import java.util.concurrent.locks.ReentrantLock;

class MyService2 {

    private Lock lock = new ReentrantLock();
    private Condition condition = lock.newCondition();

    public void serviceA() {
        try {
            lock.lock();
            condition.await();//线程挂起,进入等待中,和wait一样。
            System.out.println(Thread.currentThread().getName() + "----Ai=");
        } catch (InterruptedException e) {
            e.printStackTrace();
        } finally {
            lock.unlock();
        }
    }

    public void serviceB() {
        try {
            lock.lock();
            System.out.println(Thread.currentThread().getName() + "----Bi=");
            condition.signal();//唤醒被await的线程
        } catch (Exception e) {
            e.printStackTrace();
        } finally {
            lock.unlock();
        }

    }
}

public class ReentrantDome2 {

    public static void main(String orgs[]) {
        final MyService2 start = new MyService2();
        new Thread(new Runnable() {
            public void run() {
                start.serviceA();
            }
        }).start();

        new Thread(new Runnable() {
            public void run() {
                start.serviceB();
            }
        }).start();
    }
}

版权声明:本文为博主原创文章,遵循 CC 4.0 BY-SA 版权协议,转载请附上原文出处链接和本声明。
本文链接:https://blog.csdn.net/fox_bert/article/details/99948203

智能推荐

使用nginx解决浏览器跨域问题_nginx不停的xhr-程序员宅基地

文章浏览阅读1k次。通过使用ajax方法跨域请求是浏览器所不允许的,浏览器出于安全考虑是禁止的。警告信息如下:不过jQuery对跨域问题也有解决方案,使用jsonp的方式解决,方法如下:$.ajax({ async:false, url: 'http://www.mysite.com/demo.do', // 跨域URL ty..._nginx不停的xhr

在 Oracle 中配置 extproc 以访问 ST_Geometry-程序员宅基地

文章浏览阅读2k次。关于在 Oracle 中配置 extproc 以访问 ST_Geometry,也就是我们所说的 使用空间SQL 的方法,官方文档链接如下。http://desktop.arcgis.com/zh-cn/arcmap/latest/manage-data/gdbs-in-oracle/configure-oracle-extproc.htm其实简单总结一下,主要就分为以下几个步骤。..._extproc

Linux C++ gbk转为utf-8_linux c++ gbk->utf8-程序员宅基地

文章浏览阅读1.5w次。linux下没有上面的两个函数,需要使用函数 mbstowcs和wcstombsmbstowcs将多字节编码转换为宽字节编码wcstombs将宽字节编码转换为多字节编码这两个函数,转换过程中受到系统编码类型的影响,需要通过设置来设定转换前和转换后的编码类型。通过函数setlocale进行系统编码的设置。linux下输入命名locale -a查看系统支持的编码_linux c++ gbk->utf8

IMP-00009: 导出文件异常结束-程序员宅基地

文章浏览阅读750次。今天准备从生产库向测试库进行数据导入,结果在imp导入的时候遇到“ IMP-00009:导出文件异常结束” 错误,google一下,发现可能有如下原因导致imp的数据太大,没有写buffer和commit两个数据库字符集不同从低版本exp的dmp文件,向高版本imp导出的dmp文件出错传输dmp文件时,文件损坏解决办法:imp时指定..._imp-00009导出文件异常结束

python程序员需要深入掌握的技能_Python用数据说明程序员需要掌握的技能-程序员宅基地

文章浏览阅读143次。当下是一个大数据的时代,各个行业都离不开数据的支持。因此,网络爬虫就应运而生。网络爬虫当下最为火热的是Python,Python开发爬虫相对简单,而且功能库相当完善,力压众多开发语言。本次教程我们爬取前程无忧的招聘信息来分析Python程序员需要掌握那些编程技术。首先在谷歌浏览器打开前程无忧的首页,按F12打开浏览器的开发者工具。浏览器开发者工具是用于捕捉网站的请求信息,通过分析请求信息可以了解请..._初级python程序员能力要求

Spring @Service生成bean名称的规则(当类的名字是以两个或以上的大写字母开头的话,bean的名字会与类名保持一致)_@service beanname-程序员宅基地

文章浏览阅读7.6k次,点赞2次,收藏6次。@Service标注的bean,类名:ABDemoService查看源码后发现,原来是经过一个特殊处理:当类的名字是以两个或以上的大写字母开头的话,bean的名字会与类名保持一致public class AnnotationBeanNameGenerator implements BeanNameGenerator { private static final String C..._@service beanname

随便推点

二叉树的各种创建方法_二叉树的建立-程序员宅基地

文章浏览阅读6.9w次,点赞73次,收藏463次。1.前序创建#include&lt;stdio.h&gt;#include&lt;string.h&gt;#include&lt;stdlib.h&gt;#include&lt;malloc.h&gt;#include&lt;iostream&gt;#include&lt;stack&gt;#include&lt;queue&gt;using namespace std;typed_二叉树的建立

解决asp.net导出excel时中文文件名乱码_asp.net utf8 导出中文字符乱码-程序员宅基地

文章浏览阅读7.1k次。在Asp.net上使用Excel导出功能,如果文件名出现中文,便会以乱码视之。 解决方法: fileName = HttpUtility.UrlEncode(fileName, System.Text.Encoding.UTF8);_asp.net utf8 导出中文字符乱码

笔记-编译原理-实验一-词法分析器设计_对pl/0作以下修改扩充。增加单词-程序员宅基地

文章浏览阅读2.1k次,点赞4次,收藏23次。第一次实验 词法分析实验报告设计思想词法分析的主要任务是根据文法的词汇表以及对应约定的编码进行一定的识别,找出文件中所有的合法的单词,并给出一定的信息作为最后的结果,用于后续语法分析程序的使用;本实验针对 PL/0 语言 的文法、词汇表编写一个词法分析程序,对于每个单词根据词汇表输出: (单词种类, 单词的值) 二元对。词汇表:种别编码单词符号助记符0beginb..._对pl/0作以下修改扩充。增加单词

android adb shell 权限,android adb shell权限被拒绝-程序员宅基地

文章浏览阅读773次。我在使用adb.exe时遇到了麻烦.我想使用与bash相同的adb.exe shell提示符,所以我决定更改默认的bash二进制文件(当然二进制文件是交叉编译的,一切都很完美)更改bash二进制文件遵循以下顺序> adb remount> adb push bash / system / bin /> adb shell> cd / system / bin> chm..._adb shell mv 权限

投影仪-相机标定_相机-投影仪标定-程序员宅基地

文章浏览阅读6.8k次,点赞12次,收藏125次。1. 单目相机标定引言相机标定已经研究多年,标定的算法可以分为基于摄影测量的标定和自标定。其中,应用最为广泛的还是张正友标定法。这是一种简单灵活、高鲁棒性、低成本的相机标定算法。仅需要一台相机和一块平面标定板构建相机标定系统,在标定过程中,相机拍摄多个角度下(至少两个角度,推荐10~20个角度)的标定板图像(相机和标定板都可以移动),即可对相机的内外参数进行标定。下面介绍张氏标定法(以下也这么称呼)的原理。原理相机模型和单应矩阵相机标定,就是对相机的内外参数进行计算的过程,从而得到物体到图像的投影_相机-投影仪标定

Wayland架构、渲染、硬件支持-程序员宅基地

文章浏览阅读2.2k次。文章目录Wayland 架构Wayland 渲染Wayland的 硬件支持简 述: 翻译一篇关于和 wayland 有关的技术文章, 其英文标题为Wayland Architecture .Wayland 架构若是想要更好的理解 Wayland 架构及其与 X (X11 or X Window System) 结构;一种很好的方法是将事件从输入设备就开始跟踪, 查看期间所有的屏幕上出现的变化。这就是我们现在对 X 的理解。 内核是从一个输入设备中获取一个事件,并通过 evdev 输入_wayland

推荐文章

热门文章

相关标签