JUC之locks包下的常用锁相关类
java.util.concurrency.locks
java.util.concurrency.locks
在java.util.concurrency(JUC)
包下,它是一个提供了锁机制相关的类包,比如:Lock, Condition, ReadWriteLock
等。
Lock接口
在JDK5之前,Java应用程序对于多线程的并发安全处理只能基于synchronized关键字解决,但是synchronized在有些场景中会存在一些短板。比如:它并不适合于所有的并发场景。Lock的出现可以解决synchronized在某些场景中的短板,它比synchronized更加灵活。
synchronized、wait
synchronized可以加锁,wait、notify可以看做加锁和解锁。但是synchronized方式加锁存在一定问题,因此出现了显式锁Lock。
synchronized的问题:
1、同步块的阻塞无法中断,不能Interruptibly
2、同步块的阻塞无法控制超时,无法自动解锁
3、同步块无法异步处理锁,即不能立即知道是否可以拿到锁
4、同步块无法根据条件灵活的加锁解锁,即只能跟同步块范围一致
Lock
Lock锁采用接口设计,使用方式灵活可控,性能开销小,属于java.util.concurrent.locks
锁工具包
接口方法
方法 | 描述 |
---|---|
void lock() | 获取锁,类似synchronized (lock) |
void lockInterruptibly() | 获取锁,允许打断 |
Condition newCondition() | 新增一个绑定到当前Lock的条件 final Lock lock = new ReentrantLock() final Condition notEmpty = lock.newCondition() |
boolean tryLock() | 尝试无等待获取锁,成功则返回true |
boolean tryLock(long time, TimeUnit unit) | 尝试获取锁,成功则返回true,超时则退出 |
void unlock() | 解锁,要求当前线程已获得锁,类似同步块结束 |
使用Lock
public class LockCounter {
private int sum = 0;
// 可重入锁+公平锁
private Lock lock = new ReentrantLock(true);
public int incrAndGet() {
try {
lock.lock();
return ++sum;
} finally {
lock.unlock();
}
}
public int getSum() {
return sum;
}
public static void main(String[] args) {
int loopNum = 10_0000;
LockCounter counter = new LockCounter();
// 从0到loopNum产生递增值序列,转成一个流,然后循环遍历0到loopNum数,进行求和
IntStream.range(0, loopNum).parallel()
.forEach(i -> counter.incrAndGet());
System.out.println("counter.getSum() = " + counter.getSum());
}
}
counter.getSum() = 100000
ReentrantLock重入锁
ReentrantLock是可以重入的锁,当一个线程获取锁时,还可以接着重复获取多次。
ReentrantLock具有与synchronized相同功能,但是会比synchronized更加灵活,具有更多的方法。
ReentrantLock底层基于AbstractQueuedSynchronizer实现。
ReentrantLock与Synchronized的异同
ReentrantLock和synchronized都是独占锁,只允许线程互斥的访问临界区。
实现上两者不同:
synchronized加锁解锁的过程是隐式的,用户不用手动操作,优点是操作简单,但不够灵活。一般并发场景使用synchronized
ReentrantLock需要手动加锁和解锁,且解锁的操作尽量要放在finally代码块中,保证线程正确释放锁。操作较为复杂,但是因为可以手动控制加锁和解锁过程,在复杂的并发场景中使用。
都是可重入:
synchronized因为可重入因此可以放在被递归执行的方法上,且不用担心线程最后能否正确释放锁
ReentrantLock在重入时要却确保重复获取锁的次数必须和重复释放锁的次数一样,否则可能导致其他线程无法获得该锁。
ReentrantLock实现公平、非公平锁
ReentrantLock可以实现公平、非公平锁。
公平锁是指当锁可用时,在锁上等待时间最长的线程将获得锁的使用权。非公平锁则随机分配这种使用权。
ReentrantLock和synchronized一样,默认ReentrantLock实现是非公平锁,因为相比公平锁,非公平锁性能更好。
创建公平锁
ReentrantLock lock = new ReentrantLock(true);
创建非公平锁
ReentrantLock lock = new ReentrantLock(false);
使用示例
public class ReentrantLockDemo {
private static int count = 0;
// 公平重入锁
static Lock lock = new ReentrantLock(true);
public static void inc() {
// 线程获得锁(互斥锁)
lock.lock();
try {
Thread.sleep(1);
count++;
getLock();
} catch (InterruptedException e) {
e.printStackTrace();
} finally {
//释放锁
lock.unlock();
}
}
public static void getLock() {
// 同一个线程再次来抢占锁 : 不需要再次抢占锁,而是只增加重入的次数
lock.lock();
try {
count--;
} finally {
lock.unlock();
}
}
public static void main(String[] args) {
Thread t1 = new Thread(() -> AtomicDemo.inc());
t1.start();
System.out.println("result:" + count);
}
}
result:0
ReadWriteLock重入读写锁
ReadWriteLock管理一组锁:一个读锁,一个写锁。它适用于读多写少的并发情况。
读锁可以在没有写锁的时候被多个线程同时持有,写锁是独占的。因此,在读写锁的实现时必须确保写操作对读操作的影响。每次只能有一个写线程,但是同时可以有多个线程并发地读数据。
接口方法
Lock readLock(); 获取读锁,共享锁
Lock writeLock(); 获取写锁,独占锁,排斥读锁
使用ReadWriteLock
public class ReadWriteLockCounter {
private int sum = 0;
/**
* 可重入、读写锁、公平/非公平锁
* fair:true:公平锁 false:非公平锁
* 公平锁意味排队靠前的优先
* 非公平锁则都是同样机会
*/
private ReadWriteLock lock = new ReentrantReadWriteLock(true);
public int incrAndGet() {
try {
// 写锁 独占锁 被读锁排斥
lock.writeLock().lock();
return ++sum;
} finally {
lock.writeLock().unlock();
}
}
public int getSum() {
try {
// 读锁 共享锁 保证可见性
lock.readLock().lock();
return sum;
} finally {
lock.readLock().unlock();
}
}
public static void main(String[] args) {
int loopNum = 10_0000;
ReadWriteLockCounter readWriteLockCounter = new ReadWriteLockCounter();
// 从0到loopNum产生递增值序列,转成一个流,然后循环遍历0到loopNum数,进行求和
IntStream.range(0, loopNum).parallel()
.forEach(i -> readWriteLockCounter.incrAndGet());
System.out.println("counter.getSum() = " + readWriteLockCounter.getSum());
System.out.println("counter.getSum() = " + readWriteLockCounter.getSum());
}
}
counter.getSum() = 100000
counter.getSum() = 100000
Condition信号量
Condition是一个多线程协调通信的工具类,可以让某些线程一起等待某个条件( condition ),只有满足条件时,线程才会被唤醒
Condition具有线程的wait和notify的功能,有2个核心方法:
await:把当前线程阻塞挂起
signal:唤醒阻塞的线程
当调用await方法后,当前线程会释放锁并等待,而其他线程调用condition对象的signal或者signalall方法通知并被阻塞的线程,然后自己执行unlock释放锁,被唤醒的线程获得之前的锁继续执行,最后释放锁。
接口方法
方法 | 描述 |
---|---|
void await() | 等待信号,相当于Object的wait() |
boolean await(long time, TimeUnit unit) | 等待信号,超时则返回false |
long awaitNanos(long nanosTimeout) | 等待信号,直到被信号发送或中断, 或指定等待时间过去 |
void awaitUninterruptibly() | 等待信号 |
boolean awaitUntil(Date deadline) | 等待信号, 超时则返回false |
void signal() | 给一个等待线程发送唤醒信号, 相当于Object的notify () |
void signalAll() | 给所有等待线程发送唤醒信号,相当于Object的notifyAll() |
使用Condition
public static void main(String[] args) {
Queue<String> queue = new LinkedList<>();
// 重入锁
Lock lock = new ReentrantLock();
// 通过Lock.newCondition()创建,可以看做是Lock对象上的信号,类似于wait/notify
Condition condition = lock.newCondition();
int maxSize = 5;
Producer producer = new Producer(queue, maxSize, lock, condition);
Consumer consumer = new Consumer(queue, maxSize, lock, condition);
Thread t1 = new Thread(producer);
Thread t2 = new Thread(consumer);
t1.start();
t2.start();
}
}
public class Producer implements Runnable {
private Queue<String> msg;
private int maxSize;
Lock lock;
Condition condition;
public Producer(Queue<String> msg, int maxSize, Lock lock, Condition condition) {
this.msg = msg;
this.maxSize = maxSize;
this.lock = lock;
this.condition = condition;
}
@Override
public void run() {
int i = 0;
while (true) {
i++;
lock.lock();
while (msg.size() == maxSize) {
System.out.println("生产者队列满了,先等待");
try {
condition.await(); // 阻塞线程并释放锁
} catch (InterruptedException e) {
e.printStackTrace();
}
}
try {
Thread.sleep(1000);
} catch (InterruptedException e) {
e.printStackTrace();
}
System.out.println("生产消息:" + i);
msg.add("生产者的消息内容" + i);
condition.signal(); // 唤醒阻塞状态下的线程
lock.unlock();
}
}
}
public class Consumer implements Runnable {
private Queue<String> msg;
private int maxSize;
Lock lock;
Condition condition;
public Consumer(Queue<String> msg, int maxSize, Lock lock, Condition condition) {
this.msg = msg;
this.maxSize = maxSize;
this.lock = lock;
this.condition = condition;
}
@Override
public void run() {
int i = 0;
while (true) {
i++;
lock.lock(); //synchronized
while (msg.isEmpty()) {
System.out.println("消费者队列空了,先等待");
try {
condition.await(); //阻塞线程并释放锁 wait
} catch (InterruptedException e) {
e.printStackTrace();
}
}
try {
Thread.sleep(1000);
} catch (InterruptedException e) {
e.printStackTrace();
}
System.out.println("消费消息:" + msg.remove());
condition.signal(); //唤醒阻塞状态下的线程
lock.unlock();
}
}
}
生产消息:1
生产消息:2
生产消息:3
生产消息:4
生产消息:5
生产者队列满了,先等待
消费消息:生产者的消息内容1
消费消息:生产者的消息内容2
消费消息:生产者的消息内容3
消费消息:生产者的消息内容4
消费消息:生产者的消息内容5
消费者队列空了,先等待
生产消息:6
生产消息:7
生产消息:8
生产消息:9
LockSupport锁当前线程
LockSupport类似于Thread 类的静态方法,专门处理执行代码的本线程。
接口方法
方法 | 描述 |
---|---|
void park(Object blocker) | 暂停当前线程 |
void parkNanos(Object blocker,long nanos) | 暂停当前线程,不过有超时时间的限制 |
void parkUntil(Object blocker,long deadline) | 暂停当前线程,直到某个时间 |
void park() | 无期限暂停当前线程 |
void parkNanos(long nanos) | 暂停当前线程,不过有超时时间的限制 |
void parkUntil(long deadline) | 暂停当前线程,直到某个时间 |
void unpark(Thread thread) | 恢复当前线程。传入Thread参数,是因为一个park线程,无法自己唤醒自己,所以需要其他线程来唤醒 |
Object getBlocker(Thread t) |
使用LockSupport
public static void main(String[] args) {
Thread t1 = new Thread(() -> {
System.out.println(Thread.currentThread().getName() + " 开始执行");
LockSupport.park();
System.out.println(Thread.currentThread().getName() + " 被唤醒");
}, "t1");
t1.start();
new Thread(() -> {
System.out.println(Thread.currentThread().getName() + " 开始执行");
try {
TimeUnit.SECONDS.sleep(5);
} catch (InterruptedException e) {
throw new RuntimeException(e);
}
System.out.println(Thread.currentThread().getName() + " 开始唤醒");
LockSupport.unpark(t1);
}, "t2").start();
}
t1 开始执行
t2 开始执行
t2 开始唤醒
t1 被唤醒
用锁原则
1. 只在更新对象的成员变量时加锁
2. 只在访问可变的成员变量时加锁
3. 不在调用其他对象的方法时加锁
4. 降低锁范围:锁定代码的范围、作用域
5. 细分锁粒度:一个大锁,拆分成多个小锁
版权声明:本文内容由互联网用户自发贡献,该文观点仅代表作者本人。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如发现本站有涉嫌侵权/违法违规的内容, 请发送邮件至 举报,一经查实,本站将立刻删除。
文章由极客之音整理,本文链接:https://www.bmabk.com/index.php/post/136855.html