JMQ为提升性能,使用近乎无锁的设计:

  1. MQ中的锁是个必须使用的技术
  2. 使用锁会降低系统性能

如何正确使用锁?

异步和并发设计可大幅提升性能,但程序更复杂:多线程执行时,充斥不确定性。对一些需并发读写的共享数据,一着不慎满盘皆输。

案例:团建

老板说:“部门准备团建,愿意参加的回消息报名,统计下人数。都按我规定格式报名。”
老板发了:“A,1人”。这时候B和C都要报名,过一会儿,他俩几乎同时各发了一条消息,“B,2人”“C,2人”,每个人发的消息都只统计了老板和他们自己,一共2人,而这时,其实已经有3个人报名了,并且,在最后发消息的C的名单中,B的报名被覆盖。

典型并发读写导致的数据错误。使用锁可有效解决:任何时间都只能有一个线程持锁,持锁线程才能访问被锁保护的资源。

团建案例中,可认为群中有把锁,想要报名的人必须先拿到锁,然后才能更新名单。这就避免了多人同时更新消息,报名名单也就不会出错了。

避免滥用锁

难道遇到这种情况都用锁?
如果能不用锁,就不用锁;
如果你不确定是不是应该用锁,那也不要用锁。

因为使用锁虽然可以保护共享资源,但代价不小。

  1. 加锁和解锁都要CPU时间,这是性能损失。另外,使用锁就有可能导致线程等待锁,等待锁过程中线程是阻塞的状态,过多的锁等待会显著降低程序的性能
  2. 如果锁使用不当,很容易死锁,导致程序卡死。多线程本就难以调试,再加锁,出现并发问题或者死锁问题,程序更难调试。

所以,你在使用锁以前,一定要非常清楚明确地知道,这个问题必须要用一把锁来解决。切忌看到一个共享数据,也搞不清它在并发环境中会不会出现争用问题,就“为了保险,给它加个锁吧。”千万不能有这种不负责任的想法,否则你将会付出惨痛的代价!我曾经遇到过的严重线上事故,其中有几次就是由于不当地使用锁导致的。

只有并发下的共享资源不支持并发访问,或者并发访问共享资源会导致系统错误的情况下,才需使用锁。

锁的用法

在访问共享资源之前,先获取锁。
如果获取锁成功,就可以访问共享资源了。
最后,需要释放锁,以便其他线程继续访问共享资源。

Java使用锁

private Lock lock = new ReentrantLock();public void visitShareResWithLock() {lock.lock();try {// 在这里安全的访问共享资源} finally {lock.unlock();}
}

也可以使用synchronized关键字,它的效果和锁是一样的:

private Object lock = new Object();public void visitShareResWithLock() {synchronized (lock) {// 在这里安全的访问共享资源}
}

使用锁要注意:
使用完锁一定要释放。若在访问共享资源时抛异常,后面释放锁代码就不会再执行,导致死锁。所以要考虑代码可能走的所有分支,确保所有情况下的锁都能释放。

接下来我们说一下,使用锁的时候,遇到的最常见的问题:死锁。

避免死锁

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

导致死锁的原因不多

  1. 获取锁后没释放,有经验的程序员很少犯这种错误,即使出现也很容易解决
  2. 锁的重入
    看下面代码:
public void visitShareResWithLock() {lock.lock(); // 获取锁try {lock.lock(); // 再次获取锁,会导致死锁吗?} finally {lock.unlock();}

当前的线程获取到了锁lock,然后在持有这把锁的情况下,再次去尝试获取这把锁,这样会导致死锁吗?
不一定。会不会死锁取决于,你获取的这把锁它是不是可重入锁。如果是可重入锁,那就没有问题,否则就会死锁。

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

最后一种死锁的情况是最复杂的,也是最难解决的。如果你的程序中存在多把锁,就有可能出现这些锁互相锁住的情况。
模拟最简单最典型的死锁情况。在这个程序里面,我们有两把锁:lockA和lockB,然后我们定义了两个线程,这两个线程反复地去获取这两把锁,然后释放。
程序执行一会儿就卡住了,发生死锁。
他们获取锁的顺序不一样。
第一个线程,先获取lockA,再获取lockB;
第二个线程正好相反,先获取lockB,再获取lockA。
这最简单的两把锁两个线程死锁的情况,还可以分析清楚,如果你的程序中有十几把锁,几十处加锁解锁,几百线程,如果出现死锁你还能分析清楚是什么情况吗?

避免死锁

  1. 程序尽量少用锁
  2. 同把锁,加锁和解锁必须放在同一方法
  3. 尽量避免同时持有多把锁,即持有一把锁时,又去获取另外一把锁
  4. 若需要持多把锁,注意加解锁顺序,解锁顺序要和加锁顺序相反
  5. 给你程序中所有的锁排一个顺序,在所有需要加锁的地方,按照同样的顺序加解锁。
    如果两个线程都按照先获取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();
}}}

正确使用锁保护共享数据,协调异步线程相关推荐

  1. C++11使用互斥量保护共享数据

    C++中使用互斥量 在C++11中,可以通过实例化std::mutex创建互斥量,可以通过调用成员函数lock()进行上锁,调用unlock()进行解锁. 例如: int g_num = 0; std ...

  2. 使用互斥元保护共享数据-lock_guard

    用互斥元保护共享数据 线程相对于进程的优势在于能够共享数据,线程相对于进程的劣势也在于数据能够共享.如何多个线程安全的访问数据,你可以使用互斥锁保护数据,也可以优化数据结构使用无锁编程,或者使用事务保 ...

  3. C++11保护共享数据的其他方法

    保护共享数据的初始化过程 在多线程编程中,互斥量是最通用的保护共享数据的机制.但是在某些情况下,一些资源仅需要在第一次初始化的时候需要保护,其时候就可以不需要互斥变量的保护了.比如编码中最常见的单例模 ...

  4. 用区块链保护共享数据?存储初创公司Gospel开始试水

    初创公司Gospel Technology声称正在利用区块链(Blockchain)来保护和验证可共享的数据.Gospel称,区块链技术的方法可以做到这一点,而且要比任何替代方法更简单. Gospel ...

  5. 《C++并发编程实战》读书笔记——chapter 3_线程间共享数据

    更多的阅读笔记,及示例代码见 Github https://github.com/anlongstory/C-_Concurrency_in_Action_reading_notes 本章主要内容: ...

  6. 保护线程间的共享数据

    程序员的自我修养(六):保护线程间的共享数据 多进程和多线程最本质的区别在于共享和隔离的程度不同.对于多进程方式来说,因为隔离程度高,所以程序员很少需要去担心进程空间的数据被破坏:但是并发任务之间共享 ...

  7. C++线程间共享数据

    通常我们使用锁保护线程间共享数据,这也是最基本的方式. 当访问共享数据前,使用互斥量将相关数据锁住,再当访问结束后,再将数据解锁.线程库需要保证,当一个线程使用特定互斥量锁住共享数据时,其他的线程想要 ...

  8. 谈谈你对同步代码块中同步监视器和共享数据的理解及各自要求。

    同步监视器:俗称锁. ①任何一个类的对象都可以充当锁. ②多个线程共用同一把锁. 共享数据:多个线程共同操作的数据,即为共享数据.需要使用同步机制将操作共享数据的代码包起来.不能包多了,也不能包少了.

  9. 线程的创建 验证线程之间共享数据 守护线程 线程进程效率对比 锁 死锁 递归锁...

    线程(from threading import Thread):CPU调度的最小单位 线程的两种创建方式:方式一: 1 from threading import Thread 2 def f1(i ...

最新文章

  1. mybatis 动态传入表名 注解_mybatis动态sql(注解方式)
  2. Using Artica Squid Open Source Project to Build Powerful and Safe but Simple to Use Proxy
  3. python 抽样函数_python中resample函数实现重采样和降采样代码
  4. eBPF学习记录(三)使用BCC开发eBPF程序
  5. Apache CXF实现Web Service(5)—— GZIP使用
  6. 【iCore3双核心板】iCore3双核心板使用说明(图文)
  7. android+祖玛游戏源码,Flash祖玛游戏源代码
  8. matlab的开方算法_每天一个小算法(matlab armijo)
  9. 7款堪称神器的黑科技App,每一款都是黑科技十足!
  10. html5 前端js框架,前端h5框架总结
  11. m序列 MATLAB仿真
  12. winedt103系统找不到指定文件_latex排版入门(winEdit系统找不到指定文件的解决办法)...
  13. 嗅探(窃听网络上流经的数据包)
  14. 集合框架的理解与总结
  15. 简单谈谈ActiveMQ的两种消费方式
  16. php数据库查询到数据以表格的形式展现出来
  17. 《Java黑皮书基础篇第10版》 第3章【习题】
  18. java多态的多种表现形式
  19. 锦言妙语(Englist一百句)
  20. 一个大学毕业生的反思《下》

热门文章

  1. 阿里云ecs云服务器安装wdcp控制面板教程(推荐CentOS6.5)
  2. 富可视infocus M510T原厂手机刷机维修线刷包附教程
  3. TCP的乱序和丢包判断(附Reordering更新算法)-理论
  4. RGB、YUV、HSV和HSL区别和关联
  5. vue中配置babel-polyfill
  6. 招银网科面试题汇总part2
  7. vincent的歌词
  8. Linux系统之ifconfig命令的基本使用
  9. 一文讲懂SQL聚合函数
  10. 宠物玩具上亚马逊美国站CPC认证标准