Java并发编程之锁机制详解

作者:禅与计算机程序设计艺术

1.简介

什么是锁?

在多线程编程中,多个线程访问共享资源时可能发生冲突,这种冲突往往会导致数据不准确、程序出错甚至崩溃等后果,为了避免这种冲突,我们就需要对共享资源进行保护,以确保每个线程都能独自地运行,并且互斥地访问该资源。而锁就是用来实现资源保护的一种同步机制。锁是一个抽象概念,它是由许多具体实现方式组成的,本文将对Java中的几种锁进行详细介绍。

为什么要使用锁?

在多线程环境下,不同线程间共享同一个资源(变量、内存空间)时,如果没有采取合适的同步措施,则容易出现数据竞争,导致数据错误、程序崩溃等严重后果。为了保证数据的一致性,在多线程访问同一资源时,通常会使用锁(Lock)来协调各个线程之间的访问顺序,从而保证数据的正确性和完整性。

Lock接口及其实现类

java.util.concurrent包提供了四种锁类:ReentrantLock、ReentrantReadWriteLock、StampedLock和Condition对象。下面介绍一下这些类的用法和区别。

ReentrantLock类

ReentrantLock是Java中最基础的同步工具类,是可重入锁。该类提供与synchronized相同的同步功能,但增加了一些高级特性,如:能够响应中断、定时锁等。除了在创建它的线程已经持有锁的情况下,不会阻塞线程;获取锁的方法可以传入公平参数,默认非公平,即按照请求的顺序去获得锁。常用的方法如下:

  • lock():获取锁。

  • unlock():释放锁。

  • newCondition():创建与当前锁绑定的条件对象。

    import java.util.concurrent.locks.*;
    public class MyClass {
      private final ReentrantLock lock = new ReentrantLock();
    
      public void myMethod(){
          lock.lock(); //获取锁
          try{
              //do something with the locked resource
          }finally{
              lock.unlock(); //释放锁
          }
      }
    
      public static void main(String[] args) {
          MyClass obj = new MyClass();
    
          Thread threadA = new Thread(() -> {
             obj.myMethod();
          });
    
          Thread threadB = new Thread(() -> {
             obj.myMethod();
          });
    
          threadA.start();
          threadB.start();
      }
    }

    ReadWriteLock读写锁

    ReadWriteLock是一个接口,表示一个对象同时允许多个线程进行读操作和写操作,但是读操作不能排斥其他写操作,写操作也不能排斥其他读操作。具体操作如下:

  • readLock().lock(): 获取读锁。

  • writeLock().lock(): 获取写锁。

  • readLock().unlock(): 释放读锁。

  • writeLock().unlock(): 释放写锁。

  • getReadHoldCount(): 当前线程正在持有的读锁数量。

  • isWriteLocked(): 是否被写锁占用。

  • isWriteLockedByCurrentThread(): 当前线程是否正在占用写锁。

  • getQueueLength(): 当前等待获取读锁的线程数量。

  • hasQueuedThreads(): 是否存在等待获取读锁的线程。

  • waitUntil(): 将调用线程置于等待状态,直到指定的期限时间,如果读锁可用,返回true;否则立即返回false。

    import java.util.concurrent.TimeUnit;
    import java.util.concurrent.locks.*;
    

public class MyClass implements Runnable { private int value = 0; private final ReentrantReadWriteLock rwl = new ReentrantReadWriteLock(); private final Lock r = rwl.readLock(); private final Lock w = rwl.writeLock();

@Override
public void run() {
    for (int i = 0; i < 10; i++) {
        if (i % 2 == 0) {
            System.out.println("Getting a read lock.");
            r.lock();
            try {
                TimeUnit.SECONDS.sleep((long)(Math.random()*3));
                System.out.println(Thread.currentThread().getName()+" reads "+value);
            } catch (InterruptedException e) {} finally {
                r.unlock();
            }
        } else {
            System.out.println("Getting a write lock.");
            w.lock();
            try {
                value++;
                TimeUnit.SECONDS.sleep((long)(Math.random()*3));
                System.out.println(Thread.currentThread().getName()+" increments to "+value);
            } catch (InterruptedException e) {} finally {
                w.unlock();
            }
        }
    }
}

public static void main(String[] args) throws InterruptedException {
    MyClass obj = new MyClass();

    Thread t1 = new Thread(obj,"t1");
    Thread t2 = new Thread(obj,"t2");
    Thread t3 = new Thread(obj,"t3");

    t1.start();
    t2.start();
    t3.start();

    t1.join();
    t2.join();
    t3.join();

    System.out.println("Final value: " + obj.value);
}

}

### StampedLock类
StampedLock类提供了一种乐观读写锁。相比于ReentrantReadWriteLock和ReadWriteLock,StampedLock对性能进行了优化,提供了一种低延迟和高吞吐量的方式。具体操作如下:
- stamp(): 获取一个时间戳,用来标识读或写操作,如果需要对共享资源进行修改,则在提交修改前先获取锁定。
- validate(long stamp): 检查指定的时间戳是否有效。
- readLock(): 获取读锁。
- tryOptimisticRead(): 以乐观方式读取共享资源,不加锁,不阻塞线程。
- readUnlock(): 释放读锁。
- writeLock(): 获取写锁。
- tryConvertToWriteLock(long stamp): 如果当前线程已经获取了读锁,尝试转换为写锁,成功返回true,失败返回false。
- forceUnlock(): 释放所有锁,并唤醒所有等待线程。

import java.util.Random; import java.util.concurrent.locks.StampedLock;

public class MyClass { private int value = 0; private Random random = new Random(); private StampedLock sl = new StampedLock();

public int getValue() {
    long stamp = sl.tryOptimisticRead();
    int copy = this.value;
    return sl.validate(stamp)? copy : sl.readLock();
}

public boolean updateValue(int newValue) {
    long stamp = sl.writeLock();
    try {
        while (this.value!= oldValue) {
            stamp = sl.tryConvertToWriteLock(stamp);
            if (!sl.isWriteLocked()) {
                throw new IllegalStateException();
            }

            stamp = sl.writeLock();
        }

        this.value = newValue;
        return true;
    } finally {
        sl.unlock(stamp);
    }
}

public static void main(String[] args) throws InterruptedException {
    MyClass obj = new MyClass();
    int oldValue = obj.getValue();

    if (oldValue >= 100 ||!obj.updateValue(oldValue+1)) {
        System.out.println("Failed to increment value.");
        System.exit(1);
    }

    oldValue = obj.getValue();
    System.out.println("New Value:" + oldValue);

    oldValue = obj.getValue();
    System.out.println("Another New Value:" + oldValue);
}

} ```

猜你喜欢

转载自blog.csdn.net/universsky2015/article/details/133504779