我们都知道在java中,当多个线程需要并发访问共享资源时需要使用同步,我们经常使用的同步方式就是synchronized关键字,事实上,在jdk1.5之前,只有synchronized一种同步方式。而在jdk1.5中提供了一种新的同步方式--显示锁(Lock)。显示锁是随java.util.concurrent包一起发布的,java.util.concurrent包是并发大神Doug Lea写的一个并发工具包,里面除了显示锁,还有许多其他的实用并发工具类。

什么是显示锁

  什么是显示锁?用一段代码来说明:

package com.gome;

import java.util.concurrent.locks.Lock;
import java.util.concurrent.locks.ReentrantLock; public class LockPractice {
private static int a=0;
private static Lock lock=new ReentrantLock(); public static void increateBySynchronized(){
a=0;
for (int i = 0; i < 1000; i++) {
Thread t=new Thread(new Runnable() {
public void run() {
for (int j = 0; j < 100; j++) {
synchronized (LockPractice.class) {
a++;
}
}
}
});
t.start();
}
while (Thread.activeCount()>1) {
Thread.yield();
}
System.out.println(a);
} public static void increateByLock(){
a=0;
for (int i = 0; i < 1000; i++) {
Thread t=new Thread(new Runnable() {
public void run() {
for (int j = 0; j < 100; j++) {
lock.lock();
try {
a++;
} finally {
lock.unlock();
}
}
}
});
t.start();
}
while (Thread.activeCount()>1) {
Thread.yield();
}
System.out.println(a);
} public static void main(String[] args) {
increateBySynchronized();
increateByLock();
}
}

执行结果:

解释:

类LockPractice 中有两个方法increateBySynchronized()和increateByLock(),这两个方法都成功地用多线程并发将a累加到100000而没有出现竞态条件(race condition)问题。

其中increateBySynchronized()的同步是我们熟悉的synchronized关键字实现的:

synchronized (LockPractice.class) {
  a++;
}

这句代码的含义是:当有一个线程A进入synchronized代码块后,阻塞其他要进入该代码块的线程直到A执行完代码块。synchronized关键字会关联一个锁对象,这里是LockPractice.class。synchronized关键字底层是由jvm来实现的,当一个线程进入synchronized块时,会在关联的锁对象的对象头(MarkWord)中记录下线程信息(可以简单的理解为线程id),这样这个锁对象就被当前线程独占了,其他试图获取这个锁对象的线程将被阻塞。

因此一个线程进入、退出synchronized代码块的本质就是这个线程对锁对象的获取、释放。

而increateByLock()的同步代码如下,其中 lock是全局变量 private static Lock lock=new ReentrantLock();

lock.lock();
try {
a++;
} finally {
lock.unlock();
}

从代码上可以看出,显示锁Lock的使用和synchronized的本质很像,也是定义了一个锁对象(new ReentrantLock()),然后在进入同步代码前加锁,执行同步代码后释放锁。

但是显示锁的底层却和synchronized完全不同,并没有使用到对象头(MarkWord)这样底层的东西,显示锁只是表现出了和synchronized一样的行为(第一个访问同步代码的线程获得锁,阻塞后来的线程)。

显示锁的优点

从上面的描述来看,显示锁实现了和synchronized一样的功能,但是写起来更复杂(需要手动加锁解锁,还需要写finally防止发生异常后锁不能释放),那为什么还要加入显示锁呢?

我们可以从Lock接口提供的方法看出端倪:

方法名称 描述
void lock() 获取锁
void lockInterruptibly() throws InterruptedException 可中断地获取锁,在线程获取锁的过程中可以响应中断
boolean tryLock()   尝试非阻塞获取锁,调用方法后立即返回,成功返回true,失败返回false
boolean tryLock(long time, TimeUnit unit) throws InterruptedException 在超时时间内获取锁,到达超时时间将返回false,也可以响应中断
void unlock(); 释放锁
Condition newCondition(); 获取等待组件,等待组件实现类似于Object.wait()方法的功能

从Lock提供的接口可以看出来,显示锁至少比synchronized多了以下功能:

  1. 可中断获取锁:使用synchronized关键字获取锁的时候,如果线程没有获取到被阻塞了,那么这个时候该线程是不响应中断(interrupt)的,而使用Lock.lockInterruptibly()获取锁时被中断,线程将抛出中断异常。
  2. 可非阻塞获取锁:使用sync关键字获取锁时,如果没有成功获取,只有被阻塞,而使用Lock.tryLock()获取锁时,如果没有获取成功也不会阻塞而是直接返回false。
  3. 可限定获取锁的超时时间:使用Lock.tryLock(long time, TimeUnit unit)。
  4. 其实显示锁还有其他的优势,比如同一锁对象上可以有多个等待队列(相当于Object.wait()),我们后面会讲。

其实除了更多的功能,显示锁还有一个很大的优势:synchronized的同步是jvm底层实现的,对一般程序员来说程序遇到出乎意料的行为的时候,除了查官方文档几乎没有别的办法;而显示锁除了个别操作用了底层的Unsafe类之外,几乎都是用java语言实现的,我们可以通过学习显示锁的源码,来更加得心应手的使用显示锁。

显示锁的缺点

当然显示锁也不是完美的,否则java就不会保留着synchronized关键字了,显示锁的缺点主要有两个:

  1. 使用比较复杂,这点之前提到了,需要手动加锁,解锁,而且还必须保证在异常状态下也要能够解锁。而synchronized的使用就简单多了。
  2. 效率较低,synchronized关键字毕竟是jvm底层实现的,因此用了很多优化措施来优化速度(偏向锁、轻量锁等),而显示锁的效率相对低一些。

因此当需要进行同步时,优先考虑使用synchronized关键字,只有synchronized关键字不能满足需求时,才考虑使用显示锁。

总结

这篇文章介绍了显示锁是什么,显示锁的优点与缺点,在什么情况下会用到显示锁。

后文将重点学习显示锁的底层实现:队列同步器(AbstractQueuedSynchronizer)的实现、重入锁(ReentrantLock)的实现、读写锁(ReadWriteLock)的实现、等待/通知(Condition)的实现。

Java显式锁学习总结之一:概论的更多相关文章

  1. Java显式锁学习总结之二:使用AbstractQueuedSynchronizer构建同步组件

    Jdk1.5中包含了并发大神Doug Lea写的并发工具包java.util.concurrent,这个工具包中包含了显示锁和其他的实用同步组件.Doug Lea在构建锁和组件的时候,大多是以队列同步 ...

  2. Java显式锁学习总结之六:Condition源码分析

    概述 先来回顾一下java中的等待/通知机制 我们有时会遇到这样的场景:线程A执行到某个点的时候,因为某个条件condition不满足,需要线程A暂停:等到线程B修改了条件condition,使con ...

  3. Java显式锁学习总结之五:ReentrantReadWriteLock源码分析

    概述 我们在介绍AbstractQueuedSynchronizer的时候介绍过,AQS支持独占式同步状态获取/释放.共享式同步状态获取/释放两种模式,对应的典型应用分别是ReentrantLock和 ...

  4. Java显式锁学习总结之四:ReentrantLock源码分析

    概述 ReentrantLock,即重入锁,是一个和synchronized关键字等价的,支持线程重入的互斥锁.只是在synchronized已有功能基础上添加了一些扩展功能. 除了支持可中断获取锁. ...

  5. Java显式锁学习总结之三:AbstractQueuedSynchronizer的实现原理

    概述 上一篇我们讲了AQS的使用,这一篇讲AQS的内部实现原理. 我们前面介绍了,AQS使用一个int变量state表示同步状态,使用一个隐式的FIFO同步队列(隐式队列就是并没有声明这样一个队列,只 ...

  6. Java显式锁

    Java 显式锁. 一.显式锁 什么是显式锁? 由自己手动获取锁,然后手动释放的锁. 有了 synchronized(内置锁) 为什么还要 Lock(显示锁)? 使用 synchronized 关键字 ...

  7. Java并发编程之显式锁机制

    我们之前介绍过synchronized关键字实现程序的原子性操作,它的内部也是一种加锁和解锁机制,是一种声明式的编程方式,我们只需要对方法或者代码块进行声明,Java内部帮我们在调用方法之前和结束时加 ...

  8. “全栈2019”Java多线程第三十二章:显式锁Lock等待唤醒机制详解

    难度 初级 学习时间 10分钟 适合人群 零基础 开发语言 Java 开发环境 JDK v11 IntelliJ IDEA v2018.3 文章原文链接 "全栈2019"Java多 ...

  9. “全栈2019”Java多线程第三十一章:中断正在等待显式锁的线程

    难度 初级 学习时间 10分钟 适合人群 零基础 开发语言 Java 开发环境 JDK v11 IntelliJ IDEA v2018.3 文章原文链接 "全栈2019"Java多 ...

随机推荐

  1. 【DP】【Uva437】UVA437 The Tower of Babylon

    传送门 Description Input Output Sample Input Sample Output Case : maximum height = Case : maximum heigh ...

  2. 爬虫实例——爬取淘女郎相册(通过selenium、PhantomJS、BeautifulSoup爬取)

    环境 操作系统:CentOS 6.7 32-bit Python版本:2.6.6 第三方插件 selenium PhantomJS BeautifulSoup 代码 # -*- coding: utf ...

  3. ACE线程管理机制-并发控制

    ACE有若干可用于并发控制的类.这些类可划分为以下范畴: ACE Lock类属 ACE Guard类属 ACE Condition类属 ACE Synchronization类 由于篇幅较长,我分别写 ...

  4. HDU2688 树状数组(逆序数)

    Rotate Time Limit: 2000/1000 MS (Java/Others)    Memory Limit: 32768/32768 K (Java/Others)Total Subm ...

  5. git fatal: 拒绝合并无关的历史的错误解决

    本地初始化的项目 与 github 版本不一致, 导致无法提交 $ git pull origin master 来自 https://github.com/itaken/python-login-d ...

  6. CMDB资产管理系统开发【day26】:CMDB上节回顾

    一.上节知识点回顾 服务器设计了一个表结构 开发了一个客户端 二.后台创建缓存区表 客户端连接服务器,在服务器的下面看报错信息 因为URL都没有写,所以我找不到呀 1.在MadKing\url.py ...

  7. 2017 济南综合班 Day 4

    T1 外星人 二维前缀和 #include<cstdio> #define N 1001 using namespace std; bool v[N][N]; int sum[N][N]; ...

  8. python读文件和写入文件复习

    with open("name.txt",'r') as read_file: for name in read_file: list_name = (name.split(',' ...

  9. 【LibreOJ】#539. 「LibreOJ NOIP Round #1」旅游路线

    [题意]给定正边权有向图,车油量上限C,每个点可以花费pi加油至min(C,ci),走一条边油-1,T次询问s点出发带钱q,旅行路程至少为d的最多剩余钱数. n<=100,m<=1000, ...

  10. 更改控件中DrawableLeft图片的大小,图片与文字的距离

    Drawable drawable=getResources().getDrawable(R.drawable.xx); //获取图片 drawable.setBounds(left, top, ri ...