互斥锁(Mutex,mutual exclusion)
概念
互斥锁(Mutex,mutual exclusion的缩写 )是一种用于实现临界区保护的同步原语,目的是确保在任意时刻,只有一个线程或进程能够访问特定的共享资源,防止数据竞争和不一致性 。临界区是程序中访问共享资源的代码片段,线程在进入临界区前获取互斥锁,退出时释放锁,以此保证同一时间只有一个线程能执行该代码段。
操作
- 锁定(Lock):线程进入临界区前尝试获取互斥锁。若锁已被其他线程持有,当前线程将被阻塞,直到锁可用。
- 解锁(Unlock):线程退出临界区后释放持有的互斥锁,使其他等待线程可获取该锁。
实现方式
- 硬件实现:
- 在单处理器系统中,可通过关闭中断防止上下文切换,确保临界区操作的原子性,但此方法不适用于多处理器系统。
- 在多处理器环境下,常利用原子操作,如测试并设置(Test - and - Set)或比较并交换(Compare - and - Swap)实现互斥锁。这些操作由硬件支持,能在一个原子操作中完成锁状态的检查和设置。
- 软件实现:缺乏硬件支持时,可使用软件算法实现互斥锁,如Dekker算法和Peterson算法 。但这些算法需频繁检查共享变量状态,可能导致忙等待,浪费CPU资源。
使用注意事项
- 死锁:多个线程若以不同顺序获取多个锁,可能导致死锁。比如线程A持有锁1等待锁2,线程B持有锁2等待锁1,双方都无法继续执行。避免死锁可确保所有线程按相同顺序获取锁,或采用尝试锁定机制,无法获取锁时采取适当措施。
- 优先级反转:高优先级线程等待低优先级线程释放锁,会使系统性能下降。可使用优先级继承协议,让低优先级线程持有锁时临时提升优先级。
- 锁的粒度:锁粒度过大降低系统并发性能,粒度过小则增加锁管理开销,需依具体情况选择合适粒度。
Java代码示例
在Java中,可通过java.util.concurrent.locks.ReentrantLock
类实现互斥锁功能,示例如下:
import java.util.concurrent.locks.ReentrantLock;
class SharedResource {
private int count = 0;
private ReentrantLock lock = new ReentrantLock();
public void increment() {
lock.lock(); // 加锁
try {
count++;
} finally {
lock.unlock(); // 解锁
}
}
public int getCount() {
return count;
}
}
class MyThread extends Thread {
private SharedResource sharedResource;
MyThread(SharedResource sharedResource) {
this.sharedResource = sharedResource;
}
@Override
public void run() {
for (int i = 0; i < 1000; i++) {
sharedResource.increment();
}
}
}
public class MutexExample {
public static void main(String[] args) throws InterruptedException {
SharedResource sharedResource = new SharedResource();
MyThread thread1 = new MyThread(sharedResource);
MyThread thread2 = new MyThread(sharedResource);
thread1.start();
thread2.start();
thread1.join();
thread2.join();
System.out.println("Final count: " + sharedResource.getCount());
}
}
上述代码中,ReentrantLock
实现互斥锁。SharedResource
类有共享变量count
,increment
方法中先调用lock.lock()
加锁,确保同一时刻只有一个线程能执行count++
操作,操作完成后在finally
块中用lock.unlock()
解锁,保证锁一定能被释放。MyThread
类的线程对共享资源进行操作,main
方法创建两个线程并启动,最后输出共享变量最终值。