项目中,我们经常会碰到多线程并发的问题,而这些问题往往会导致数据错误等各种千奇百怪的现象。那么我们有什么办法避免呢?当然有。java给我们提供了synchronized关键字;本文将通过以下几点来展开叙述。
1. synchronized的作用
2.synchronized方法(普通方法和静态方法)
3.synchronized代码块(this、object和class)
synchronized的作用
保证用synchronized修饰的部分,在同一时间只有一个线程能访问,其他的线程必须等待该部分代码执行完毕后,依次进行访问,从而实现线程同步。是不是会有点抽象,没关系,我们通过下面的各种示例来理解。
synchronized方法(普通方法和静态方法)
1. 修饰普通方法
只需在我们的普通方法前面加上synchronized关键字修饰即可,我们直接举个栗子
public synchronized void printA(){
for(int i = 0; i < 5; i++){
try {
Thread.sleep(1000);
System.out.println(Thread.currentThread().getName() + ": i in SynchronizedMethod is " + i);
} catch (InterruptedException e) {
e.printStackTrace();
}
}
}
这就是一个synchronized方法了。下面我们试着开两个线程(thread1和thread2)同时调用上面的printA()方法
/**
* 两个线程并发,同一对象调用同一方法(结果:线程同步)
*/
SynchronizedMethod synchronizedMethod1 = new SynchronizedMethod();
Thread thread1 = new Thread(new Runnable() {
@Override
public void run() {
synchronizedMethod1.printA();
}
}, "thread1");
Thread thread2 = new Thread(new Runnable() {
@Override
public void run() {
synchronizedMethod1.printA();
}
}, "thread2");
thread1.start();
thread2.start();
运行后发现,结果如下:
thread1: i in SynchronizedMethod is 0
thread1: i in SynchronizedMethod is 1
thread1: i in SynchronizedMethod is 2
thread1: i in SynchronizedMethod is 3
thread1: i in SynchronizedMethod is 4
thread2: i in SynchronizedMethod is 0
thread2: i in SynchronizedMethod is 1
thread2: i in SynchronizedMethod is 2
thread2: i in SynchronizedMethod is 3
thread2: i in SynchronizedMethod is 4
至此已经实现了线程同步。但是上面这个代码中,thread1和thread2中调用的都是同一个对象synchronizedMethod1的printA()方法;
那如果再创建一个对象叫synchronizedMethod2,在thread1中调用synchronizedMethod1的printA()方法,在thread2中调用synchronizedMethod2的printA()方法,那么还能实现线程同步吗?
/**
* 两个线程并发,不同对象调用同一个方法(结果:线程不同步)
*/
SynchronizedMethod synchronizedMethod1 = new SynchronizedMethod();
SynchronizedMethod synchronizedMethod2 = new SynchronizedMethod();
Thread thread1 = new Thread(new Runnable() {
@Override
public void run() {
synchronizedMethod1.printA();
}
}, "thread1");
Thread thread2 = new Thread(new Runnable() {
@Override
public void run() {
synchronizedMethod2.printA();
}
}, "thread2");
thread1.start();
thread2.start();
运行结果如下:
thread2: i in SynchronizedMethod is 0
thread1: i in SynchronizedMethod is 0
thread2: i in SynchronizedMethod is 1
thread1: i in SynchronizedMethod is 1
thread2: i in SynchronizedMethod is 2
thread1: i in SynchronizedMethod is 2
thread2: i in SynchronizedMethod is 3
thread1: i in SynchronizedMethod is 3
thread2: i in SynchronizedMethod is 4
thread1: i in SynchronizedMethod is 4
我们发现,如果是两个对象的话,虽然都调用了printA()方法,且printA()方法是synchronized修饰的同步方法,但是并不能实现线程同步。
所以我们总结一下:对于synchronized修饰的普通方法,只有是在同一个对象的情况下,才可以实现线程同步;
那么肯定有小伙伴想问:怎么才让printA()方法在多个不同对象调用的情况下,实现线程同步呢?我们只需要将printA()方法改成静态方法即可,当然这只是一种实现方式,更多的实现方式请继续阅读后面的synchronized代码块。
2. 修饰静态方法
老规矩,废话不多说,直接上栗子
public static synchronized void printA(){
for(int i = 0; i < 5; i++){
try {
Thread.sleep(1000);
System.out.println(Thread.currentThread().getName() + ": i in SynchronizedMethod is " + i);
} catch (InterruptedException e) {
e.printStackTrace();
}
}
}
我们直接把上一个例子中的printA()方法加上static修饰,接着我们依然通过上面第二个例子中的代码运行一下:
SynchronizedMethod synchronizedMethod1 = new SynchronizedMethod();
SynchronizedMethod synchronizedMethod2 = new SynchronizedMethod();
Thread thread1 = new Thread(new Runnable() {
@Override
public void run() {
synchronizedMethod1.printA();
}
}, "thread1");
Thread thread2 = new Thread(new Runnable() {
@Override
public void run() {
synchronizedMethod2.printA();
}
}, "thread2");
thread1.start();
thread2.start();
结果如下:
thread1: i in SynchronizedMethod is 0
thread1: i in SynchronizedMethod is 1
thread1: i in SynchronizedMethod is 2
thread1: i in SynchronizedMethod is 3
thread1: i in SynchronizedMethod is 4
thread2: i in SynchronizedMethod is 0
thread2: i in SynchronizedMethod is 1
thread2: i in SynchronizedMethod is 2
thread2: i in SynchronizedMethod is 3
thread2: i in SynchronizedMethod is 4
哈,现在又变成线程又同步啦。
总结:对于synchronized修饰的普通方法,只有是在同一个对象的情况下,才可以实现线程同步;而synchronized修饰的静态方法,即使是不同的对象,也可以实现线程同步;因为普通方法仅属于当前类的一个对象,而静态方法则属于这个类;
但是有很多时候,我们并不需要整个方法都是同步的,这样会很大程度地降低效率,那么我们是否可以对方法中的一段代码实现线程同步呢?当然是可以的啦,仅需使用synchronized修饰代码块即可。
synchronized代码块(this、object和class)
1.synchronized(this)
用这种方式修饰代码块,是对当前类的对象设置一个同步锁,当不同线程,同一个对象执行到该代码块时,代码块里面的部分才会实现同步,当然代码块外面的部分依然是不同步的。
public void printA(){
System.out.println(Thread.currentThread().getName() + ": printer A is already");
synchronized (this){
for(int i = 0; i < 5; i++){
try {
Thread.sleep(1000);
System.out.println(Thread.currentThread().getName() + ": i in SynchronizedBlock is " + i);
} catch (InterruptedException e) {
e.printStackTrace();
}
}
}
}
同样开起thread1和thread2两个线程执行以下printA()方法。
/**
* 两个线程并发,同一对象调用同一个方法(结果:synchronized代码块内线程同步,外部不同步)
*/
SynchronizedBlock synchronizedBlock1 = new SynchronizedBlock();
Thread thread1 = new Thread(new Runnable() {
@Override
public void run() {
synchronizedBlock1.printA();
}
}, "thread1");
Thread thread2 = new Thread(new Runnable() {
@Override
public void run() {
synchronizedBlock1.printA();
}
}, "thread2");
thread1.start();
thread2.start();
运行结果如下:
thread1: printer A is already
thread2: printer A is already
thread1: i in SynchronizedBlock is 0
thread1: i in SynchronizedBlock is 1
thread1: i in SynchronizedBlock is 2
thread1: i in SynchronizedBlock is 3
thread1: i in SynchronizedBlock is 4
thread2: i in SynchronizedBlock is 0
thread2: i in SynchronizedBlock is 1
thread2: i in SynchronizedBlock is 2
thread2: i in SynchronizedBlock is 3
thread2: i in SynchronizedBlock is 4
与我们预想的一样,synchronized代码块里面的是线程同步的,但是代码块外面的逻辑是线程不同步的。
与之前的synchronized修饰普通方法一样,如果是两个对象,那么还会是线程同步吗?答案是线程不同步,具体大家可以自行去尝试一波,这里不多赘述了。
那synchronized代码块又如何保证多个对象的情况下,也实现线程同步呢?这里就需要用synchronized(Xxx.class),这里Xxx是类名。
2.synchronized(Xxx.class)
在代码块第1小节中的示例的基础上,我们将synchronized(this)改为synchronized(SynchronizedBlock.class)
public void printA(){
System.out.println(Thread.currentThread().getName() + ": printer A is already");
synchronized (SynchronizedBlock.class){
for(int i = 0; i < 5; i++){
try {
Thread.sleep(1000);
System.out.println(Thread.currentThread().getName() + ": i in SynchronizedBlock is " + i);
} catch (InterruptedException e) {
e.printStackTrace();
}
}
}
}
然后我们在执行的时候,先创建两个对象,再在thread1和thread2中分别用这两个不同的对象调用printA()方法
/**
* 两个线程并发,不同对象调用同一个方法(结果:synchronized代码块内线程同步,外部不同步)
*/
SynchronizedBlock synchronizedBlock1 = new SynchronizedBlock();
SynchronizedBlock synchronizedBlock2 = new SynchronizedBlock();
Thread thread1 = new Thread(new Runnable() {
@Override
public void run() {
synchronizedBlock1.printA();
}
}, "thread1");
Thread thread2 = new Thread(new Runnable() {
@Override
public void run() {
synchronizedBlock2.printA();
}
}, "thread2");
thread1.start();
thread2.start();
执行结果如下,代码块内是可以实现线程同步的。
thread1: printer A is already
thread2: printer A is already
thread1: i in SynchronizedBlock is 0
thread1: i in SynchronizedBlock is 1
thread1: i in SynchronizedBlock is 2
thread1: i in SynchronizedBlock is 3
thread1: i in SynchronizedBlock is 4
thread2: i in SynchronizedBlock is 0
thread2: i in SynchronizedBlock is 1
thread2: i in SynchronizedBlock is 2
thread2: i in SynchronizedBlock is 3
thread2: i in SynchronizedBlock is 4
3.synchronized(object)
我们再来研究一下synchronized(object),这种方式其实是给object添加了一个同步锁,所以能不能实现线程同步,需要看object是否是同一个对象。
示例1:
使用synchronized(object)修饰代码块
public class SynchronizedBlock {
private Object mObject;
public SynchronizedBlock(){}
public SynchronizedBlock(Object object){
this.mObject = object;
}
public void printA(){
System.out.println(Thread.currentThread().getName() + ": printer A is already");
synchronized (mObject){
for(int i = 0; i < 5; i++){
try {
Thread.sleep(1000);
System.out.println(Thread.currentThread().getName() + ": i in SynchronizedBlock is " + i);
} catch (InterruptedException e) {
e.printStackTrace();
}
}
}
}
}
下面是执行代码,但是在执行之前,我们先创建两个对象,给这两个对象初始化时传入同一个object对象,再开两个线程分别让每一个对象分别执行printA()方法。
Object object = new Object();
SynchronizedBlock synchronizedBlock1 = new SynchronizedBlock(object);
SynchronizedBlock synchronizedBlock2 = new SynchronizedBlock(object);
Thread thread1 = new Thread(new Runnable() {
@Override
public void run() {
synchronizedBlock1.printA();
}
}, "thread1");
Thread thread2 = new Thread(new Runnable() {
@Override
public void run() {
synchronizedBlock2.printA();
}
}, "thread2");
thread1.start();
thread2.start();
执行结果如下,可以实现线程同步。
thread1: printer A is already
thread2: printer A is already
thread1: i in SynchronizedBlock is 0
thread1: i in SynchronizedBlock is 1
thread1: i in SynchronizedBlock is 2
thread1: i in SynchronizedBlock is 3
thread1: i in SynchronizedBlock is 4
thread2: i in SynchronizedBlock is 0
thread2: i in SynchronizedBlock is 1
thread2: i in SynchronizedBlock is 2
thread2: i in SynchronizedBlock is 3
thread2: i in SynchronizedBlock is 4
示例2:
我们对示例1中的SynchronizedBlock方法做如下修改:
public class SynchronizedBlock {
private Object mObject = new Object();
public SynchronizedBlock(){}
public void printA(){
System.out.println(Thread.currentThread().getName() + ": printer A is already");
synchronized (mObject){
for(int i = 0; i < 5; i++){
try {
Thread.sleep(1000);
System.out.println(Thread.currentThread().getName() + ": i in SynchronizedBlock is " + i);
} catch (InterruptedException e) {
e.printStackTrace();
}
}
}
}
}
下面是执行代码,直接创建两个对象,再开两个线程分别让每一个对象分别执行printA()方法。
SynchronizedBlock synchronizedBlock1 = new SynchronizedBlock();
SynchronizedBlock synchronizedBlock2 = new SynchronizedBlock();
Thread thread1 = new Thread(new Runnable() {
@Override
public void run() {
synchronizedBlock1.printA();
}
}, "thread1");
Thread thread2 = new Thread(new Runnable() {
@Override
public void run() {
synchronizedBlock2.printA();
}
}, "thread2");
thread1.start();
thread2.start();
结果当然是不能实现线程同步。我们看一下打印的结果,验证一下。
thread1: printer A is already
thread2: printer A is already
thread2: i in SynchronizedBlock is 0
thread1: i in SynchronizedBlock is 0
thread2: i in SynchronizedBlock is 1
thread1: i in SynchronizedBlock is 1
thread2: i in SynchronizedBlock is 2
thread1: i in SynchronizedBlock is 2
thread1: i in SynchronizedBlock is 3
thread2: i in SynchronizedBlock is 3
thread2: i in SynchronizedBlock is 4
thread1: i in SynchronizedBlock is 4
果然,打印的结果的确是无法做到线程同步的。
总结:synchronized(this)修饰代码块,代码块内的代码仅针对同一个对象实现线程同步;
synchronized(Xxx.class)修饰代码块,只要是同一个类,不管有多少个对象,代码块内的代码也同样实现线程同步;
synchronized(object)修饰代码块,这里代码块内的代码能不能实现线程同步,取决于object是否是同一个对象。如果是则同步;如果不是,则不同步;
至此,今天关于synchronized的知识点就到这里了,如果大家看完之后还有什么疑惑或者有什么建议的,可以相互交流^.^