正确使用锁保护共享数据,协调异步线程(下)

简介: 正确使用锁保护共享数据,协调异步线程

避免死锁

  • 死锁
    由于某种原因,锁一直没释放,后续需要获取锁的线程都将处于等待锁的状态,这样程序就卡死。


导致死锁的原因不多

  1. 获取锁后没释放,有经验的程序员很少犯这种错误,即使出现也很容易解决
  2. 锁的重入

看下面代码:

public void visitShareResWithLock() {
  lock.lock(); // 获取锁
  try {
    lock.lock(); // 再次获取锁,会导致死锁吗?
  } finally {
    lock.unlock();
  }

当前的线程获取到了锁lock,然后在持有这把锁的情况下,再次去尝试获取这把锁,这样会导致死锁吗?

不一定。会不会死锁取决于,你获取的这把锁它是不是可重入锁。如果是可重入锁,那就没有问题,否则就会死锁。


大部分编程语言都提供了可重入锁,若无特别要求,尽量使用可重入锁。因为若程序复杂,调用栈很深,很多情况下,当需要获取一把锁时,你不太好判断在n层调用之外的某个地方,是不是已经获取过这把锁,这时,获取可重入锁就有必要。



最后一种死锁的情况是最复杂的,也是最难解决的。如果你的程序中存在多把锁,就有可能出现这些锁互相锁住的情况。

模拟最简单最典型的死锁情况。在这个程序里面,我们有两把锁:lockA和lockB,然后我们定义了两个线程,这两个线程反复地去获取这两把锁,然后释放。

程序执行一会儿就卡住了,发生死锁。

他们获取锁的顺序不一样。

第一个线程,先获取lockA,再获取lockB;

第二个线程正好相反,先获取lockB,再获取lockA。

这最简单的两把锁两个线程死锁的情况,还可以分析清楚,如果你的程序中有十几把锁,几十处加锁解锁,几百线程,如果出现死锁你还能分析清楚是什么情况吗?


避免死锁


程序尽量少用锁

同把锁,加锁和解锁必须放在同一方法

尽量避免同时持有多把锁,即持有一把锁时,又去获取另外一把锁

若需要持多把锁,注意加解锁顺序,解锁顺序要和加锁顺序相反

给你程序中所有的锁排一个顺序,在所有需要加锁的地方,按照同样的顺序加解锁。

如果两个线程都按照先获取lockA再获取lockB的顺序加锁,就不会产生死锁。



使用读写锁


共享数据,如果某方法访问它时,只读取,并不更新,就不需要加锁?

还是需要的,因为如果一个线程读时,另外一个线程同时在更新,那么你读数据有可能是更新到一半的。

所以,无论只读还是读写访问,都是需要加锁的。


锁虽然解决安全问题,但牺牲性能无法并发。


若无线程在更新,即使多线程并发读,也没问题。大部分情况下,数据读要远多于写,所以,我们希望的是:


读可并发执行。

写的同时不能并发读,也不能并发写。

这就兼顾性能和安全。读写锁就为这此而设计。

Java读写锁实例

ReadWriteLock rwlock = new ReentrantReadWriteLock();
public void read() {
  rwlock.readLock().lock();
  try {
    // 在这儿读取共享数据
  } finally {
    rwlock.readLock().unlock();
  }
}
public void write() {
  rwlock.writeLock().lock();
  try {
    // 在这儿更新共享数据
  } finally {
    rwlock.writeLock().unlock();
  }
}

需要读数据的时候,我们获取读锁,不是互斥锁,read()方法可多线程并行执行,这使读性能很好。

写数据,获取写锁,当一个线程持有写锁,其他线程既无法获取读锁,也不能获取写锁,从而保护共享数据。

如此读写锁就兼顾了性能和安全。


在Java中实现一个try-with-lock呢?

java7开始io就有try-with-resource。
可以利用这一个特性,来说实现,自动释放。
代码如下:
public class AutoUnlockProxy implements Closeable {
private Lock lock;
public AutoUnlockProxy(Lock lock) {
this.lock = lock;
}
@Override
public void close() throws IOException {
lock.unlock();
System.out.println("释放锁");
}
public void lock() {
lock.lock();
}
public void tryLock(long time, TimeUnit unit) throws InterruptedException {
lock.tryLock(time, unit);
}
public static void main(String[] args) {
try (AutoUnlockProxy autoUnlockProxy = new AutoUnlockProxy(new ReentrantLock())) {
autoUnlockProxy.lock();
System.out.println("加锁了");
} catch (IOException e) {
e.printStackTrace();
}
}
}
目录
相关文章
|
2月前
|
编解码 数据安全/隐私保护 计算机视觉
Opencv学习笔记(十):同步和异步(多线程)操作打开海康摄像头
如何使用OpenCV进行同步和异步操作来打开海康摄像头,并提供了相关的代码示例。
103 1
Opencv学习笔记(十):同步和异步(多线程)操作打开海康摄像头
|
3月前
|
安全 Java 调度
Java编程时多线程操作单核服务器可以不加锁吗?
Java编程时多线程操作单核服务器可以不加锁吗?
47 2
|
15天前
|
供应链 安全 NoSQL
PHP 互斥锁:如何确保代码的线程安全?
在多线程和高并发环境中,确保代码段互斥执行至关重要。本文介绍了 PHP 互斥锁库 `wise-locksmith`,它提供多种锁机制(如文件锁、分布式锁等),有效解决线程安全问题,特别适用于电商平台库存管理等场景。通过 Composer 安装后,开发者可以利用该库确保在高并发下数据的一致性和安全性。
30 6
|
2月前
|
缓存 安全 Java
使用 Java 内存模型解决多线程中的数据竞争问题
【10月更文挑战第11天】在 Java 多线程编程中,数据竞争是一个常见问题。通过使用 `synchronized` 关键字、`volatile` 关键字、原子类、显式锁、避免共享可变数据、合理设计数据结构、遵循线程安全原则和使用线程池等方法,可以有效解决数据竞争问题,确保程序的正确性和稳定性。
50 2
|
2月前
|
运维 API 计算机视觉
深度解密协程锁、信号量以及线程锁的实现原理
深度解密协程锁、信号量以及线程锁的实现原理
42 1
|
2月前
|
安全 调度 C#
STA模型、同步上下文和多线程、异步调度
【10月更文挑战第19天】本文介绍了 STA 模型、同步上下文和多线程、异步调度的概念及其优缺点。STA 模型适用于单线程环境,确保资源访问的顺序性;同步上下文和多线程提高了程序的并发性和响应性,但增加了复杂性;异步调度提升了程序的响应性和资源利用率,但也带来了编程复杂性和错误处理的挑战。选择合适的模型需根据具体应用场景和需求进行权衡。
|
2月前
|
Java 应用服务中间件 测试技术
Java21虚拟线程:我的锁去哪儿了?
【10月更文挑战第8天】
42 0
|
2月前
|
网络协议 安全 Java
难懂,误点!将多线程技术应用于Python的异步事件循环
难懂,误点!将多线程技术应用于Python的异步事件循环
72 0
|
2月前
|
安全 调度 数据安全/隐私保护
iOS线程锁
iOS线程锁
30 0
|
2月前
|
Java API
【多线程】乐观/悲观锁、重量级/轻量级锁、挂起等待/自旋锁、公平/非公锁、可重入/不可重入锁、读写锁
【多线程】乐观/悲观锁、重量级/轻量级锁、挂起等待/自旋锁、公平/非公锁、可重入/不可重入锁、读写锁
38 0