​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();
}
}
}
}

谈谈对 java 中 synchronzied 的理解_java

可以看出,注释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​​方法。此时运行结果如下:

谈谈对 java 中 synchronzied 的理解_面试_02

由此可以看出:只有某一个线程执行完之后,才会调用另一个线程。也就是说这两个线程出现的互斥。

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();
}
}
}
}

运行结果如下:

谈谈对 java 中 synchronzied 的理解_多线程_03

注释1和注释2 调用的是不同的实例对象,和demo1一样,不同之处在于注释3 此时添加了 ​​static​​关键字,

此时产生了互斥效果。 因此,两个线程依次执行。

可对你会好奇,如果此时注释3的代码修改为:(去掉synchronzied,添加static)

public static void printLog() { }

效果会怎样呢?

运行结果如下:

谈谈对 java 中 synchronzied 的理解_java_04

结果同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();
}
}
}
}
}

运行结果如下:

谈谈对 java 中 synchronzied 的理解_java_05

(其中: synchronized (lock) 改成 synchronized (this) 运行结果不变。)

上图可以看出任何 Object 对象 都可以当作锁对象。

其他知识点:

Java中实现的同步方法还有一个放在一起区分:ReentrantLock

synchronized的使用较简单,加锁和释放锁都是由虚拟机自动完成的。

而ReentrantLock需要开发者手动去完成,但是使用场景更多,公开锁、读写锁都可以在复杂场景中发挥重要的作用。关于这些的上体的介绍,不在此总结,以后遇到了 看心情再总结一波~ OVer~

4.总结

上面总结了synchronzized的常用用法。

接下来,从面试过程中考查的角度总结一下synchronized的修饰后的几种对象情况。

synchronized修饰的对象有以下几种:


  1. 修饰一个代码块,被修饰的代码块称为同步语句块,其作用的范围是大括号{}括起来的代码,作用的对象是调用这个代码块的对象;
  2. 修饰一个方法,被修饰的方法称为同步方法,其作用的范围是整个方法,作用的对象是调用这个方法的对象;
  3. 修饰一个静态的方法,其作用的范围是整个静态方法,作用的对象是这个类的所有对象;
  4. 修饰一个类,其作用的范围是synchronized后面括号()括起来的部分,作用的对象是这个类的所有对象。

如有不对的地方,欢迎指出,欢迎从不同的角度解读,未完待续~

参考资料:

《Android工程师进阶》姜新星