一:修饰代码块
二:修饰方法
修饰静态方法
修饰非静态方法
修饰方法的继承
三:wait和notify的使用
用synchronized修饰代码块或方法即可完成同步,synchronized的使用方法有以下几种。
修饰代码块时需要明确指定锁对象,锁住的时所有以该锁对象为锁的代码块
public void method1() { synchronized (lockObj) { // action1 } } public void method2() { synchronized (lockObj) { // action2 } }
如以上代码,无论method1和method2是否属于同一个类,method1和method2内的同步代码块都使用lockObj作为锁对象,那么他们就使用的是同一个锁,action1和action2也就保证了同步。lockObj可以是任何不为NULL的实例。
synchronized修饰方法只需要在方法前加synchronized关键字修饰,不过修饰静态方法和非静态方法加锁的范围却不一样。
public class SyncDemo { public synchronized static void method1() { // action1 } public synchronized static void method2() { // action2 } }
如以上代码,分别修饰了静态方法method1和method2。synchronized作用于静态方法时,锁住的是Class实例,又因为Class实例存储在方法区内全局共享,因此静态方法锁相当于一个全局锁,会锁住SyncDemo内的所有静态同步方法。
public class SyncDemo { public synchronized void method1() { // action1 } public synchronized void method2() { // action2 } }
如以上代码,分别修饰了非静态方法method1和method2。synchronized作用于非静态方法时,锁住的是对象实例。如SyncDemo s1 = new SyncDemo();SyncDemo s2 = new SyncDemo();此时s1.method1()与s1.method2()同步,但是s1.method1()与s2.method1()不同步。
synchronized能继承吗?答案是如果写了目标方法,那么目标方法按重写的来,如果没有重写则按父类的方法来。接下来就只需要记住synchronized修饰静态/非静态方法的到底锁的是什么就能很好的明白存在继承关系的场景下两个方法是否是同步的了。
public class Pa { public synchronized void method1() { // action1 } public static synchronized void method2() { // action2 } } public class Cl extends Pa { 如果Cl不重写任何方法,那么method2锁的是Pa.class,method1锁的是实例对象 如果Cl重写两个方法,那么method2锁的是Cl.class,method1锁的依然是实例对象 如果Cl重写去掉了synchronized 关键字,那么两个方法就都不同步了 }
wait()方法可以让持有锁的线程暂时放弃锁并阻塞,等待别人唤醒后才重新竞争锁并从阻塞处继续执行,如果没有其它线程唤醒会一直阻塞下去。唤醒的方法就是使用notify(),notify()方法唤醒一个阻塞的线程,notifyAll()则唤醒所有阻塞的线程。不过wait(),notify()和notifyAll()只能在持有锁的情况下也就是在同步代码内使用,否则会抛出IllegalMonitorStateException。至于原因则涉及到synchronized的实现原理了,感兴趣的话可以看一下这一篇博文。
public class SimSynchronized { public static void main(String[] args) { Object lock = new Object(); new Thread(() -> { synchronized (lock) { System.out.println("-------------------A线程获得锁---------------------"); System.out.println("-------------------A线程执行同步代码块1---------------------"); try { System.out.println("-------------------A线程等待---------------------"); lock.wait(); // 阻塞等待 } catch (InterruptedException e) { e.printStackTrace(); } System.out.println("-------------------A线程执行同步代码块2---------------------"); System.out.println("-------------------A线程唤醒其它线程---------------------"); lock.notify(); // 唤醒 } }).start(); try { // 睡眠以下保证A线程先获得锁 Thread.sleep(100); } catch (InterruptedException e1) { // TODO Auto-generated catch block e1.printStackTrace(); } new Thread(() -> { synchronized (lock) { System.out.println("-------------------B线程获得锁---------------------"); System.out.println("-------------------B线程执行同步代码块1---------------------"); System.out.println("-------------------B线程唤醒其它线程---------------------"); lock.notify(); // 唤醒 try { System.out.println("-------------------B线程等待---------------------"); lock.wait(); 阻塞等待 } catch (InterruptedException e) { e.printStackTrace(); } System.out.println("-------------------B线程执行同步代码块2---------------------"); } }).start(); } }
运行结果:
-------------------A线程获得锁--------------------- -------------------A线程执行同步代码块1--------------------- -------------------A线程等待--------------------- -------------------B线程获得锁--------------------- -------------------B线程执行同步代码块1--------------------- -------------------B线程唤醒其它线程--------------------- -------------------B线程等待--------------------- -------------------A线程执行同步代码块2--------------------- -------------------A线程唤醒其它线程--------------------- -------------------B线程执行同步代码块2---------------------
PS:
【JAVA核心知识】系列导航 [持续更新中…]
上篇导航:19:JAVA中的各种锁
下篇预告:20:synchronized实现原理与锁膨胀:无锁or偏向锁-轻量级锁-重量级锁,看完就懂
欢迎关注…