Java 8新特性探究(十)StampedLock将是解决同步问题的新宠

简介: 本文将从synchronized、Lock到新增的StampedLock进行对比分析

Java8就像一个宝藏,一个小的API改进,也足与写一篇文章,比如同步,一直是多线程并发编程的一个老话题,相信没有人喜欢同步的代码,这会降低应用的吞吐量等性能指标,最坏的时候会挂起死机,但是即使这样你也没得选择,因为要保证信息的正确性。所以本文决定将从synchronized、Lock到Java8新增的StampedLock进行对比分析,相信StampedLock不会让大家失望。

synchronized

在java5之前,实现同步主要是使用synchronized。它是Java语言的关键字,当它用来修饰一个方法或者一个代码块的时候,能够保证在同一时刻最多只有一个线程执行该段代码。

有四种不同的同步块:

  1. 实例方法
  2. 静态方法
  3. 实例方法中的同步块
  4. 静态方法中的同步块

大家对此应该不陌生,所以不多讲了,以下是代码示例
``
synchronized(this)
// do operation
}

小结:在多线程并发编程中Synchronized一直是元老级角色,很多人都会称呼它为重量级锁,但是随着Java SE1.6对Synchronized进行了各种优化之后,性能上也有所提升。

###Lock

它是Java 5在java.util.concurrent.locks新增的一个API。

Lock是一个接口,核心方法是lock(),unlock(),tryLock(),实现类有ReentrantLock, ReentrantReadWriteLock.ReadLock, ReentrantReadWriteLock.WriteLock;

ReentrantReadWriteLock, ReentrantLock 和synchronized锁都有相同的内存语义。

与synchronized不同的是,Lock完全用Java写成,在java这个层面是无关JVM实现的。Lock提供更灵活的锁机制,很多synchronized 没有提供的许多特性,比如锁投票,定时锁等候和中断锁等候,但因为lock是通过代码实现的,要保证锁定一定会被释放,就必须将unLock()放到finally{}中

下面是Lock的一个代码示例

rwlock.writeLock().lock();
try {
// do operation
} finally {
rwlock.writeLock().unlock();
}

小结:比synchronized更灵活、更具可伸缩性的锁定机制,但不管怎么说还是synchronized代码要更容易书写些

###StampedLock

它是java8在java.util.concurrent.locks新增的一个API。

ReentrantReadWriteLock 在沒有任何读写锁时,才可以取得写入锁,这可用于实现了悲观读取(Pessimistic Reading),即如果执行中进行读取时,经常可能有另一执行要写入的需求,为了保持同步,ReentrantReadWriteLock 的读取锁定就可派上用场。

然而,如果读取执行情况很多,写入很少的情况下,使用 ReentrantReadWriteLock 可能会使写入线程遭遇饥饿(Starvation)问题,也就是写入线程吃吃无法竞争到锁定而一直处于等待状态。

StampedLock控制锁有三种模式(写,读,乐观读),一个StampedLock状态是由版本和模式两个部分组成,锁获取方法返回一个数字作为票据stamp,它用相应的锁状态表示并控制访问,数字0表示没有写锁被授权访问。在读锁上分为悲观锁和乐观锁。

所谓的乐观读模式,也就是若读的操作很多,写的操作很少的情况下,你可以乐观地认为,写入与读取同时发生几率很少,因此不悲观地使用完全的读取锁定,程序可以查看读取资料之后,是否遭到写入执行的变更,再采取后续的措施(重新读取变更信息,或者抛出异常) ,这一个小小改进,可大幅度提高程序的吞吐量!!

下面是java doc提供的StampedLock一个例子

class Point {
private double x, y;
private final StampedLock sl = new StampedLock();
void move(double deltaX, double deltaY) { // an exclusively locked method

 long stamp = sl.writeLock();
 try {
   x += deltaX;
   y += deltaY;
 } finally {
   sl.unlockWrite(stamp);
 }

}
//下面看看乐观读锁案例
double distanceFromOrigin() { // A read-only method

 long stamp = sl.tryOptimisticRead(); //获得一个乐观读锁
 double currentX = x, currentY = y; //将两个字段读入本地局部变量
 if (!sl.validate(stamp)) { //检查发出乐观读锁后同时是否有其他写锁发生?
    stamp = sl.readLock(); //如果没有,我们再次获得一个读悲观锁
    try {
      currentX = x; // 将两个字段读入本地局部变量
      currentY = y; // 将两个字段读入本地局部变量
    } finally {
       sl.unlockRead(stamp);
    }
 }
 return Math.sqrt(currentX * currentX + currentY * currentY);

}
//下面是悲观读锁案例
void moveIfAtOrigin(double newX, double newY) { // upgrade

 // Could instead start with optimistic, not read mode
 long stamp = sl.readLock();
 try {
   while (x == 0.0 && y == 0.0) { //循环,检查当前状态是否符合
     long ws = sl.tryConvertToWriteLock(stamp); //将读锁转为写锁
     if (ws != 0L) { //这是确认转为写锁是否成功
       stamp = ws; //如果成功 替换票据
       x = newX; //进行状态改变
       y = newY; //进行状态改变
       break;
     }
     else { //如果不能成功转换为写锁
       sl.unlockRead(stamp); //我们显式释放读锁
       stamp = sl.writeLock(); //显式直接进行写锁 然后再通过循环再试
     }
   }
 } finally {
   sl.unlock(stamp); //释放读锁或写锁
 }

}
}

小结:

StampedLock要比ReentrantReadWriteLock更加廉价,也就是消耗比较小。

###StampedLock与ReadWriteLock性能对比

下图是和ReadWritLock相比,在一个线程情况下,是读速度其4倍左右,写是1倍。

![image](https://yqfile.alicdn.com/e2860bbe5e554d1e2c04d18b870691e304bcf1b8.png)

下图是六个线程情况下,读性能是其几十倍,写性能也是近10倍左右:

![image](https://yqfile.alicdn.com/c2b5528626b145a846882cc749fcd3c737537b97.png)

下图是吞吐量提高:

![image](https://yqfile.alicdn.com/6e6e3fb7dd3b4825f5dd442fb7d21859a21943bf.png)

###总结

1、synchronized是在JVM层面上实现的,不但可以通过一些监控工具监控synchronized的锁定,而且在代码执行时出现异常,JVM会自动释放锁定;

2、ReentrantLock、ReentrantReadWriteLock,、StampedLock都是对象层面的锁定,要保证锁定一定会被释放,就必须将unLock()放到finally{}中;

3、StampedLock 对吞吐量有巨大的改进,特别是在读线程越来越多的场景下;

4、StampedLock有一个复杂的API,对于加锁操作,很容易误用其他方法;

5、当只有少量竞争者的时候,synchronized是一个很好的通用的锁实现;

6、当线程增长能够预估,ReentrantLock是一个很好的通用的锁实现;

StampedLock 可以说是Lock的一个很好的补充,吞吐量以及性能上的提升足以打动很多人了,但并不是说要替代之前Lock的东西,毕竟他还是有些应用场景的,起码API比StampedLock容易入手,下篇博文争取更新快一点,可能会是Nashorn的内容,这里允许我先卖个关子。。。
相关文章
|
21天前
|
安全 Java 数据安全/隐私保护
|
21天前
|
搜索推荐 Java
Java的面向对象特性主要包括封装、继承和多态
【4月更文挑战第5天】Java的面向对象特性主要包括封装、继承和多态
15 3
|
23天前
|
移动开发 Java Android开发
构建高效Android应用:探究Kotlin与Java的性能差异
【4月更文挑战第3天】在移动开发领域,性能优化一直是开发者关注的焦点。随着Kotlin的兴起,其在Android开发中的地位逐渐上升,但关于其与Java在性能方面的对比,尚无明确共识。本文通过深入分析并结合实际测试数据,探讨了Kotlin与Java在Android平台上的性能表现,揭示了在不同场景下两者的差异及其对应用性能的潜在影响,为开发者在选择编程语言时提供参考依据。
|
30天前
|
Java 编译器 Android开发
构建高效Android应用:探究Kotlin与Java的性能差异
在开发高性能的Android应用时,选择合适的编程语言至关重要。近年来,Kotlin因其简洁性和功能性受到开发者的青睐,但其性能是否与传统的Java相比有所不足?本文通过对比分析Kotlin与Java在Android平台上的运行效率,揭示二者在编译速度、运行时性能及资源消耗方面的具体差异,并探讨在实际项目中如何做出最佳选择。
18 4
|
1天前
|
安全 Java 大数据
探索Java的奇妙世界:语言特性与实际应用
探索Java的奇妙世界:语言特性与实际应用
|
2天前
|
Java
【Java基础】详解面向对象特性(诸如继承、重载、重写等等)
【Java基础】详解面向对象特性(诸如继承、重载、重写等等)
6 0
|
8天前
|
机器学习/深度学习 Java API
Java8中的新特性
Java8中的新特性
|
9天前
|
存储 缓存 安全
Java并发基础之互斥同步、非阻塞同步、指令重排与volatile
在Java中,多线程编程常常涉及到共享数据的访问,这时候就需要考虑线程安全问题。Java提供了多种机制来实现线程安全,其中包括互斥同步(Mutex Synchronization)、非阻塞同步(Non-blocking Synchronization)、以及volatile关键字等。 互斥同步(Mutex Synchronization) 互斥同步是一种基本的同步手段,它要求在任何时刻,只有一个线程可以执行某个方法或某个代码块,其他线程必须等待。Java中的synchronized关键字就是实现互斥同步的常用手段。当一个线程进入一个synchronized方法或代码块时,它需要先获得锁,如果
24 0
|
10天前
|
分布式计算 Java API
Java 8新特性之Lambda表达式与Stream API
【4月更文挑战第16天】本文将介绍Java 8中的两个重要新特性:Lambda表达式和Stream API。Lambda表达式是Java 8中引入的一种新的编程语法,它允许我们将函数作为参数传递给其他方法,从而使代码更加简洁、易读。Stream API是Java 8中引入的一种新的数据处理方式,它允许我们以声明式的方式处理数据,从而使代码更加简洁、高效。本文将通过实例代码详细讲解这两个新特性的使用方法和优势。
|
17天前
|
Java API 开发者
Java 8新特性之函数式编程实战
【4月更文挑战第9天】本文将深入探讨Java 8的新特性之一——函数式编程,通过实例演示如何运用Lambda表达式、Stream API等技术,提高代码的简洁性和执行效率。