我们已经知道,synchronized 是java的关键字,是Java的内置特性,在JVM层面实现了对临界资源的同步互斥访问,但 synchronized 粒度有些大,在处理实际问题时存在诸多局限性,比如响应中断等。Lock 提供了比 synchronized更广泛的锁操作,它能以更优雅的方式处理线程同步问题。
Lock锁与Synchronized关键字比较
我们可以回顾一下synchronized的使用,synchronized释放锁的时机有以下几种:
- 当执行完代码块中的代码,释放锁;
- 当代码抛出异常,释放锁;
- 当调用锁的wait方法,释放锁;
以上三种情况便可以完全的帮我们解决线程同步的问题,为什么还要引入Lock呢?同样,synchronized关键字也有三种局限性:
- synchronized关键字无法响应中断,如果线程未获得锁,便会一直地尝试去获得锁,不会响应中断,lock锁的lockInterruptibly()方法能让线程响应中断,同时tryLock可以加入时间参数,若一定时间内未获得锁便返回,很灵活;
- synchronized关键字无法读写分离,在多个读线程访问临界资源时,是不需要同步的,但是synchronized关键字通通都给同步了,会导致效率很慢,Lock提供读写锁,实现读写分离;
- synchronized关键字无法知道线程是否获得锁;
以上为synchronized的局限性,但是Lock也有他的缺点,Lock需要显式的释放锁,否则会造成死锁。
Lock是一个接口,是JDK层面的实现;而synchronized是Java中的关键字,是Java的内置特性,是JVM层面的实现;
可重入锁
如果锁具备可重入性,则称作为 可重入锁 。像 synchronized 和 ReentrantLock 都是可重入锁,可重入性实际上表明了 **锁的分配粒度:**基于线程的分配,而不是基于方法调用的分配。举个简单的例子,当一个线程执行到某个synchronized方法时,比如说method1,而在method1中会调用另外一个synchronized方法method2,此时线程不必重新去申请锁,而是可以直接执行方法method2。
class MyClass {
public synchronized void method1() {
method2();
}
public synchronized void method2() {
}
}
上述代码中的两个方法method1和method2都用synchronized修饰了。假如某一时刻,线程A执行到了method1,此时线程A获取了这个对象的锁,而由于method2也是synchronized方法,假如synchronized不具备可重入性,此时线程A需要重新申请锁。但是,这就会造成死锁,因为线程A已经持有了该对象的锁,而又在申请获取该对象的锁,这样就会线程A一直等待永远不会获取到的锁。而由于synchronized和Lock都具备可重入性,所以不会发生上述现象。
可中断锁
顾名思义,可中断锁就是可以响应中断的锁。在Java中,synchronized就不是可中断锁,而Lock是可中断锁。
如果某一线程A正在执行锁中的代码,另一线程B正在等待获取该锁,可能由于等待时间过长,线程B不想等待了,想先处理其他事情,我们可以让它中断自己或者在别的线程中中断它,这种就是可中断锁。在前面演示tryLock(long time, TimeUnit unit)和lockInterruptibly()的用法时已经体现了Lock的可中断性。
公平锁
公平锁即 尽量 以请求锁的顺序来获取锁。比如,同是有多个线程在等待一个锁,当这个锁被释放时,等待时间最久的线程(最先请求的线程)会获得该所,这种就是公平锁。而非公平锁则无法保证锁的获取是按照请求锁的顺序进行的,这样就可能导致某个或者一些线程永远获取不到锁。
在Java中,synchronized就是非公平锁(抢占锁),它无法保证等待的线程获取锁的顺序。而对于ReentrantLock 和 ReentrantReadWriteLock,它默认情况下是非公平锁,但是可以设置为 公平锁(协同式线程调度)。
读写锁
读写锁将对临界资源的访问分成了两个锁,一个读锁和一个写锁。正因为有了读写锁,才使得多个线程之间的读操作不会发生冲突。ReadWriteLock就是读写锁,它是一个接口,ReentrantReadWriteLock实现了这个接口。可以通过readLock()获取读锁,通过writeLock()获取写锁。
ReentrantLock
ReentrantLock是Java在JDK1.5引入的显式锁,在实现原理和功能上都和内置锁(synchronized)上都有区别。
底层实现原理
ReentrantLock是基于AQS实现。AQS是一个抽象类,所以不能直接实例化,当我们需要实现一个自定义锁的时候可以去继承AQS然后重写获取锁的方式和释放锁的方式****管理state,而ReentrantLock就是通过重写了AQS的tryAcquire和tryRelease方法实现的lock和unlock。
public class ReentrantLock implements Lock, java.io.Serializable {
private static final long serialVersionUID = 7373984872572414699L;
/** Synchronizer providing all implementation mechanics */
private final Sync sync;
/**
* Base of synchronization control for this lock. Subclassed
* into fair and nonfair versions below. Uses AQS state to
* represent the number of holds on the lock.
*/
abstract static class Sync extends AbstractQueuedSynchronizer {
private static final long serialVersionUID = -5179523762034025860L;
/**
* Performs non-fair tryLock. tryAcquire is implemented in
* subclasses, but both need nonfair try for trylock method.
*/
@ReservedStackAccess
final boolean nonfairTryAcquire(int acquires) {
final Thread current = Thread.currentThread();
int c = getState();
if (c == 0) {
if (compareAndSetState(0, acquires)) {
setExclusiveOwnerThread(current);
return true;
}
}
else if (current == getExclusiveOwnerThread()) {
int nextc = c + acquires;
if (nextc < 0) // overflow
throw new Error("Maximum lock count exceeded");
setState(nextc);
return true;
}
return false;
}
@ReservedStackAccess
protected final boolean tryRelease(int releases) {
int c = getState() - releases;
if (Thread.currentThread() != getExclusiveOwnerThread())
throw new IllegalMonitorStateException();
boolean free = false;
if (c == 0) {
free = true;
setExclusiveOwnerThread(null);
}
setState(c);
return free;
}
使用方法
ReentrantLock,即可重入锁。ReentrantLock是唯一实现了Lock接口的类,并且ReentrantLock提供了更多的方法。
@Slf4j
public class LockDemo {
private Lock lock=new ReentrantLock();
public void baseUse(){
lock.lock();
try {
Thread.sleep(3000);//模拟业务代码
}catch (Exception e){
log.error("发生异常:",e);
}finally {
lock.unlock();
}
}
}
解锁方法,一定在finally里,保证锁是一定能释放的,否则很容易引起死锁。
ReentrantReadWriteLock
读写锁并不是java的概念,是一个通用技术,所有的读写锁都遵从3大原则:
- 允许多个线程同时读变量。
- 只允许一个线程写变量。
- 如果一个线程正在写共享变量,禁止其他线程读这个共享变量。
底层实现原理
读写锁将对临界资源的访问分成了两个锁,一个读锁和一个写锁。正因为有了读写锁,才使得多个线程之间的读操作不会发生冲突。ReadWriteLock就是读写锁,它是一个接口,ReentrantReadWriteLock实现了这个接口。可以通过readLock()获取读锁,通过writeLock()获取写锁。
伪代码如下:
public class ReentrantReadWriteLock
implements ReadWriteLock, java.io.Serializable {
private static final long serialVersionUID = -6992448646407690164L;
/** Inner class providing readlock */
private final ReentrantReadWriteLock.ReadLock readerLock;
/** Inner class providing writelock */
private final ReentrantReadWriteLock.WriteLock writerLock;
/** Performs all synchronization mechanics */
final Sync sync;
public static class ReadLock implements Lock, java.io.Serializable {
private static final long serialVersionUID = -5992448646407690164L;
private final Sync sync;
}
public static class WriteLock implements Lock, java.io.Serializable {
private static final long serialVersionUID = -4992448646407690164L;
private final Sync sync;
}
}
使用方法
public class UseReentrantReadWriteLock {
private ReentrantReadWriteLock rwLock =
new ReentrantReadWriteLock();
private ReentrantReadWriteLock.ReadLock readLock = rwLock.readLock();
private ReentrantReadWriteLock.WriteLock writeLock = rwLock.writeLock();
public void read(){
try {
readLock.lock();
System.out.println("当前线程:" + Thread.currentThread().getName() + "进入...");
Thread.sleep(3000);
System.out.println("当前线程:" + Thread.currentThread().getName() + "退出...");
} catch (InterruptedException e) {
e.printStackTrace();
}finally {
readLock.unlock();
}
}
public void write(){
try {
writeLock.lock();
System.out.println("当前线程:" + Thread.currentThread().getName() + "进入...");
Thread.sleep(3000);
System.out.println("当前线程:" + Thread.currentThread().getName() + "退出...");
} catch (InterruptedException e) {
e.printStackTrace();
}finally {
writeLock.unlock();
}
}
public static void main(String[] args){
final UseReentrantReadWriteLock urrw =
new UseReentrantReadWriteLock();
Thread t1 =
new Thread(new Runnable() {
@Override
public void run() {
urrw.read();
}
},"t1");
Thread t2 = new Thread(new Runnable() {
@Override
public void run() {
urrw.read();
}
},"t2");
Thread t3 = new Thread(new Runnable() {
@Override
public void run() {
urrw.write();
}
},"t3");
Thread t4 = new Thread(new Runnable() {
@Override
public void run() {
urrw.write();
}
}, "t4");
t1.start();
//t3.start();
//t4.start();
t2.start();
}
}
缓存器实现:
public class CacheDemo {
//缓存器
private Map<String,Object> map =
new HashMap<>();
private ReadWriteLock rwl = new ReentrantReadWriteLock();
public Object get(String id) {
Object value = null;
rwl.readLock().lock();//首先开启读锁,从缓存中去取
try {
value = map.get(id);
if (value == null) {//如果缓存中没有释放读锁,上写锁
rwl.readLock().unlock();
rwl.writeLock().lock();
try {
if (value == null) {
value = "aaa";//此时可以去数据库中查找,这里简单的模拟一下
}
} finally {
rwl.writeLock().unlock();//释放写锁
}
rwl.writeLock().unlock();//然后再上读锁
}
} finally {
rwl.readLock().unlock();
}
return value;
}
}
参考:www.jianshu.com/p/b52671244…
Condition类
底层实现原理
Condition是在java 1.5中才出现的。它用来替代传统的Object的wait()、notify()实现线程间的协作,相比使用Object的wait()、notify(),使用Condition的await()、signal()这种方式实现线程间协作更加安全和高效。
使用方法
- Condition是个接口,基本的方法就是await()和signal()方法;
- Condition依赖于Lock接口,生成一个Condition的基本代码是lock.newCondition()
- 调用Condition的await()和signal()方法,都必须在lock保护之内,就是说必须在lock.lock()和lock.unlock之间才可以使用。
Conditon中的await()对应Object的wait();
Condition中的signal()对应Object的notify();
Condition中的signalAll()对应Object的notifyAll()。
package thread;
import java.util.concurrent.locks.Condition;
import java.util.concurrent.locks.Lock;
import java.util.concurrent.locks.ReentrantLock;
/**
*
* @author xg
*
* 2020年9月1日 下午5:48:54
*/
public class ConTest {
final Lock lock = new ReentrantLock();
final Condition condition = lock.newCondition();
public static void main(String[] args) {
// TODO Auto-generated method stub
ConTest test = new ConTest();
Producer producer = test.new Producer();
Consumer consumer = test.new Consumer();
consumer.start();
producer.start();
}
class Consumer extends Thread{
@Override
public void run() {
consume();
}
private void consume() {
try {
lock.lock();
System.out.println("我在等一个新信号"+this.currentThread().getName());
condition.await();
} catch (InterruptedException e) {
// TODO Auto-generated catch block
e.printStackTrace();
} finally{
System.out.println("拿到一个信号"+this.currentThread().getName());
lock.unlock();
}
}
}
class Producer extends Thread{
@Override
public void run() {
produce();
}
private void produce() {
try {
lock.lock();
System.out.println("我拿到锁"+this.currentThread().getName());
condition.signalAll();
System.out.println("我发出了一个信号:"+this.currentThread().getName());
} finally{
lock.unlock();
}
}
}
}
参考:www.jianshu.com/p/0a768e3b3…