实用的Java并发编程——互斥锁

在这个张文中,我们主要讨论如何使用互斥来解决并发编程中的原子性问题。

总结
并发编程中原子性问题的根源是线程切换,那么禁止线程切换能解决原子性问题吗?

这需要逐案讨论。在单核CPU的情况下,同一时间只有一个线程在执行,禁止CPU中断,也就是说操作系统不会重新调度线程,从而禁止线程切换,让获得CPU使用权的线程可以继续执行。

在多核CPU的情况下,同时可能有两个线程同时执行,一个线程在CPU-1上执行,另一个线程在CPU-2上执行。此时禁止CPU中断,只能保证某个CPU线程的连续执行,但不能保证只有一个线程在运行。

同一时间只有一个线程在执行,这叫互斥。如果能保证共享变量的修改互斥,那么单核CPU和多核CPU都可以保证原子性。

怎么做?答案是互斥。

互斥锁模型
互斥锁的简单模型
当我们谈到互斥时,我们通常把一段需要互斥执行的代码称为临界段。下面是一个简单的图表。

在线程进入临界区之前,先尝试锁定它。如果成功,就可以进入临界区。如果失败了,需要等待。当关键区域中的代码被执行或发生异常时,线程释放锁。

互斥锁的改进模型
上面的模型虽然直观,但是太简单了。我们需要考虑两个问题:

我们锁定了什么?
我们在保护什么?
在现实世界中,锁和要被锁保护的资源是有对应关系的。一般来说,你用你的锁保护你的东西,而我用我的锁保护我的东西。

在并发编程的世界里,锁和资源应该有相似的对应关系。

下面是一个改进的锁模型。

首先,我们应该在关键区域标记要保护的资源R。然后,我们为资源r创建一个锁LR,最后,当我们进入和离开临界区时,我们需要锁定和解锁锁LR。

通过这样的处理,我们建立了锁和资源的关联关系,不会出现类似“用我的锁保护你的资源”的问题。

Java世界中的互斥锁
在Java语言中,我们使用synchronized关键字实现互斥锁。

synchronized关键字可以应用于方法或直接应用于代码块。

让我们看看下面的示例代码。

public class SynchronizedDemo {

// 修饰实例方法
synchronized void updateData() {
	// 业务代码
}

// 修饰静态方法
synchronized static void retrieveData() {
	// 业务代码
}

// 修饰代码块
Object obj = new Object();

void createData() {
	synchronized(obj) {
		// 业务代码
	}
}

}

与我们描述的互斥模型相比,我们在上面的代码中看不到与锁定和解锁相关的代码,因为Java编译器已经在synchronized关键字修改的方法或代码块前后自动为我们添加了锁定和解锁逻辑。这样做的好处是我们不用担心锁定后忘记解锁。

同步锁定和锁定对象
当我们使用synchronized关键字时,它锁定的对象是什么?如果没有明确指定锁对象,Java有以下默认规则

修改静态方法时,当前类的类对象被锁定。
修改非静态方法时,当前实例对象被锁定。
根据上述规则,以下代码是等效的。

// 修饰实例方法
synchronized void updateData() {
// 业务代码
}

// 修饰实例方法
synchronized(this) void updateData2() {
	// 业务代码
}
// 修饰静态方法
synchronized static void retrieveData() {
	// 业务代码
}

// 修饰静态方法
synchronized(SynchronizedDemo.class) static void retrieveData2() {
	// 业务代码
}
  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

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

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

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

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

余额充值