一、初识Lock

  Lock是一个接口,提供了无条件的、可轮询的、定时的、可中断的锁获取操作,所有加锁和解锁的方法都是显式的,其包路径是:java.util.concurrent.locks.Lock,其核心方法是lock()、unlock()、tryLock(),实现类有ReentrantLock、ReentrantReadWriteLock.ReadLock、ReentrantReadWriteLock.WriteLock,下图展示了Lock接口中定义的方法:

二、ReentrantLock

(1)初识ReentrantLock

  Java在过去很长一段时间只能通过synchronized关键字来实现互斥,它有一些缺点,比如你不能扩展锁之外的方法或者块边界,尝试获取锁时不能中途取消等。Java5通过Lock接口提供了更复杂的控制来解决这些问题,《Java并发编程实战》一书有如下描述:

(2)示例

此处我们看下面这两个例子,请注意其中ReentrantLock使用方式的区别:

(1)此处两个方法之间的锁是独立的

  1. package com.test;
  2.  
  3. import java.util.concurrent.locks.ReentrantLock;
  4.  
  5. public class ReentrantLockDemo {
  6. public static void main(String[] args) {
  7. final Countx ct = new Countx();
  8. for (int i = 0; i < 2; i++) {
  9. new Thread() {
  10. @Override
  11. public void run() {
  12. ct.get();
  13. }
  14. }.start();
  15. }
  16. for (int i = 0; i < 2; i++) {
  17. new Thread() {
  18. @Override
  19. public void run() {
  20. ct.put();
  21. }
  22. }.start();
  23. }
  24. }
  25. }
  26.  
  27. class Countx {
  28.  
  29. public void get() {
  30. final ReentrantLock lock = new ReentrantLock();
  31. try {
  32. lock.lock();// 加锁
  33. System.out.println(Thread.currentThread().getName() + "get begin");
  34. Thread.sleep(1000L);// 模仿干活
  35. System.out.println(Thread.currentThread().getName() + "get end");
  36. lock.unlock(); // 解锁
  37. } catch (InterruptedException e) {
  38. e.printStackTrace();
  39. }
  40. }
  41.  
  42. public void put() {
  43. final ReentrantLock lock = new ReentrantLock();
  44. try {
  45. lock.lock();// 加锁
  46. System.out.println(Thread.currentThread().getName() + "put begin");
  47. Thread.sleep(1000L);// 模仿干活
  48. System.out.println(Thread.currentThread().getName() + "put end");
  49. lock.unlock(); // 解锁
  50. } catch (InterruptedException e) {
  51. e.printStackTrace();
  52. }
  53. }
  54. }

运行结果如下(每次运行结果都是不一样的,仔细体会一下):

  1. Thread-1get begin
  2. Thread-0get begin
  3. Thread-2put begin
  4. Thread-3put begin
  5. Thread-0get end
  6. Thread-3put end
  7. Thread-1get end
  8. Thread-2put end

(2)此处两个方法之间使用相同的锁

  1. package com.test;
  2.  
  3. import java.util.concurrent.locks.ReentrantLock;
  4.  
  5. public class ReentrantLockDemo {
  6. public static void main(String[] args) {
  7. final Countx ct = new Countx();
  8. for (int i = 0; i < 2; i++) {
  9. new Thread() {
  10. @Override
  11. public void run() {
  12. ct.get();
  13. }
  14. }.start();
  15. }
  16. for (int i = 0; i < 2; i++) {
  17. new Thread() {
  18. @Override
  19. public void run() {
  20. ct.put();
  21. }
  22. }.start();
  23. }
  24. }
  25. }
  26.  
  27. class Countx {
  28. final ReentrantLock lock = new ReentrantLock();
  29.  
  30. public void get() {
  31. // final ReentrantLock lock = new ReentrantLock();
  32. try {
  33. lock.lock();// 加锁
  34. System.out.println(Thread.currentThread().getName() + "get begin");
  35. Thread.sleep(1000L);// 模仿干活
  36. System.out.println(Thread.currentThread().getName() + "get end");
  37. lock.unlock(); // 解锁
  38. } catch (InterruptedException e) {
  39. e.printStackTrace();
  40. }
  41. }
  42.  
  43. public void put() {
  44. // final ReentrantLock lock = new ReentrantLock();
  45. try {
  46. lock.lock();// 加锁
  47. System.out.println(Thread.currentThread().getName() + "put begin");
  48. Thread.sleep(1000L);// 模仿干活
  49. System.out.println(Thread.currentThread().getName() + "put end");
  50. lock.unlock(); // 解锁
  51. } catch (InterruptedException e) {
  52. e.printStackTrace();
  53. }
  54. }
  55. }

运行结果如下(每次运行结果都是一样的):

  1. Thread-0get begin
  2. Thread-0get end
  3. Thread-1get begin
  4. Thread-1get end
  5. Thread-2put begin
  6. Thread-2put end
  7. Thread-3put begin
  8. Thread-3put end

三、ReadWriteLock

(1)初识ReadWriteLock

  Java中的ReadWriteLock是什么?一般而言,读写锁是用来提升并发程序性能的锁分离技术的成果,Java中的ReadWriteLock是Java5中新增的一个接口,提供了readLock和writeLock两种锁机制。一个ReadWriteLock维护一对关联的锁,一个用于只读操作,一个用于写,在没有写线程的情况下,一个读锁可能会同时被多个读线程持有,写锁是独占的。

我们来看一下ReadWriteLock的源码:

  1. public interface ReadWriteLock{
  2. Lock readLock();
  3. Lock writeLock();
  4. }

  从源码上面我们可以看出来ReadWriteLock并不是Lock的子接口,只不过ReadWriteLock借助Lock来实现读写两个锁并存、互斥的操作机制。在ReadWriteLock中每次读取共享数据就需要读取锁,当需要修改共享数据时就需要写入锁,看起来好像是两个锁,但是并非如此。

  ReentrantReadWriteLock是ReadWriteLock在java.util里面唯一的实现类,主要使用场景是当有很多线程都从某个数据结构中读取数据,而很少有线程对其进行修改。在这种情况下,允许读取器线程共享访问时合适的,写入器线程必须是互斥访问的,你可以使用JDK中的ReentrantReadWriteLock来实现这个规则。

ReentrantReadWriteLock的实现里面有以下几个特性:

(1)公平性

(2)重入性

(3)锁降级:写线程获取写入锁后可以获取读取锁,然后释放写入锁,这样就可以从写入锁变成了读取锁,从而实现锁降级的特性。

(4)锁升级

(5)锁获取中断

(6)条件变量

(7)重入数:读取锁和写入锁的数量最大分别是65535。它最多支持65535个写锁和65535个读锁。

概括起来其实就是读写锁的机制:

A、读-读不互斥

B、读-写互斥

C、写-写互斥

(2)示例

示例一:ReadLock和WriteLock单独使用的情况

  1. package demo.thread;
  2.  
  3. import java.util.HashMap;
  4. import java.util.Map;
  5. import java.util.concurrent.locks.ReentrantReadWriteLock;
  6.  
  7. public class ReadWriteLockDemo {
  8. public static void main(String[] args) {
  9. final Count ct = new Count();
  10. for (int i = 0; i < 2; i++) {
  11. new Thread() {
  12. @Override
  13. public void run() {
  14. ct.get();
  15. }
  16. }.start();
  17. }
  18. for (int i = 0; i < 2; i++) {
  19. new Thread() {
  20. @Override
  21. public void run() {
  22. ct.put();
  23. }
  24. }.start();
  25. }
  26. }
  27. }
  28.  
  29. class Count {
  30. private final ReentrantReadWriteLock rwl = new ReentrantReadWriteLock();
  31.  
  32. public void get() {
  33. rwl.readLock().lock();// 上读锁,其他线程只能读不能写,具有高并发性
  34. try {
  35. System.out.println(Thread.currentThread().getName() + " read start.");
  36. Thread.sleep(1000L);// 模拟干活
  37. System.out.println(Thread.currentThread().getName() + "read end.");
  38. } catch (InterruptedException e) {
  39. e.printStackTrace();
  40. } finally {
  41. rwl.readLock().unlock(); // 释放写锁,最好放在finnaly里面
  42. }
  43. }
  44.  
  45. public void put() {
  46. rwl.writeLock().lock();// 上写锁,具有阻塞性
  47. try {
  48. System.out.println(Thread.currentThread().getName() + " write start.");
  49. Thread.sleep(1000L);// 模拟干活
  50. System.out.println(Thread.currentThread().getName() + "write end.");
  51. } catch (InterruptedException e) {
  52. e.printStackTrace();
  53. } finally {
  54. rwl.writeLock().unlock(); // 释放写锁,最好放在finnaly里面
  55. }
  56. }
  57.  
  58. }

运行结果如下:

  1. Thread-1 read start.
  2. Thread-0 read start.
  3. Thread-1read end.
  4. Thread-0read end.
  5. Thread-3 write start.
  6. Thread-3write end.
  7. Thread-2 write start.
  8. Thread-2write end.

从结果上面可以看的出来,读的时候是并发的,写的时候是有顺序的带阻塞机制的

实例二:ReadLock和WriteLock的复杂使用情况,模拟一个有读写数据的场景

  1. private final Map<String, Object> map = new HashMap<String, Object>();// 假设这里面存了数据缓存
  2. private final ReentrantReadWriteLock rwlock = new ReentrantReadWriteLock();
  3.  
  4. public Object readWrite(String id) {
  5. Object value = null;
  6. rwlock.readLock().lock();// 首先开启读锁,从缓存中去取
  7. try {
  8. value = map.get(id);
  9. if (value == null) { // 如果缓存中没有数据,释放读锁,上写锁
  10. rwlock.readLock().unlock();
  11. rwlock.writeLock().lock();
  12. try {
  13. if (value == null) {
  14. value = "aaa"; // 此时可以去数据库中查找,这里简单的模拟一下
  15. }
  16. } finally {
  17. rwlock.writeLock().unlock(); // 释放写锁
  18. }
  19. rwlock.readLock().lock(); // 然后再上读锁
  20. }
  21. } finally {
  22. rwlock.readLock().unlock(); // 最后释放读锁
  23. }
  24. return value;
  25. }

请一定要注意读写锁的获取与释放顺序。

(3)比较分析

ReentrantReadWriteLock与ReentrantLock的比较:

(1)相同点:都是一种显式锁,手动加锁和解锁,都很适合高并发场景

(2)不同点:ReentrantReadWriteLock是对ReentrantLock的复杂扩展,能适合更复杂的业务场景,ReentrantReadWriteLock可以实现一个方法中读写分离的锁机制。而ReentrantLock加锁解锁只有一种机制

四、StampedLock

  Java8引入了一个新的读写锁:StampedLock,这个锁更快,而且它提供强大的乐观锁API,这意味着你能以一个较低的代价获得一个读锁, 在这段时间希望没有写操作发生,当这段时间完成后,你可以查询一下锁,看在刚才这段时间是否有写操作发生,然后你可以决定是否需要再试一次或升级锁或放弃。

通常我们的同步锁代码如下:

  1. synchronized(this){
  2. // do operation
  3. }

Java6提供的ReentrantReadWriteLock使用方式如下:

  1. rwlock.writeLock().lock();
  2. try {
  3. // do operation
  4. } finally {
  5. rwlock.writeLock().unlock();
  6. }

  ReentrantReadWriteLock、ReentrantLock和synchronized锁都有相同的内存语义,不管怎么说synchronized代码要更容易书写,而ReentrantLock的代码必须严格按照一定的方式来写,否则就会造成严重的问题。StampedLock要比ReentrantReadWriteLock更加廉价,也就是消耗比较小,StampedLock控制锁有三种模式(写,读,乐观读)

参考资料:

Java 8新特性StampedLock

(1)http://www.importnew.com/14941.html

(2)http://www.jdon.com/idea/java/java-8-stampedlock.html

===============深入学习===AbstractQueuedSynchronizer=================

此处贴出一些资源,后续会研究这部分内容

(1)http://ifeve.com/jdk1-8-abstractqueuedsynchronizer/

(2)http://ifeve.com/introduce-abstractqueuedsynchronizer/

java并发:线程同步机制之Lock的更多相关文章

  1. Java 并发 线程同步

    Java 并发 线程同步 @author ixenos 同步 1.异步线程本身包含了执行时需要的数据和方法,不需要外部提供的资源和方法,在执行时也不关心与其并发执行的其他线程的状态和行为 2.然而,大 ...

  2. java synchronized 线程同步机制详解

    Java语言的关键字,当它用来修饰一个方法或者一个代码块的时候,能够保证在同一时刻最多只有一个线程执行该段代码. 一.当两个并发线程访问同一个对象object中的这个synchronized(this ...

  3. Java并发——线程同步Volatile与Synchronized详解

    0. 前言 转载请注明出处:http://blog.csdn.net/seu_calvin/article/details/52370068 面试时很可能遇到这样一个问题:使用volatile修饰in ...

  4. Java多线程编程(4)--线程同步机制

    一.锁 1.锁的概念   线程安全问题的产生是因为多个线程并发访问共享数据造成的,如果能将多个线程对共享数据的并发访问改为串行访问,即一个共享数据同一时刻只能被一个线程访问,就可以避免线程安全问题.锁 ...

  5. Java多线程 | 02 | 线程同步机制

    同步机制简介 ​ 线程同步机制是一套用于协调线程之间的数据访问的机制.该机制可以保障线程安全.Java平台提供的线程同步机制包括: 锁,volatile关键字,final关键字,static关键字,以 ...

  6. 【总结】Java线程同步机制深刻阐述

    原文:http://hxraid.iteye.com/blog/667437 我们可以在计算机上运行各种计算机软件程序.每一个运行的程序可能包括多个独立运行的线程(Thread). 线程(Thread ...

  7. Java分享笔记:创建多线程 & 线程同步机制

    [1] 创建多线程的两种方式 1.1 通过继承Thread类创建多线程 1.定义Thread类的子类,重写run()方法,在run()方法体中编写子线程要执行的功能. 2.创建子线程的实例对象,相当于 ...

  8. Java多线程的同步机制(synchronized)

    一段synchronized的代码被一个线程执行之前,他要先拿到执行这段代码的权限,在 java里边就是拿到某个同步对象的锁(一个对象只有一把锁): 如果这个时候同步对象的锁被其他线程拿走了,他(这个 ...

  9. java两种同步机制的实现 synchronized和reentrantlock

    java两种同步机制的实现 synchronized和reentrantlock 双11加保障过去一周,趁现在有空,写一点硬货,因为在进入阿里之后工作域的原因之前很多java知识点很少用,所以记录一下 ...

随机推荐

  1. Angular动态注册组件(controller,service...)

    使用angular的场景一般是应用类网站 这也意味着会有很多的controller,service,directive等等 正常情况下我们要把这些内容一次性下载并注册,由于文件较多,对首次加载的效率影 ...

  2. Eclipse++Xdebug开发php环境配置

    一.php环境配置: 本次使用了appserv 2.5.10集成安装包.具体版本如下,安装后php版本是5.2.6 vc6,apache版本2.2 安装完成后,php配置文件在c:\windows目录 ...

  3. 烂泥:通过vsphere给esxi添加本地硬盘

    本文由秀依林枫提供友情赞助,首发于烂泥行天下. 公司ESXi服务器的硬盘空间不够使用,现在新加了一块硬盘在ESxi服务器上.在服务器上添加完硬盘后,在Vsphere上是看不到新加硬盘的. 下面我们来通 ...

  4. python ljust,rjust,center,zfill对齐使用方法

    字符串在输出时的对齐:S.ljust(width,[fillchar]) #输出width个字符,S左对齐,不足部分用fillchar填充,默认的为空格. S.rjust(width,[fillcha ...

  5. nginx 配置单入口

    # 略... location / { try_fiels $uri $uri/ /index.php; } # 略...

  6. [转]10个学习Android开发的网站推荐

    本文转自:http://blog.csdn.net/i_lovefish/article/details/43950893 1. Android Developers 作为一个Android开发者,官 ...

  7. cni 添加网络 流程分析

    cnitool: Add or remove network interfaces from a network namespace cnitool add <net> <netns ...

  8. 边工作边刷题:70天一遍leetcode: day 85

    Find the Celebrity 要点: 这题从solution反过来想比较好:loop through n同时maintain一个candidate:如果cand认识某个i,那么modify c ...

  9. string 类的实现

    . #include<iostream> . #include<iomanip> . using namespace std; . . class String{ . frie ...

  10. Android配置----Android开发环境搭建

    [声明] 欢迎转载,但请保留文章原始出处→_→ 生命壹号:http://www.cnblogs.com/smyhvae/ 文章来源:http://www.cnblogs.com/smyhvae/p/3 ...