写点什么

博客之星:我去,你竟然还不会用 synchronized

  • 2021 年 11 月 11 日
  • 本文字数:1929 字

    阅读完需:约 6 分钟

点击 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() 方法进行判断。


运行该测试用例,结果会是什么呢?



很不幸,失败了。预期的值为 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


【一线大厂Java面试题解析+后端开发学习笔记+最新架构讲解视频+实战项目源码讲义】
浏览器打开:qq.cn.hn/FTf 免费领取
复制代码


));


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());


}

评论

发布
暂无评论
博客之星:我去,你竟然还不会用 synchronized