前言:相信大家在进行Java开发的时候经常会接触到同步的概念,在多线程并发的情况下,为保证同一个时间点只能被一个线程访问到,就需要用到同步机制。想要了解更多关于Java多线程知识,请移步:Android多线程机制专栏
对于一段代码片,或者一个方法怎么进行线程同步?这时就会用到我们今天的主角(synchronized)了。我们日常使用synchronized的时候,经常会直接在方法前面加上synchronized关键字,或者用synchronized(this)直接修饰一段代码片等,就能实现同步操作了。但是不同的使用情况有什么区别?作用范围又是什么?相信还是有很多同学不甚了解吧。其实它涉及到类锁和对象锁两个概念呢!!
今天我们就一起来探讨synchronized的多种用法,通过代码来分析各种情况的意义和原理。
(1)在person内部有synchronized修饰方法和普通方法:
public class Person {
private String name;
public Person(String name) {
this.name = name;
}
public synchronized void sayHello(){
for(int i=0;i<3;i++){
try {
System.out.println(name+" say hello: "+i);
Thread.sleep(1000);
} catch (InterruptedException e) {
e.printStackTrace();
}
}
}
public void sayWelcome(){
for(int i=0;i<3;i++){
try {
System.out.println(name+" say Welcome: "+i);
Thread.sleep(1000);
} catch (InterruptedException e) {
e.printStackTrace();
}
}
}
}
调用测试方法,不同线程同时访问synchronized修饰的sayhello()方法和普通方法saywelcome():
final Person person1 = new Person("person1");
Thread thread1 = new Thread(new Runnable() {
@Override
public void run() {
person1.sayHello();
}
});
Thread thread2 = new Thread(new Runnable() {
@Override
public void run() {
person1.sayWelcome();
}
});
thread1.start();
thread2.start();
控制台输出结果为:
person1 say hello: 0
person1 say Welcome: 0
person1 say hello: 1
person1 say Welcome: 1
person1 say hello: 2
person1 say Welcome: 2
结果显示:一个线程访问对象的synchronized修饰同步方法时,另一个线程仍然可以访问对象的非synchronized修饰的方法。
(2)模拟不同线程同时访问同一对象的同步方法:
final Person person1 = new Person("person1");
Thread thread1 = new Thread(new Runnable() {
@Override
public void run() {
person1.sayHello();
}
});
Thread thread2 = new Thread(new Runnable() {
@Override
public void run() {
//也调用sayHello同步方法
person1.sayHello();
}
});
thread1.start();
thread2.start();
直接修改测试代码,两个线程同时调用synchronized修饰的sahello()方法。
控制台输出结果为:
person1 say hello: 0
person1 say hello: 1
person1 say hello: 2
person1 say hello: 0
person1 say hello: 1
person1 say hello: 2
结果显示:一个线程访问对象的synchronized修饰同步方法时,另一个线程想要访问对象的synchronized修饰的方法时需要等待。
(3)模拟不同线程同时访问不同对象的同步方法:
仍然不需要修改person中的代码,测试代码修改为:
final Person person1 = new Person("person1");
final Person person2 = new Person("person2");
Thread thread1 = new Thread(new Runnable() {
@Override
public void run() {
person1.sayHello();
}
});
Thread thread2 = new Thread(new Runnable() {
@Override
public void run() {
person2.sayHello();
}
});
thread1.start();
thread2.start();
控制台输出为:
person1 say hello: 0
person2 say hello: 0
person1 say hello: 1
person2 say hello: 1
person1 say hello: 2
person2 say hello: 2
结果显示:不同线程访问同一个类的不同对象的同一同步方法时,没有任何影响。这就是上面提到的对象锁,只是在对象内有效。
修改person类中同步代码的书写方式为:
public void sayHello(){
synchronized (this) {
for (int i = 0; i < 3; i++) {
try {
System.out.println(name + " say hello: " + i);
Thread.sleep(1000);
} catch (InterruptedException e) {
e.printStackTrace();
}
}
}
}
(4) 在不同线程同时访问同步方法和非同步方法时,控制台的输出为:
person1 say hello: 0
person1 say Welcome: 0
person1 say hello: 1
person1 say Welcome: 1
person1 say hello: 2
person1 say Welcome: 2
(5)在不同线程同时访问同一对象的同步方法时,控制台的输出为:
person1 say hello: 0
person1 say hello: 1
person1 say hello: 2
person1 say hello: 0
person1 say hello: 1
person1 say hello: 2
(6)在不同线程分别访问同一类不同对象的同步方法时,控制台的输出为:
person1 say hello: 0
person2 say hello: 0
person1 say hello: 1
person2 say hello: 1
person1 say hello: 2
person2 say hello: 2
通过例4、5、6的测试,可以看出分别和例1、2、3的结果完全相同。
结论:通过在方法前加synchronized关键字和在方法内使用synchronized(this)进行方法同步时,起到相同的效果,它们都属于对象锁,是对对象进行加锁。访问同一个类的不同实例间的同步方法互不影响
修改person类sayhello()方法为:
public void sayHello(){
synchronized (Person.class) {
for (int i = 0; i < 3; i++) {
try {
System.out.println(name + " say hello: " + i);
Thread.sleep(1000);
} catch (InterruptedException e) {
e.printStackTrace();
}
}
}
}
(7)在不同线程同时访问同步方法和非同步方法时,控制台的输出为:
person1 say hello: 0
person1 say Welcome: 0
person1 say hello: 1
person1 say Welcome: 1
person1 say hello: 2
person1 say Welcome: 2
(8)在不同线程同时访问同一对象的同步方法时,控制台的输出为:
person1 say hello: 0
person1 say hello: 1
person1 say hello: 2
person1 say hello: 0
person1 say hello: 1
person1 say hello: 2
(9)在不同线程分别访问同一类不同对象的同步方法时,问题就出现在这里,测试代码为:
final Person person1 = new Person("person1");
final Person person2 = new Person("person2");
Thread thread1 = new Thread(new Runnable() {
@Override
public void run() {
person1.sayHello();
}
});
Thread thread2 = new Thread(new Runnable() {
@Override
public void run() {
person2.sayHello();
}
});
thread1.start();
thread2.start();
控制台的输出结果为:
person1 say hello: 0
person1 say hello: 1
person1 say hello: 2
person1 say hello: 0
person1 say hello: 1
person1 say hello: 2
和测试用例(3和6)出现了差异,为什么不同的线程,访问不同对象的方法都会出现同步的情况??
原因是使用synchronized(class)的方式进行同步的时候,是加了一个类锁。作用范围扩大为类,不再局限为对象了。无论创建多少个对象,都是这个类的实例,所以会存在同步的效果。