浅谈Java中的锁

发表于:2016-10-31 09:32

字体: | 上一篇 | 下一篇 | 我要投稿

 作者:xiaoluo91    来源:51Testing软件测试网采编

  锁在并发编程中的重要性不言而喻, 但是如何更好地选择, 下面借几个问答来开始吧! 后续我会再写一篇有关于无锁队列的Blog
  1. synchonrize如何更好地使用?
  谈到这个问题, 主要先从这几个方面来入手:
  · 线程的几种状态
  · synchonrize的几种使用方法比较
  · synchonrize和volatile比较
  · synchonrize和juc中的锁比较
  · 用了锁就真的没有并发问题了么?
  1.1 线程的几种状态
  不熟悉线程的生命周期和相互的转换控制, 是无法写好并发代码的.
  图简单易懂, 主要是搞清楚, sleep, yield, wait, notify, notifyAll对于锁的处理, 这里就不多展开了. 简单比较如下:
  wait有出让Object锁的语义, 要想出让锁, 前提是要先获得锁, 所以要先用synchronized获得锁之后才能调用wait. notify原因类似, Object.wait()和notify()不具有原子性语义, 所以必须用synchronized保证线程安全.
  yield()方法对应了如下操作: 先检测当前是否有相同优先级的线程处于同可运行状态, 如有, 则把 CPU 的占有权交给此线程, 否则继续运行原来的线程. 所以yield()方法称为“退让”, 它把运行机会让给了同等优先级的其他线程.
  1.2 synchonrize的最佳实践
  synchronize关键字主要有下面5种用法
  在方法上进行同步, 分为(1)instance method/(2)static method, 这两个的区别后面说
  在内部块上进行同步, 分为(3)synchronize(this), (4)synchonrize(XXX.class), (5)synchonrize(mutex)
public class SyncMethod {
private int value = 0;
private final Object mutex = new Object();
public synchronized int incAndGet0() {
return ++value;
}
public int incAndGet1() {
synchronized(this){
return ++value;
}
}
public int incAndGet2() {
synchronized(SyncMethod.class){
return ++value;
}
}
public int incAndGet3() {
synchronized(mutex){
return ++value;
}
}
public static synchonrize int incAndGet4() {
synchronized(mutex){
return ++value;
}
}
}
  现在来分析:
  作为修饰符加在方法声明上, synchronized修饰非静态方法时表示锁住了调用该方法的堆对象, 修饰静态方法时表示锁住了这个类在方法区中的类对象.
  synchronized(X.class) 使用类对象作为monitor. 同一时间只有一个线程可以能访问块中资源.
  synchronized(this)和synchronized(mutex) 都是对象锁, 同一时间每个实例都保证只能有一个实例能访问块中资源.
  sychronized的对象最好选择引用不会变化的对象(例如被标记为final,或初始化后永远不会变), 虽然synchronized是在对象上加锁, 但是它首先要通过引用来定位对象, 如果引用会变化, 可能带来意想不到的后果
  1.3 synchronized和volatile比较
  简单的说就是synchronized的代码块是确保可见性和原子性的, volatile只能确保可见性 当且仅当下面条件全部满足时, 才能使用volatile
  对变量的写入操作不依赖于变量的当前值, (++i/i++这种肯定不行), 或者能确保只有单个线程在更新
  该变量不会与其他状态变量一起纳入不变性条件中
  访问变量时不需要加锁
  1.4 synchonrize和juc中的锁比较
  ReentrantLock在内存上的语义于synchronize相同, 但是它提供了额外的功能, 可以作为一种高级工具. 当需要一些 可定时, 可轮询, 可中断的锁获取操作, 或者希望使用公平锁, 或者使用非块结构的编码时 才应该考虑ReetrantLock.
  总结一点, 在业务并发简单清晰的情况下推荐synchronized, 在业务逻辑并发复杂, 或对使用锁的扩展性要求较高时, 推荐使用ReentrantLock这类锁. 另外今后JVM的优化方向一定是基于底层synchronize的, 性能方面应该选择synchronize
  1.5 用了锁就真的没有并发问题了么?
  先上代码, 看一下是否有并发问题
  Map syncMap = Collections.synchronizedMap(new HashMap());
  if(!map.containsKey("a")){
  map.put("a",value);
  }
  虽然Map上所有的方法都已被synchronize保护了, 但是在外部使用的时候, 一定要注意 竞态条件
  竞态条件: 先检查后执行的这种操作是最常见的竞态条件
  下面是并发条件下的一些Donts
  Don’t synchronize on an object you’re changing
  Don’t synchronize on a String literal
  Don’t synchronize on auto-boxed values
  Don’t synchronize on null
  Don’t synchronize on a Lock object
  Don’t synchronize on getClass()
  Be careful locking on a thread-safe object with encapsulated locking
  2. Juc中的同步辅助类
  2.1 Semaphore 信号量是一类经典的同步工具. 信号量通常用来限制线程可以同时访问的(物理或逻辑)资源数量.
  2.2 CountDownLatch 一种非常简单, 但很常用的同步辅助类. 其作用是在完成一组正在其他线程中执行的操作之前,允许一个或多个线程一直阻塞.
  2.3 CyclicBarrier 一种可重置的多路同步点, 在某些并发编程场景很有用. 它允许一组线程互相等待, 直到到达某个公共的屏障点 (common barrier point). 在涉及一组固定大小的线程的程序中, 这些线程必须不时地互相等待, 此时 CyclicBarrier 很有用.因为该 barrier在释放等待线程后可以重用, 所以称它为循环的barrier.
  2.4 Phaser 一种可重用的同步屏障, 功能上类似于CyclicBarrier和CountDownLatch, 但使用上更为灵活. 非常适用于在多线程环境下同步协调分阶段计算任务(Fork/Join框架中的子任务之间需同步时, 优先使用Phaser)
  2.5 Exchanger 允许两个线程在某个汇合点交换对象, 在某些管道设计时比较有用. Exchanger提供了一个同步点, 在这个同步点, 一对线程可以交换数据. 每个线程通过exchange()方法的入口提供数据给他的伙伴线程, 并接收他的伙伴线程提供的数据并返回. 当两个线程通过Exchanger交换了对象, 这个交换对于两个线程来说都是安全的. Exchanger可以认为是 SynchronousQueue 的双向形式, 在运用到遗传算法和管道设计的应用中比较有用.
21/212>
《2023软件测试行业现状调查报告》独家发布~

关注51Testing

联系我们

快捷面板 站点地图 联系我们 广告服务 关于我们 站长统计 发展历程

法律顾问:上海兰迪律师事务所 项棋律师
版权所有 上海博为峰软件技术股份有限公司 Copyright©51testing.com 2003-2024
投诉及意见反馈:webmaster@51testing.com; 业务联系:service@51testing.com 021-64471599-8017

沪ICP备05003035号

沪公网安备 31010102002173号