1、如何定义锁
1.1、Java如何定义锁
Java中以可重入锁ReentrantLock
为例,其定义为ReentrantLock lock = new ReentrantLock();
1.2、Go如何定义锁
lock:=sync.Mutex{}
2、如何使用锁
通过写一个简单的计数器程序来看看Java和Go的锁分别如何使用。
题目:用10个线程/协程同时计数,每个线程/协程从1计数到1000,所有线程/协程计数完以后打印最终的计数结果。
有经验的开发者都知道对于并发计数,肯定会存在线程/协程安全问题,导致最后计数不准确。
2.1、Java如何使用锁
/**
* 计数器
*/
static class Counter {
private int c;
public int getC() {
return c;
}
public void setC(int c) {
this.c = c;
}
}
public static void countWithLock() throws InterruptedException {
// 计数器
Counter c = new Counter();
// 定义门闩,目的是等所有协程执行完后打印最终计数
CountDownLatch latch = new CountDownLatch(10);
// 定义锁
ReentrantLock lock = new ReentrantLock();
for (int i = 0; i < 10; i++) {
new Thread(() -> {
for (int j = 0; j < 1000; j++) {
// 加锁
lock.lock();
try {
// 计数
c.setC(c.getC() + 1);
} finally {
// 解锁
lock.unlock();
}
}
latch.countDown();
}).start();
}
latch.await();
System.out.println(c.getC());
}
这里需要注意一下:
- 这里的计数器不能直接使用
int c = 0;
,因为lambda表达式里不能使用变化的变量。 - 其实可以直接将计数器定义为原子类型
AtomicInteger c = new AtomicInteger();
,这样即使不加锁,也可以得出正确的结果。 - 这里为了演示锁的使用,再结合上面两个点,将计数器声明为了一个对象。
2.2、Go如何使用锁
func countWithLock() {
// 定义计数器
count:=0
// 定义门闩,目的是等所有协程执行完后打印最终计数
wg:=sync.WaitGroup{}
wg.Add(10)
// 定义锁
lock:=sync.Mutex{}
for i := 0; i < 10; i++ {
go func() {
defer wg.Done()
for i := 0; i < 1000; i++ {
// 加锁
lock.Lock()
// 计数
count++
// 解锁
lock.Unlock()
}
}()
}
wg.Wait()
fmt.Println("count=",count)
}
上面程序中使用的CountDownLatch和WaitGroup,可以参考上一篇《Go语言 和 Java语言对比理解系列四:门闩》
3、联系与区别
- 都实现了锁接口。Java实现了
java.util.concurrent.locks.Lock
接口;Go实现了sync.Locker
接口。 - 都有两个直观的方法:加锁/解锁。Java对应lock()/unlock();Go对应Lock()/UnLock()。
- 两者内部都是基于CAS(CompareAndSet)实现加锁。
- 两者都实现了公平/非公平模式。Java对应公平锁/非公平锁;Go对应饥饿模式/正常模式。Java中需要在定义锁的时候指定是公平或非公平;Go里面是根据条件自动切换两种模式。