博客之星:我去,你竟然还不会用 synchronized
点击 OK 按钮后,IDEA 会在 src 的同级目录 test 下创建一个名为 SynchronizedMethodTest 的测试类:
class SynchronizedMethodTest {
@Test
void calculate() {
}
}
calculate()
方法上会有一个 @Test
的注解,表示这是一个测试方法。添加具体的代码,如下所示:
ExecutorService service = Executors.newFixedThreadPool(3);
SynchronizedMethod summation = new SynchronizedMethod();
IntStream.range(0, 1000)
.forEach(count -> service.submit(summation::calculate));
service.awaitTermination(1000, TimeUnit.MILLISECONDS);
assertEquals(1000, summation.getSum());
1)Executors.newFixedThreadPool()
方法可以创建一个指定大小的线程池服务 ExecutorService。
2)通过 IntStream.range(0, 1000).forEach()
来执行 calculate()
方法 1000 次。
3)通过 assertEquals()
方法进行判断。
运行该测试用例,结果会是什么呢?
![](https://static001.geekbang.org/infoq/cd/cdc73eae45a27cf1c24ded5863349ae5.png)
很不幸,失败了。预期的值为 1000,但实际的值是 976。这是因为多线程环境下,可变的共享数据没有得到保护。
02、synchronized 的用法
这么说吧,初学者在遇到多线程问题时,只要 synchronized 关键字使用得当,问题就能够迎刃而解。记得我刚回洛阳的时候,面试官问我,项目中是怎么解决并发问题的呢?我就说用 synchronized 关键字,至于其他的一些锁机制,我那时候还不知道。
嗯,面试官好像也不知道,因为小公司嘛,并发的量级有限,性能也不用考量得太过深入(大公司的读者可以呵呵了)。接下来,就随我来,一起看看 synchronized 最常见的三种用法吧。
1)直接用在方法上,就像下面这样:
public synchronized void synchronizedCalculate() {
setSum(getSum() + 1);
}
修改一下测试用例:
@Test
void synchronizedCalculate() throws InterruptedException {
ExecutorService service = Executors.newFixedThreadPool(3);
SynchronizedMethod summation = new SynchronizedMethod();
IntStream.range(0, 1000)
.forEach(count -> service.submit(summation::synchronizedCalculate
));
service.awaitTermination(1000, TimeUnit.MILLISECONDS);
assertEquals(1000, summation.getSum());
}
这时候,再运行测试用例就通过了。因为 synchronized 关键字会对 SynchronizedMethod 对象进行加锁,同一时间内只允许一个线程对 sum 进行修改。这就好像有一间屋子,线程进入屋子里面才可以对 sum 加 1,而 synchronized 就相当于在门上加了一个锁,一个线程进去后就锁上门,修改完 sum 后,下一个线程再进去,其他线程就在门外候着。
2)用在 static 方法上,就像下面这样:
public class SynchronizedStaticMethod {
public static int sum;
public synchronized static void synchronizedCalculate() {
sum = sum + 1;
}
}
sum 是一个静态变量,要修改静态变量的时候,就需要把方法也变成 static 的。
来新建一个测试用例:
class SynchronizedStaticMethodTest {
@Test
void synchronizedCalculate() throws InterruptedException {
ExecutorService service = Executors.newFixedThreadPool(3);
IntStream.range(0, 1000)
.forEach(count -> service.submit(SynchronizedStaticMethod::synchronizedCalculate));
service.awaitTermination(1000, TimeUnit.MILLISECONDS);
assertEquals(1000, SynchronizedStaticMethod.sum);
}
}
静态方法上添加 synchronized 的时候就不需要实例化对象了,直接使用类名就可以引用方法和使用变量了。测试用例也是可以通过的。
synchronized static 和 synchronized 不同的是,前者锁的是类,同一时间只能有一个线程访问这个类;后者锁的是对象,同一时间只能有一个线程访问方法。
3)用在方法块上,就像下面这样:
public void synchronisedThis() {
synchronized (this) {
setSum(getSum() + 1);
}
}
这时候,将 this 传递给了 synchronized 代码块,当在某个线程中执行这段代码块,该线程会获取 this 对象的锁,从而使得其他线程无法同时访问该代码块。如果方法是静态的,我们将传递类名代替对象引用,示例如下所示:
public static void synchronisedThis() {
synchronized (SynchronizedStaticMethod.class) {
sum = sum + 1;
}
}
新建一个测试用例:
@Test
void synchronisedThis() throws InterruptedException {
ExecutorService service = Executors.newFixedThreadPool(3);
SynchronizedMethod summation = new SynchronizedMethod();
IntStream.range(0, 1000)
.forEach(count -> service.submit(summation::synchronisedThis));
service.awaitTermination(1000, TimeUnit.MILLISECONDS);
assertEquals(1000, summation.getSum());
}
评论