Lock锁
1、Lock接口
锁是用于通过多个线程控制对共享资源访问的工具。
Lock接口基本的方法:
/**
* @see ReentrantLock
* @see Condition
* @see ReadWriteLock
*/
public interface Lock {
//获得锁。如锁不可用,休眠直到获取锁。
void lock();
//可用立即返回。同上,只是获取中可以中断当前线程
void lockInterruptibly() throws InterruptedException;
//调用时才可获锁。可用返回true;不可用false 。
boolean tryLock();
//超时则结束,返回false. 可被中断
boolean tryLock(long time, TimeUnit unit) throws InterruptedException;
void unlock();//释放锁。
// 获取等待通知组件,该组件和当前锁绑定,当前线程只获得锁,才能调用该组件wait(),调用后释放锁
Condition newCondition();
}
Condition接口基本方法:
public interface Condition {
void await() throws InterruptedException;
void awaitUninterruptibly();
long awaitNanos(long nanosTimeout) throws InterruptedException;
boolean await(long time, TimeUnit unit) throws InterruptedException;
boolean awaitUntil(Date deadline) throws InterruptedException;
void signal();
void signalAll();
}
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()。
condition实现生产者、消费者模式:原文链接
package thread;
import java.util.PriorityQueue;
import java.util.concurrent.locks.Condition;
import java.util.concurrent.locks.Lock;
import java.util.concurrent.locks.ReentrantLock;
public class ConTest2 {
private int queueSize = 10;
private PriorityQueue<Integer> queue = new PriorityQueue<Integer>(queueSize);
private Lock lock = new ReentrantLock();
private Condition notFull = lock.newCondition();
private Condition notEmpty = lock.newCondition();
public static void main(String[] args) throws InterruptedException {
ConTest2 test = new ConTest2();
Producer producer = test.new Producer();
Consumer consumer = test.new Consumer();
producer.start();
consumer.start();
Thread.sleep(0);
producer.interrupt();
consumer.interrupt();
}
class Consumer extends Thread{
@Override
public void run() {
consume();
}
volatile boolean flag=true;
private void consume() {
while(flag){
lock.lock();
try {
while(queue.isEmpty()){
try {
System.out.println("队列空,等待数据");
notEmpty.await();
} catch (InterruptedException e) {
flag =false;
}
}
queue.poll(); //每次移走队首元素
notFull.signal();
System.out.println("从队列取走一个元素,队列剩余"+queue.size()+"个元素");
} finally{
lock.unlock();
}
}
}
}
class Producer extends Thread{
@Override
public void run() {
produce();
}
volatile boolean flag=true;
private void produce() {
while(flag){
lock.lock();
try {
while(queue.size() == queueSize){
try {
System.out.println("队列满,等待有空余空间");
notFull.await();
} catch (InterruptedException e) {
flag =false;
}
}
queue.offer(1); //每次插入一个元素
notEmpty.signal();
System.out.println("向队列取中插入一个元素,队列剩余空间:"+(queueSize-queue.size()));
} finally{
lock.unlock();
}
}
}
}
}
Lock和synchronized有以下几点不同:
(1) Lock是一个接口,是JDK层面的实现;而synchronized是Java中的关键字,是Java的内置特性,是JVM层面的实现;
(2) synchronized 在发生异常时,会自动释放线程占有的锁,因此不会导致死锁现象发生;而Lock在发生异常时,如果没有主动通过unLock()去释放锁,则很可能造成死锁现象,因此使用Lock时需要在finally块中释放锁;
(3) Lock 可以让等待锁的线程响应中断,而使用synchronized时,等待的线程会一直等待下去,不能够响应中断;
(4) 通过Lock可以知道有没有成功获取锁,而synchronized却无法办到;
(5) Lock可以提高多个线程进行读操作的效率。
在性能上来说,如果竞争资源不激烈,两者的性能是差不多的。而当竞争资源非常激烈时(即有大量线程同时竞争),此时Lock的性能要远远优于synchronized。所以说,在具体使用时要根据适当情况选择。
2、ReentranLock
什么是 “可重入”,可重入就是说某个线程已经获得某个锁,可以再次获取锁而不会出现死锁。
什么是可重入锁,不可重入锁呢?“ 重入” 字面意思已经很明显了,就是可以重新进入。可重入锁,就是说一个线程在获取某个锁后,还可以继续获取该锁,即允许一个线程多次获取同一个锁。比如synchronized内置锁就是可重入的,如果A类有2个synchornized方法method1和method2,那么method1调用method2是允许的。显然重入锁给编程带来了极大的方便。假如内置锁不是可重入的,那么导致的问题是:1个类的synchornized方法不能调用本类其他synchornized方法,也不能调用父类中的synchornized方法。与内置锁对应,JDK提供的显示锁ReentrantLock也是可以重入的,这里通过一个例子着重说下可重入锁的释放需要的事儿。
可重入,就是可以重复获取相同的锁,synchronized和ReentrantLock都是可重入的,可重入降低了编程复杂性。
package com.test.reen;
// 演示可重入锁是什么意思
public class WhatReentrant {
public static void main(String[] args) {
new Thread(new Runnable() {
@Override
public void run() {
synchronized (this) {
System.out.println("第1次获取锁,这个锁是:" + this);
int index = 1;
while (true) {
synchronized (this) {
System.out.println("第" + (++index) + "次获取锁,这个锁是:" + this);
}
if (index == 10) {
break;
}
}
}
}
}).start();
}
}
package com.test.reen;
import java.util.Random;
import java.util.concurrent.locks.ReentrantLock;
// 演示可重入锁是什么意思
public class WhatReentrant2 {
public static void main(String[] args) {
ReentrantLock lock = new ReentrantLock();
new Thread(new Runnable() {
@Override
public void run() {
try {
lock.lock();
System.out.println("第1次获取锁,这个锁是:" + lock);
int index = 1;
while (true) {
try {
lock.lock();
System.out.println("第" + (++index) + "次获取锁,这个锁是:" + lock);
try {
Thread.sleep(new Random().nextInt(200));
} catch (InterruptedException e) {
e.printStackTrace();
}
if (index == 10) {
break;
}
} finally {
lock.unlock();
}
}
} finally {
lock.unlock();
}
}
}).start();
}
}
synchronized和ReentrantLock的区别:
ReentrantLock
- ReentrantLock是JDK方法,需要手动声明上锁和释放锁,因此语法相对复杂些;如果忘记释放锁容易导致死锁
- ReentrantLock具有更好的细粒度,可以在ReentrantLock里面设置内部Condititon类,可以实现分组唤醒需要唤醒的线程
- RenentrantLock能实现公平锁
Synchronized
-
Synchoronized语法上简洁方便
-
Synchoronized是JVM方法,由编辑器保证枷锁和释放
3、ReadWriteLock
ReentrantLock保证线程安全性,但效率低。
ReadWriteLock接口的实现类——ReentrantReadWriteLock读写锁解决。
public interface ReadWriteLock {
/**
* Returns the lock used for reading.
*
* @return the lock used for reading
*/
Lock readLock();
/**
* Returns the lock used for writing.
*
* @return the lock used for writing
*/
Lock writeLock();
}
ReadWriteLock管理一组锁,一个是只读的锁,一个是写锁。
读<共享锁>,写<排他锁>。分离读写并发性提升。读写互斥、写写互斥。
ReetrantReadWriteLock读写锁的效率明显高于synchronized关键字。
import java.util.HashMap;
import java.util.Map;
import java.util.concurrent.TimeUnit;
import java.util.concurrent.locks.ReentrantLock;
import java.util.concurrent.locks.ReentrantReadWriteLock;
public class ReadWriteDemo {
public static void main(String[] args) {
MyCache myCache = new MyCache();
// 多个线程同时进行读写
// 五个线程在写 线程是CPU调度的
for (int i = 1; i < 5; i++) {
final int temp = i;
new Thread(()->{
myCache.put(temp+"",temp+"");
},String.valueOf(i)).start();
}
// 五个线程在读
for (int i = 1; i < 5; i++) {
final int temp = i;
new Thread(()->{
myCache.get(temp+"");
},String.valueOf(i)).start();
}
}
}
class MyCache{
private volatile Map<String,Object> map = new HashMap<>();
// ReadWriteLock --> ReentrantReadWriteLock lock不能区分读和写
// ReentrantReadWriteLock 可以区分读和写,实现更加精确的控制
// 读写锁
private ReentrantReadWriteLock readWriteLock = new ReentrantReadWriteLock();
// 写,独占
public void put(String key,String value){
// lock.lock 加锁
readWriteLock.writeLock().lock();
try {
System.out.println(Thread.currentThread().getName()+"写入"+key);
map.put(key,value);
// 存在别的线程插队
System.out.println(Thread.currentThread().getName()+"写入完成");
} catch (Exception e) {
e.printStackTrace();
} finally {
readWriteLock.writeLock().unlock(); // lck.unlock();
}
}
// 读
public void get(String key){
readWriteLock.readLock().lock();
try {
System.out.println(Thread.currentThread().getName()+"读取"+key);
Object result = map.get(key);
System.out.println(Thread.currentThread().getName()+"读取结果:"+result);
} catch (Exception e) {
e.printStackTrace();
} finally {
readWriteLock.readLock().unlock();
}
}
}