1、简介
在Java中,提供了一个关键字 synchronized 来保证多线程安全问题,synchronized可以修饰一个方法、代码块,能够保证在同一时刻最多只有一个线程执行该段代码。本篇文章将全面讲解synchronized 各种情景下的使用及产生的作用。
2、synchronized 修饰某一非静态方法
我们先看一下示例:
private class MyObject1{
public synchronized void method1(String name){
System.out.println(name+": method1 start sleep..."+new Date());
ThreadUtils.sleep(1000);
System.out.println(name+": method1 end sleep..."+new Date());
}
public synchronized void method2(String name){
System.out.println(name+": method2 start sleep..."+new Date());
ThreadUtils.sleep(2000);
System.out.println(name+": method2 end sleep..."+new Date());
}
}
测试代码如下:
private void test1(){
MyObject1 myObject1 = new MyObject1();
MyObject1 myObject2 = new MyObject1();
Thread thread1 = new Thread(new Runnable() {
@Override
public void run() {
myObject1.method1(Thread.currentThread().getName());
}
}, "thread1");
Thread thread2 = new Thread(new Runnable() {
@Override
public void run() {
myObject2.method1(Thread.currentThread().getName());
}
}, "thread2");
Thread thread3 = new Thread(new Runnable() {
@Override
public void run() {
myObject2.method1(Thread.currentThread().getName());
}
}, "thread3");
Thread thread4 = new Thread(new Runnable() {
@Override
public void run() {
myObject2.method2(Thread.currentThread().getName());
}
}, "thread4");
thread1.start();
thread2.start();
thread3.start();
thread4.start();
}
示例输出如下:
thread2: method1 start sleep...Sat Jun 13 09:22:31 CST 2020
thread1: method1 start sleep...Sat Jun 13 09:22:31 CST 2020
thread1: method1 end sleep...Sat Jun 13 09:22:32 CST 2020
thread2: method1 end sleep...Sat Jun 13 09:22:32 CST 2020
thread4: method2 start sleep...Sat Jun 13 09:22:32 CST 2020
thread4: method2 end sleep...Sat Jun 13 09:22:34 CST 2020
thread3: method1 start sleep...Sat Jun 13 09:22:34 CST 2020
thread3: method1 end sleep...Sat Jun 13 09:22:35 CST 2020
通过输出日志,我们可以得到如下总结:
1、不同的对象同步方法,被不同的线程执行,相互之间没有影响;例如 MyObject1 创建了两个对象
myObject1 和 myObject2 , 被两个不同的线程thread1、thread2调用其同步方法method1, 相互之间是没有影响的。
2、相同对象相同的同步方法,被不同线程执行,不同线程之间具有同步关系;例如 myObject2 的 method1 分别被 thread2 和 thread3 线程执行, 两个线程具有同步关系。
3、相同对象不同的同步方法,被不同线程执行,不同线程之间具有同步关系;例如 myObject2 的 method1 和 method2 分别被 thread3 和 thread4 线程执行, 两个线程具有同步关系。
一句话可以概括为:synchronized 修饰某一非静态方法,其实是为这个类添加对象锁,当某个类中 有多个 被synchronized修饰的同步方法,谁先持有了对象锁,谁先执行, 其他线程需要等待,即便线程调用的是不同的同步方法,也需要等待。
3、synchronized 修饰某一静态方法
通过上面的非静态方法分析,你是否能够猜想到当synchronized 修饰某一静态方法 具有哪些作用呢?请思考一分钟,再看下面讲解。
实例:
private static class MyObject1{
public static synchronized void method3(String name){
System.out.println(name+": method3 start sleep..."+new Date());
ThreadUtils.sleep(1000);
System.out.println(name+": method3 end sleep..."+new Date());
}
public static synchronized void method33(String name){
System.out.println(name+": method33 start sleep..."+new Date());
ThreadUtils.sleep(2000);
System.out.println(name+": method33 end sleep..."+new Date());
}
}
测试用例:
private void test(){
Thread thread3_1 = new Thread(new Runnable() {
@Override
public void run() {
MyObject1.method3(Thread.currentThread().getName());
}
}, "thread3_1");
Thread thread3_2 = new Thread(new Runnable() {
@Override
public void run() {
MyObject1.method33(Thread.currentThread().getName());
}
}, "thread3_2");
Thread thread3_3 = new Thread(new Runnable() {
@Override
public void run() {
MyObject1.method33(Thread.currentThread().getName());
}
}, "thread3_3");
thread3_1.start();
thread3_2.start();
thread3_3.start();
}
示例输出如下:
thread3_1: method3 start sleep...Sat Jun 13 09:53:29 CST 2020
thread3_1: method3 end sleep...Sat Jun 13 09:53:30 CST 2020
thread3_3: method33 start sleep...Sat Jun 13 09:53:30 CST 2020
thread3_3: method33 end sleep...Sat Jun 13 09:53:32 CST 2020
thread3_2: method33 start sleep...Sat Jun 13 09:53:32 CST 2020
thread3_2: method33 end sleep...Sat Jun 13 09:53:34 CST 2020
通过输出日志,我们可以得到如下总结:
1、不同的静态同步方法,被不同的线程执行,不同线程之间具有同步关系;例如 两个不同的线程thread3_1、thread3_2调用其同步方法method3、method33, 相互之间是同步执行的。
2、相同的静态同步方法,被不同线程执行,不同线程之间具有同步关系;例如 两个不同的线程thread3_2、thread3_3调用其同步方法method33、method33, 相互之间是同步执行的。
一句话可以概括为:synchronized 修饰某一静态方法,其实是为这个类添加类锁,当某个类中 有多个 被synchronized修饰的同步方法,谁先持有了类锁,谁先执行, 其他线程需要等待,即便线程调用的是不同的同步方法,也需要等待。
4、synchronized 修饰同步代码块
synchronized 修饰同步代码块 通常 有两种方式,分别如下:
方式一:
synchronized (MyObject1.class){
// 具体代码
………………
}
方式二:
synchronized (this){
// 具体代码
………………
}
这里不在用示例进行对比,大家可以自己去写测试用例,其实和上面1、2的出的结论是一样的,当线程执行到synchronized (this) 代码块中时, 当前线程持有了该对象的锁,具有的特性如1;当线程执行到synchronized (MyObject1.class) 代码块中时,当前线程持有了该类的锁,具有的特性如2。
5、synchronized 一些特殊使用
5.1 变量的一致性
首先我们看如下示例:
private static class MyService{
private boolean isContinueRun = true;
public void runMethod(){
while (isContinueRun){
// 具体执行代码
}
System.out.println(Thread.currentThread().getName()+" is stop ..."+new Date());
}
public void setContinueRun(boolean continueRun) {
System.out.println(Thread.currentThread().getName()+" set continueRun is "+continueRun+"..."+new Date());
isContinueRun = continueRun;
}
public boolean isContinueRun() {
return isContinueRun;
}
}
测试用例:
private void test(){
MyService myService = new MyService();
new Thread(new Runnable() {
@Override
public void run() {
myService.runMethod();
}
}).start();
ThreadUtils.sleep(500);
new Thread(new Runnable() {
@Override
public void run() {
myService.setContinueRun(false);
}
}).start();
ThreadUtils.sleep(500);
System.out.println(Thread.currentThread().getName()+" isContinueRun: "+myService.isContinueRun()+"... "+new Date());
}
示例输出如下:
Thread-1 set continueRun is false...Sat Jun 13 10:40:26 CST 2020
main isContinueRun: false... Sat Jun 13 10:40:27 CST 2020
程序并未执行完成。
原因很简单,每个线程都有自己的栈空间,它会把共享内存中的数据拷贝一份存放到自己的栈空间,因此当第二个线程 修改 共享内存中的isContinueRun时,只会修改自己栈及共享内存中的数据,并不会涉及到第一个线程栈中的数据。
要解决这个问题很简单,通常我们只需要将isContinueRun 添加要给关键字volatile 即可,该关键字修饰的变量具有线程可见性。
其实,我们还有另外一种解决方法,修改MyService中的runMethod,具体如下:
public void runMethod(){
while (isContinueRun){
synchronized (this){}
}
System.out.println(Thread.currentThread().getName()+" is stop ..."+new Date());
}
或者
public void runMethod(){
String str = new String();
while (isContinueRun){
synchronized (str){}
}
System.out.println(Thread.currentThread().getName()+" is stop ..."+new Date());
}
运行结果如下:
Thread-1 set continueRun is false...Sat Jun 13 11:13:41 CST 2020
Thread-0 is stop ...Sat Jun 13 11:13:41 CST 2020
main isContinueRun: false... Sat Jun 13 11:13:41 CST 2020
我们可以看到 第一个线程能够正常退出。
总结:synchronized 不仅可以解决同一时刻,只有一个线程执行该同步代码,还可以解决一个线程看到对象处于不一致的状态,保证进入同步方法或同步代码块之前的每个线程 修改的变量的效果。
参考文献
《Java多线程编程核心技术》