谈谈对 java 中 synchronzied 的理解
Posted 小羊子说
tags:
篇首语:本文由小常识网(cha138.com)小编为大家整理,主要介绍了谈谈对 java 中 synchronzied 的理解相关的知识,希望对你有一定的参考价值。
synchronized
在平时开发中和面试中常常会用到,深入了解并总结一下对synchronized
的认识是有必要的,不同时期结合不同的运用场景的运用,往往会有不同角度的认识。本文总结了synchronized
的三个常用经典用法。
文章目录
synchronize
英文单词翻译过来为:同步。
synchronized
是Java中的关键字,是一种同步锁。
synchronized
可以修饰三个层面:
- 修饰实例方法
- 修饰静态方法
- 修饰错码块
0. 为什么要线程同步?
当多个线程同时读写同一份共享资源的时候,可能会引起冲突。这时候,我们需要引入线程“同步”机制,即各个线程之间要有先来后到,不能一窝蜂同时挤上去抢作一团。
线程同步其实就是相对于现实中的“排队”,几个线程之间要排队,一个一个对共享资源进行操作,而不是同时进行操作。
线程同步是为了防止多个线程同时访问同一个数据对象时,对数据造成破坏。
线程的同步是保证多线程安全访问资源的一种手段。
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
方法。此时运行结果如下:
由此可以看出:只有某一个线程执行完之后,才会调用另一个线程。也就是说这两个线程出现的互斥。
2022.3.18 新增使用心得:
根据上述的测试可以得出结论:如果是实例化不同的对象后,在不同的实例化对象中操作,不存在线程同步的问题,都是各做各的。如果是不同的线程操作同一个对象时,才会出现线程同步的问题。比如为什么要用单例模式等等,这里需要结合线程同步的使用场景多体会总结。
回到最开始的结论:
线程同步是为了防止多个线程同时访问同一个数据对象时,对数据造成破坏。 注意这里的“同一对象”。
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工程师进阶》姜新星
以上是关于谈谈对 java 中 synchronzied 的理解的主要内容,如果未能解决你的问题,请参考以下文章