目录:
- synchronize作用
- synchronize使用方式
- synchronize导致的死锁
- synchronize特性
- synchronize原理
synchronize作用
在并发场景下限制共享资源的访问,使其只有一个线程可以执行某个方法或代码块,实现线程安全。
synchronize使用方式
synchronize导致的死锁
Thread1:lock A,waiting for B。
Thread2:lock B,waiting for A。
public class DeadLock { /** * A锁 */ private static String A = "A"; /** * B锁 */ private static String B = "B"; public static void main(String[] args) { new DeadLock().deadLock(); } public void deadLock() { // 先获取A锁再获取B锁 Thread t1 = new Thread(() -> { synchronized (A) { try { // 获取A锁后休眠2s Thread.sleep(2000); } catch (InterruptedException e) { e.printStackTrace(); } synchronized (B) { // 获取B锁 System.out.println("thread1..."); } } }); // 先获取B锁再获取A锁 Thread t2 = new Thread(() -> { synchronized (B) { try { // 获取B锁后休眠2s Thread.sleep(2000); } catch (InterruptedException e) { e.printStackTrace(); } synchronized (A) { System.out.println("thread2..."); } } }); t1.start(); t2.start(); } }
synchronize特性
1、原子性:线程互斥的访问同步代码。
2、可见性:保证共享变量的修改能及时可见。
- 对一个变量unlock时,必须要将其同步到主内存中。
- 对一个变量lock时,会清空工作内存中此变量的值。
- 在执行引擎使用此变量前,需要重新从主内存中执行load或assign操作,以初始化变量值。
3、有序性:有效解决重排序问题,即一个unlock操作先行发生与lock操作。
synchronize可以把任何非null的对象作为锁,在Host JVM中锁叫做对象监视器。
synchronize原理
当一个线程访问同步代码块的时候,首先需要获取锁才能执行,当退出或抛出异常时需要释放锁。
那么它是如何来实现这一机制的呢,来看看如下代码:
public class SynchronizedDemo { public void method() { synchronized (this) { System.out.println("Method 1 start"); } } }
monitorenter:监视器锁(monitor),当monitor被占用时便是锁定状态,执行monitorenter会尝试获取monitor的所有权。过程如下:
- monitor进入数为0,则该线程进入monitor,进入数为1,则该线程为monitor的所有者。
- 若该线程已拥有monitor,只是重新进入的话,monitor加1。
- 如果其它线程占用monitor,则该线程阻塞,直到进入数为0,该线程重新尝试获取monitor的所有权。
monitorexit:执行monitorexit的必须是monitor的拥有者。
- 执行此操作,monitor的进入数减1,若减1后进入数为0,则线程退出monitor。
———————————————————————————————————————————————————————
通过上述的示例便可以很清楚的了解到synchronize是如何实现的了,通过monitor对象来实现的。
其实wait、notify方法也依赖于monitor对象,这也就是为什么wait、notify方法必须要在synchronize中才能调用的原因了。