我们都知道在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. BZOJ1053:[HAOI2007]反素数——题解

    http://www.lydsy.com/JudgeOnline/problem.php?id=1053 对于任何正整数x,其约数的个数记作g(x).例如g(1)=1.g(6)=4.如果某个正整数x满 ...

  2. linux 文件检索操作

    linux命令太多了,作为一个后端开发人员,常用的也就这几个 uname -a 查看版本 tail tail -f /data/wealth-consignment-service/logs/stat ...

  3. 高效率JavaScript代码的编写技巧

    使用DocumentFragment优化多次append 添加多个dom元素时,先将元素append到DocumentFragment中,最后统一将DocumentFragment添加到页面.该做法可 ...

  4. 监听scrollview

    http://blog.csdn.net/u012527802/article/details/47320009

  5. ubuntu14安装tensorflow并测试

    1.ubuntu版本的选择:看了很多博文,建议使用ubuntu14,稳定兼容性好. 2.tensorflow的安装: http://wiki.jikexueyuan.com/project/tenso ...

  6. DOM学习控件定位和案例

    Dom中有多种定位属性,下面是一个简单案例 <html><!--制作一个会跟着鼠标走的图像,还有控件定位的案例--> <head> <title>doc ...

  7. CMDB服务器管理系统【s5day88】:采集资产之整合插件

    以后导入配置文件不用去from conf而是导入from lib.config,因为在这可以导入global_settings和settings.py import sys import os imp ...

  8. 关于mysql 删除数据后物理空间未释放

    转载自:http://www.cnblogs.com/shawnloong/archive/2013/02/07/2908911.html OPTIMIZE TABLE 当您的库中删除了大量的数据后, ...

  9. HDU 5876 Sparse Graph BFS+set删点

    Problem Description In graph theory, the complement of a graph G is a graph H on the same vertices s ...

  10. iOS 隐藏导航栏下的黑线

    一.找到导航栏下的黑线 // 寻找导航栏下的黑线 - (UIImageView *)findHairlineImageViewUnder:(UIView *)view { if ([view isKi ...