synchronized
在平时开发中和面试中常常会用到,深入了解并总结一下对synchronized
的认识是有必要的,不同时期结合不同的运用场景的运用,往往会有不同角度的认识。本文总结了synchronized
的三个常用经典用法。
文章目录
synchronize
英文单词翻译过来为:同步。
synchronized
是Java中的关键字,是一种同步锁。
synchronized
可以修饰三个层面:
- 修饰实例方法
- 修饰静态方法
- 修饰错码块
1.synchronized 修饰实例方法
public class SynDemo{
private int sum = 0;
public synchronized void add(){
sum = sum + 1;
}
}
当前情况下的锁对象是当前实例对象,因此只有同一个实例对象调用此方法时才会产生互斥效果,不同实例不会产生互斥效果。
什么是互斥:只有某一个线程中的代码执行完成后,才会执行另一个线程中的代码。也就是说这两个线程是互斥的。
(锁可以理解为使需要互斥执行的代码块被一个标识保护,当一个线程执行此代码块时,其他线程只能等待)
实现Demo1如下:
public class SynDemo {
public static void main(String[] args) {
SynDemo demo1 = new SynDemo();
SynDemo demo2 = new SynDemo();
Thread thread1 = new Thread(new Runnable() {
@Override
public void run() {
demo1.printLog(); // 1
}
});
Thread thread2 = new Thread(new Runnable() {
@Override
public void run() {
demo2.printLog();// 2
}
});
thread1.start();
thread2.start();
}
public void printLog() {
for (int i = 0; i < 5; i++) {
try {
System.out.println(Thread.currentThread().getName() + " is printing " + i);
Thread.sleep(300);
} catch (InterruptedException e) {
e.printStackTrace();
}
}
}
}
可以看出,注释1和注释2为不同对象的方法执行,在不同的线程中调用的是不同对象的printLog
方法,因此彼此之间是不会排斥的,两个线程之间依次交替执行。没有出现一个线程执行完成后才执行的情况,也不存在线程阻塞等待的情况。
如果此时将printLog
中的方法改成 :
public synchronized void printLog(){
}
虽然此时添加了synchronized
,但是运行结果仍然不变。因为此时还是运行在不同的对象中,不会出现互斥。
如果我们此时将两个线程调用同一个对象的printLog
方法:
实现Demo2如下:
public class SynDemo {
public static void main(String[] args) {
SynDemo demo1 = new SynDemo();
Thread thread1 = new Thread(new Runnable() {
@Override
public void run() {
demo1.printLog(); //1
}
});
Thread thread2 = new Thread(new Runnable() {
@Override
public void run() {
demo1.printLog(); //2
}
});
thread1.start();
thread2.start();
}
public synchronized void printLog() {
for (int i = 0; i < 5; i++) {
try {
System.out.println(Thread.currentThread().getName() + " is printing " + i);
Thread.sleep(300);
} catch (InterruptedException e) {
e.printStackTrace();
}
}
}
}
注释1和注释2为同一对象调用printLog
方法。此时运行结果如下:
由此可以看出:只有某一个线程执行完之后,才会调用另一个线程。也就是说这两个线程出现的互斥。
2.synchronized 修饰静态方法
如果synchronized 修饰的是静待方法,则锁对象是当前的 Class 对象。因此即使在不同线程中调用不同实例对象,也会有互斥效果。
实现Demo3如下:
public class SynDemo {
public static void main(String[] args) {
SynDemo demo1 = new SynDemo();
SynDemo demo2 = new SynDemo();
Thread thread1 = new Thread(new Runnable() {
@Override
public void run() {
demo1.printLog(); //1
}
});
Thread thread2 = new Thread(new Runnable() {
@Override
public void run() {
demo2.printLog(); //2
}
});
thread1.start();
thread2.start();
}
public static synchronized void printLog() { //3
for (int i = 0; i < 5; i++) {
try {
System.out.println(Thread.currentThread().getName() + " is printing " + i);
Thread.sleep(300);
} catch (InterruptedException e) {
e.printStackTrace();
}
}
}
}
运行结果如下:
注释1和注释2 调用的是不同的实例对象,和demo1一样,不同之处在于注释3 此时添加了 static
关键字,
此时产生了互斥效果。 因此,两个线程依次执行。
可对你会好奇,如果此时注释3的代码修改为:(去掉synchronzied,添加static)
public static void printLog() { }
效果会怎样呢?
运行结果如下:
结果同demo1。
3.synchronized 修饰代码块
当synchronized作用于代码块时,锁对象就是跟在后面的括号中的对象。
public class SynDemo4 {
Object lock = new Object();
public static void main(String[] args) {
SynDemo4 demo1 = new SynDemo4();
Thread thread1 = new Thread(new Runnable() {
@Override
public void run() {
demo1.printLog(); //1
}
});
Thread thread2 = new Thread(new Runnable() {
@Override
public void run() {
demo1.printLog(); //2
}
});
thread1.start();
thread2.start();
}
public void printLog() {
synchronized (lock) {
for (int i = 0; i < 5; i++) {
try {
System.out.println(Thread.currentThread().getName() + " is printing " + i);
Thread.sleep(300);
} catch (InterruptedException e) {
e.printStackTrace();
}
}
}
}
}
运行结果如下:
(其中: synchronized (lock) 改成 synchronized (this) 运行结果不变。)
上图可以看出任何 Object 对象 都可以当作锁对象。
其他知识点:
Java中实现的同步方法还有一个放在一起区分:ReentrantLock
synchronized的使用较简单,加锁和释放锁都是由虚拟机自动完成的。
而ReentrantLock需要开发者手动去完成,但是使用场景更多,公开锁、读写锁都可以在复杂场景中发挥重要的作用。关于这些的上体的介绍,不在此总结,以后遇到了 看心情再总结一波~ OVer~
4.总结
上面总结了synchronzized的常用用法。
接下来,从面试过程中考查的角度总结一下synchronized的修饰后的几种对象情况。
synchronized修饰的对象有以下几种:
- 修饰一个代码块,被修饰的代码块称为同步语句块,其作用的范围是大括号{}括起来的代码,作用的对象是调用这个代码块的对象;
- 修饰一个方法,被修饰的方法称为同步方法,其作用的范围是整个方法,作用的对象是调用这个方法的对象;
- 修饰一个静态的方法,其作用的范围是整个静态方法,作用的对象是这个类的所有对象;
- 修饰一个类,其作用的范围是synchronized后面括号()括起来的部分,作用的对象是这个类的所有对象。
如有不对的地方,欢迎指出,欢迎从不同的角度解读,未完待续~
参考资料:
《Android工程师进阶》姜新星