Java并发编程之重入锁与读写锁

重入锁

重入锁,顾名思义,就是支持重进入的锁,它表示该锁能够支持一个线程对资源的重复加锁。重进入是指任意线程在获取到锁之后能够再次获取该锁而不会被锁阻塞,该特性的实现需要解决以下两个问题。

1、线程再次获取锁。锁需要去识别获取锁的线程是否为当前占据锁的线程,如果是,则再次成功获取。

2、锁的最终释放。线程重复n次获取了锁,随后在第n次释放该锁后,其他线程能够获取到该锁。锁的最终释放要求锁对于获取进行计数自增,计数表示当前锁被重复获取的次数,而锁被释放时,计数自减,当计数等于0时表示锁已经成功释放。

Java里面内置锁(synchronize)和Lock(ReentrantLock)都是可重入的

synchronized 实例

package com.home;
public class SynchronizedTest implements Runnable {
  public synchronized void method1() {
    System.out.println("method1获得锁,正常运行!");
    method2();
  }
  public synchronized void method2() {
    System.out.println("method2获得锁,也正常运行!");
  }
  @Override
  public void run() {
    method1();
  }
  public static void main(String[] args) {
    SynchronizedTest st = new SynchronizedTest();
    new Thread(st).start();
    new Thread(st).start();
  }
}

Lock 实例

package com.home;
import java.util.concurrent.locks.Lock;
import java.util.concurrent.locks.ReentrantLock;
public class LockTest implements Runnable {
  Lock lock = new ReentrantLock();
  public void method1() {
    lock.lock();
    System.out.println("method1获得锁,正常运行!");
    method2();
    lock.unlock();
  }
  public void method2() {
    lock.lock();
    System.out.println("method2获得锁,也正常运行!");
    lock.unlock();
  }
  @Override
  public void run() {
    method1();
  }
  public static void main(String[] args) {
    LockTest lt = new LockTest();
    new Thread(lt).start();
    new Thread(lt).start();
  }
}

两个例子最后的结果都是正确的,结果如下:

method1获得锁,正常运行!
method2获得锁,也正常运行!
method1获得锁,正常运行!
method2获得锁,也正常运行!

可重入锁最大的作用是避免死锁

读写锁

读写锁维护了一对相关的锁,一个用于只读操作,一个用于写入操作。只要没有writer,读取锁可以由多个reader线程同时保持。写入锁是独占的。

可重入读写锁 ReentrantReadWriteLock

ReentrantReadWriteLock对象提供了readLock()和writeLock()方法, 用于获取读取锁和写入锁.

读取锁允许多个reader线程同时持有, 而写入锁最多只能有一个writter线程持有.

读写锁的使用场合: 读取共享数据的频率远大于修改共享数据的频率. 在上述场合下, 使用读写锁控制共享资源的访问, 可以提高并发性能.

如果一个线程已经持有了写入锁, 则可以再持有读写锁. 相反, 如果一个线程已经持有了读取锁, 则在释放该读取锁之前, 不能再持有写入锁.

可以调用写入锁的newCondition()方法获取与该写入锁绑定的Condition对象, 此时与普通的互斥锁并没有什么区别. 但是调用读取锁的newCondition()方法将抛出异常.

例子

package com.home;
import java.util.Random;
import java.util.concurrent.locks.ReadWriteLock;
import java.util.concurrent.locks.ReentrantReadWriteLock;
class ReadWrte {
  // 共享数据,可以多个线程读数据,只能有一个线程写数据
  private int data;
  // 创建读写锁
  ReadWriteLock rwLock = new ReentrantReadWriteLock();
  /**
   * 读数据,上读锁
   */
  public void get() {
    // 读锁
    rwLock.readLock().lock();
    try {
      System.out.println(Thread.currentThread().getName() + ",Read!");
      Thread.sleep((long) Math.random() * 1000);
      System.out.println(Thread.currentThread().getName() + " 读出的数据为:" +
        this.getData());
    } catch (Exception e) {
      e.printStackTrace();
    } finally {
      rwLock.readLock().unlock();
    }
  }
  /**
   * 写数据,上写锁
   *
   * @param data
   */
  public void put(int data) {
    // 写锁
    rwLock.writeLock().lock();
    try {
      System.out.println(Thread.currentThread().getName() + ",Write!");
      Thread.sleep((long) Math.random() * 1000);
      this.setData(data);
      System.out.println(Thread.currentThread().getName() + " 写入的数据为:" +
        this.getData());
    } catch (InterruptedException e) {
      e.printStackTrace();
    } finally {
      rwLock.writeLock().unlock();
    }
  }
  public int getData() {
    return data;
  }
  public void setData(int data) {
    this.data = data;
  }
}
/**
 * 测试类
 *
 * @author itmyhome
 *
 */
public class ReadWriteLockTest {
  /**
   * @param args
   */
  public static void main(String[] args) {
    // 创建ReadWrte对象
    final ReadWrte rw = new ReadWrte();
    for (int i = 0; i < 10; i++) {
      // 创建并启动10个读线程
      new Thread(new Runnable() {
          @Override
          public void run() {
            rw.get();
          }
        }).start();
      // 创建并启动10个写线程
      new Thread(new Runnable() {
          @Override
          public void run() {
            // 写入一个随机数
            rw.put(new Random().nextInt(8));
          }
        }).start();
    }
  }
}

输出为

Thread-0,Read!
Thread-4,Read!
Thread-8,Read!
Thread-12,Read!
Thread-0 读出的数据为:0
Thread-4 读出的数据为:0
Thread-8 读出的数据为:0
Thread-12 读出的数据为:0
Thread-19,Write!
Thread-19 写入的数据为:5
Thread-7,Write!
Thread-7 写入的数据为:7
Thread-3,Write!
Thread-3 写入的数据为:4
Thread-16,Read!
Thread-16 读出的数据为:4
Thread-11,Write!
Thread-11 写入的数据为:0
Thread-15,Write!
Thread-15 写入的数据为:5
Thread-2,Read!
Thread-2 读出的数据为:5
Thread-17,Write!
Thread-17 写入的数据为:2
Thread-6,Read!
Thread-6 读出的数据为:2
Thread-1,Write!
Thread-1 写入的数据为:5
Thread-13,Write!
Thread-13 写入的数据为:4
Thread-9,Write!
Thread-9 写入的数据为:7
Thread-5,Write!
Thread-5 写入的数据为:2
Thread-10,Read!
Thread-10 读出的数据为:2
Thread-18,Read!
Thread-14,Read!
Thread-18 读出的数据为:2
Thread-14 读出的数据为:2

从图中我们可以看出,可以多个线程同时读,但只能一个线程写,即写数据和写入数据一并完成。

https://mp.weixin.qq.com/s/vkvYJnKfQyuUeD_BDQy_1g

获取更多学习资料,可以扫描下方二维码

原文地址:https://www.cnblogs.com/lemonrel/p/11679400.html

时间: 2024-08-14 08:07:10

Java并发编程之重入锁与读写锁的相关文章

Java并发编程-可重入锁

可重入锁,也叫做递归锁,指的是同一线程 外层函数获得锁之后 ,内层递归函数仍可以获取该锁而不受影响.在JAVA环境下 ReentrantLock 和synchronized 都是 可重入锁. [java]public class Test implements Runnable{ public synchronized void get(){ System.out.println(Thread.currentThread().getId()); set(); } public synchroni

浅谈Java中的锁:Synchronized、重入锁、读写锁

Java开发必须要掌握的知识点就包括如何使用锁在多线程的环境下控制对资源的访问限制 ◆ Synchronized ◆ 首先我们来看一段简单的代码: 12345678910111213141516171819 public class NotSyncDemo { public static int i=0; static class ThreadDemo extends Thread { @Override public void run() { for (int j=0;j<10000;j++)

Java并发编程深入学习——Lock锁

Lock锁介绍 ??在Java 5.0之前,在协调对共享对象的访问时可以使用的机制只有synchronized和volatile.Java 5.0 增加了一种新的机制:ReentrantLock.它并不是一种替代内置加锁的方法,而是当内置加锁机制不适用时,作为一种可选择的高级功能. Lock接口 Lock接口位于java.util.concurrent.locks包中,它定义了一组抽象的加锁操作. public interface Lock { //获取锁 void lock(); // 如果当

【Java并发工具类】StampedLock:比读写锁更快的锁

前言 ReadWriteLock适用于读多写少的场景,允许多个线程同时读取共享变量.但在读多写少的场景中,还有更快的技术方案.在Java 1.8中, 提供了StampedLock锁,它的性能就比读写锁还要好.下面我们介绍StampedLock的使用方法.内部工作原理以及在使用过程中需要注意的事项. StampedLock支持的三种锁模式 ReadWriteLock支持两种访问模式:读锁和写锁,而StampedLock支持三种访问模式:写锁.悲观读锁和乐观读. 其中写锁和悲观读锁的语义与ReadW

Java并发编程:synchronized和锁优化

每天学习一点点 编程PDF电子书.视频教程免费下载:http://www.shitanlife.com/code 1. 使用方法 synchronized 是 java 中最常用的保证线程安全的方式,synchronized 的作用主要有三方面: 确保线程互斥的访问代码块,同一时刻只有一个方法可以进入到临界区 保证共享变量的修改能及时可见 有效解决重排序问题 语义上来讲,synchronized主要有三种用法: 修饰普通方法,锁的是当前对象实例(this) 修饰静态方法,锁的是当前 Class

Java 并发编程中的 CountDownLatch 锁用于多个线程同时开始运行或主线程等待子线程结束

Java 5 开始引入的 Concurrent 并发软件包里面的 CountDownLatch 其实可以把它看作一个计数器,只不过这个计数器的操作是原子操作,同时只能有一个线程去操作这个计数器,也就是同时只能有一个线程去减这个计数器里面的值.CountDownLatch的一个非常典型的应用场景是:有一个任务想要往下执行,但必须要等到其他的任务执行完毕后才可以继续往下执行.假如我们这个想要继续往下执行的任务调用一个CountDownLatch对象的await()方法,其他的任务执行完自己的任务后调

java并发编程常见锁类型

锁是java并发编程中最重要的同步机制.锁除了让临界区互斥执行外,还可以让释放锁的线程向获取同一个锁的线程发送消息.锁是解决并发冲突的重要工具.在开发中我们会用到很多类型的锁,每种锁都有其自身的特点和适用范围.需要深刻理解锁的理念和区别,才能正确.合理地使用锁.常用锁类型乐观锁与悲观锁悲观锁对并发冲突持悲观态度,先取锁后访问数据,能够较大程度确保数据安全性.而乐观锁认为数据冲突的概率比较低,可以尽可能多地访问数据,只有在最终提交数据进行持久化时才获取锁.悲观锁总是先获取锁,会增加很多额外的开销,

Java并发编程学习路线

一年前由于工作需要从微软技术栈入坑Java,并陆陆续续做了一个Java后台项目,目前在搞Scala+Java混合的后台开发,一直觉得并发编程是所有后台工程师的基本功,所以也学习了小一年Java的并发工具,对整体的并发理解乃至分布式都有一定的提高,所以想和大家分享一下. 我的学习路线 首先说说学习路线,我一开始是直接上手JCIP(Java Concurrency in Practice),发现不是很好懂,把握不了那本书的主线,所以思索着从国内的作者开始先,所以便读了下方腾飞的<Java并发编程的艺

Java并发编程高级内容介绍

计数器:CountDownLatch CountDownLatch类似于一个计数器,和Atomic类比较相近,操作是原子的,即多个线程同时只能有一个可以去操作.CountDownLatch对象设置一个初始的数字作为计数值,任何调用这个对象上的await()方法都会阻塞,直到这个计数器的计数值被其他的线程调用countDown()减为0为止.典型的应用场景就是:有一个任务想要往下执行,但必须要等到其他的任务执行完毕后才可以继续往下执行.例如在Zookeeper的使用过程中,由于客户端与服务器建立连