参考二哥:https://blog.csdn.net/qing_gee/article/details/106032117
一、为什么使用synchronized
在多线程并发修改共享数据时无法保证线程安全。synchronized
关键字可以保证在某一时刻只有一个线程可以访问某个方法或者代码块,只有当前线程执行完成其他线程才能够重新拿到执行权力。
下面我们来演示一下如果不是用synchronized
关键字的情况下,使用多线程操作会出现什么情况。
首先我们创建一个类SynchronizedMethod
,里面只有一个变量就是number
,
里面有一个addOne()
方法,每调用一次会使当前对象的number
值+1
package com.example.demo1.entity;
/**
* create by c-pown on 2020-06-04
*/
public class SynchronizedMethod {
private int number;
public int getNumber() {
return number;
}
public void setNumber(int number) {
this.number = number;
}
public void addOne(){
setNumber(getNumber() + 1);
}
}
我们新建一个测试类测试一下,多线程连续调用1000次是什么结果
package com.example.demo1.entity;
import org.junit.Test;
import java.util.concurrent.ExecutorService;
import java.util.concurrent.Executors;
import java.util.concurrent.TimeUnit;
import java.util.stream.IntStream;
import java.util.stream.Stream;
import static org.junit.Assert.*;
public class SynchronizedMethodTest {
@Test
public void addOne() throws InterruptedException{
SynchronizedMethod synchronizedMethod = new SynchronizedMethod();
ExecutorService executor = Executors.newFixedThreadPool(3);
IntStream.range(0,1000).forEach(count->executor.submit(synchronizedMethod::addOne));
executor.awaitTermination(10, TimeUnit.SECONDS);
executor.shutdown();
System.out.println("当前数值:"+synchronizedMethod.getNumber());
}
}
1)Executors.newFixedThreadPool()
方法可以创建一个指定大小的线程池服务 ExecutorService
。
2)通过 IntStream.range(0, 1000).forEach()
来执行 addOne()
方法 1000 次。
很不幸,我们发现结果并不是1000,这是因为多线程操作数据时,可变的共享数据没有得到保护。
下面我们给addOne()
方法里面的方法体添加一些打印信息,看下具体执行过程是怎样的。
public void addOne(){
System.out.println("当前线程:"+Thread.currentThread().getName()+"开始");
try {
//为了使结果更加明显我们延迟5ms
Thread.sleep(5);
} catch (InterruptedException e) {
e.printStackTrace();
}
setNumber(getNumber() + 1);
System.out.println("当前线程:"+Thread.currentThread().getName()+"结束");
}
我们在调用一下看看:
可以明显的看出,在线程1
访问方法addOne()
的时候,并没有执行结束,线程2、线程3
就已经开始访问方法了,很明显,线程不安全。
二、synchronized关键字的使用
1.我们给addOne()方法前边加上synchronized关键字在看一下
public synchronized void addOne(){
System.out.println("当前线程:"+Thread.currentThread().getName()+"开始");
try {
Thread.sleep(5);
} catch (InterruptedException e) {
e.printStackTrace();
}
setNumber(getNumber() + 1);
System.out.println("当前线程:"+Thread.currentThread().getName()+"结束");
}
可以发现当前线程在执行过程中,其他线程是无法加入的,就好比一个屋子,一个线程进去了就会关上门,其他人只能在门外侯着,他出来了别人才能进去,这就保证了线程安全。
结果也是没有问题。
2.给静态方法加上synchronized
package com.example.demo1.entity;
/**
* create by c-pown on 2020-06-04
*/
public class SynchronizedMethod {
public static int number;
public synchronized static void addOne(){
System.out.println("当前线程:"+Thread.currentThread().getName()+"开始");
try {
Thread.sleep(5);
} catch (InterruptedException e) {
e.printStackTrace();
}
number=number+1;
System.out.println("当前线程:"+Thread.currentThread().getName()+"结束");
}
}
package com.example.demo1.entity;
import org.junit.Test;
import java.util.concurrent.ExecutorService;
import java.util.concurrent.Executors;
import java.util.concurrent.TimeUnit;
import java.util.stream.IntStream;
import java.util.stream.Stream;
import static org.junit.Assert.*;
public class SynchronizedMethodTest {
@Test
public void addOne() throws InterruptedException{
ExecutorService executor = Executors.newFixedThreadPool(3);
IntStream.range(0,1000).forEach(count->executor.submit(SynchronizedMethod::addOne));
executor.awaitTermination(10, TimeUnit.SECONDS);
executor.shutdown();
System.out.println("当前数值:"+SynchronizedMethod.number);
}
}
静态方法无需实例化,直接使用类名调用即可
也是没毛病的。
三、synchronized作用于同步代码块
package com.example.demo1.entity;
/**
* create by c-pown on 2020-06-04
*/
public class SynchronizedMethod {
private int number;
public int getNumber() {
return number;
}
public void setNumber(int number) {
this.number = number;
}
public void addOne(){
System.out.println("当前线程:"+Thread.currentThread().getName()+"开始");
try {
Thread.sleep(5);
} catch (InterruptedException e) {
e.printStackTrace();
}
synchronized (this){
setNumber(getNumber() + 1);
}
System.out.println("当前线程:"+Thread.currentThread().getName()+"结束");
}
}
我们方法里面最核心的部分加上synchronized
代码块,在执行一次。
synchronized (this){
setNumber(getNumber() + 1);
}
我们会发现线程1
进入addOne()
方法的时候线程2
,线程3
也是可以进来的,说明方法体的其他地方并不是线程安全的,但是setNumber(getNumber() + 1)
这一块时却是线程安全的,从而保证结果的正确与一致性。