Android---Synchronized 和 ReentrantLock

Synchronized 基本使用

1. 修饰实例方法

public class SynchronizedMethods{
    
    private int sum = 0;

    public synchronized void calculate(){
        sum = sum + 1;
    }
}

这种情况下的锁对象是当前实例对象,因此只有同一个实例对象调用此方法才会产生互斥效果;不同的实例对象之间不会有互斥效果。 比如如下代码:

 上述代码是在不同线程中用不同的对象调用 printLog() 方法,彼此之间不会有排斥,运行结果如下:可以看出两个线程是交互执行的。

将上述代码做如下修改, 两个线程调用同一个对象的 printLog() 方法

执行效果如下, 只有某一个线程中的代码执行完后才会调用另外一个线程中的代码。此时两个线程之间是互斥的。

2. 修饰静态方法

如果 synchronized 修饰的是静态方法,则锁对象是当前类的 Class 对象即使在不同线程中调用不同实例对象,也会有互斥效果。修改代码如下

执行结果如下,可以看出两个线程还是依次执行的。

3. 修饰代码块

如果 synchronized 修饰的是代码块,则锁对象是括号"()"里的对象。如下代码可以看出,任何 Object 对象都可以看着锁对象

执行结果如下,可以看出两个线程还是依次执行的。

实现细节

synchronized 既可以作用于方法也可以作用于代码块。但在实现上是有区别的,比如如下代码使用 synchronized 作用于代码块

使用 javap -c Foo 查看上述代码的字节码,如下

可以看出,编译成的字节码包含 monitorenter 和 monitorexit 俩个字节码指令。

注意:有两个 monitorexit。虚拟机需要保证当异常出现时也能释放锁,因此两个 monitorexit ,一个是代码正常执行结束后释放锁,一个是代码执行异常时释放锁。

synchronized 修饰方法,如下所示。被 synchronized 修饰方法被编译成字节码后,在方法的 flags 属性中会被标记为 ACC_SYNCHRONIZED。当虚拟机访问一个被标记为ACC_SYNCHRONIZED的方法时,会自动在方法开始和结束时添加 monitorenter 和 monitorexit 指令。

monitorenter 和 monitorexit 可以理解为一把具体的锁,这个锁中保存着两个比较重要的属性:计数器指针

计数器:代表当前线程一共访问了几次这把锁;

指针:指向持有这把锁的线程。

ReentrantLock 的基本使用

ReentrantLock 的使用同 Synchronized 优点不同,它的加锁和解锁需要手动完成。

如上代码所示,ReentrantLock.lock() 和 ReentrantLock.unlock() 都需要手动完成。运行效果如下。ReentrantLock 也能实现于 Synchronized 相同的效果。

注意:将 unlock() 操作放在 finally 代码块中,是因为 ReentrantLock 并不会自动释放锁,当异常发生时,确保释放锁操作一定会被执行(finally 里的代码在异常发生时,也能执行)。而 Synchronized 在异常发生时会自动释放锁。

公平锁实现

ReentrantLock 有一个带参数的构造器,如下。

默认情况下 Synchronized 和 ReentrantLock 都是非公平锁,但是 ReentrantLock 可以通过传入一个 true 来创建一个公平锁。

公平锁:通过同步队列来实现多个线程按照申请锁的先后顺序获取锁。

使用实例如下:

读写锁(ReentrantReadWriteLock)

在常见的开发中,经常会定义一个线程间共享的用作缓存的数据结构。比如一个较大的 Map,缓存中保存了全部的城市 Id 和 name 对应关系。这个大 Map 绝大部分时间提供读服务,而写操作占用的时间很少,通常是在服务器启动时初始化,然后可以每隔一段时间再刷新缓存的数据。但是写操作开始到结束之间,不能再有其它读操作进行,并且写操作完成之后的更新数据需要对后续的读操作可见。

使用 concurrent 包中的读写锁(ReentrantReadWriteLock)实现上述功能,只需要在读操作时获取读锁,写操作时获取写锁即可。当写锁被获取到时,后续的读写锁都会被阻塞,写锁缩释放后,所有操作继续执行。

读写锁的使用

1. 创建读写锁

ReentrantReadWriteLock rwLock = new ReentrantReadWriteLock();

2. 通过读写锁对象分别获取读锁(ReadLock)和写锁(Write Lock)

ReentrantReadWriteLock.ReadLock readLock = rwLock.readLock();
ReentrantReadWriteLock.ReadLock writeLock = rwLock.writeLock();

3. 使用读锁同步缓存读操作,使用写锁同步缓存写操作

// 读操作
readLock.lock();
try{
    // 从缓存中读取数据
} finally{
    readLock.unlock();
}

// 写操作
writeLock.lock();
try{
    // 想缓存中写入数据
} finally{
    writeLock.lock();
}

具体实现

 如上代码,图中的 number 是线程中共享的数据,用来模拟缓存数据;图中1处,分别创建2个 Reader 线程并从缓存中读取数据,1个 Writer 将数据写入缓存中;图中2处,使用读锁(ReadLock)将读取数据的操作枷锁;图中3处,使用写锁(WriteLock)将写入数据到缓存中的操作加锁。

总结

● Java中两个实现同步的方式synchronized和ReentrantLock

● synchronized使用更简单,加锁和释放锁都是由虚拟机自动完成

● ReentrantLock需要开发者手动去完成,很Reentrantl ock的使用场景更多
公平锁读写锁都可以在复杂场景中发挥重要作用。

  • 0
    点赞
  • 1
    收藏
    觉得还不错? 一键收藏
  • 打赏
    打赏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包

打赏作者

别偷我的猪_09

你的鼓励将是我创作的最大动力

¥1 ¥2 ¥4 ¥6 ¥10 ¥20
扫码支付:¥1
获取中
扫码支付

您的余额不足,请更换扫码支付或充值

打赏作者

实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值