java synchronized修饰static方法与非static方法的区别
如果使用synchronized修饰static方法,会发生什么?
一、问题的解答
先回顾一下上篇文章:
http://www.xie4ever.com/2017/10/26/java-%E6%9B%B4%E6%AD%A3%E5%AF%B9synchronized%E7%9A%84%E4%B8%80%E4%B8%AA%E9%94%99%E8%AF%AF%E8%AE%A4%E8%AF%86/
在上篇文章中,我们得到了一个结论:
多个线程访问同一个类的synchronized方法时, 都是串行执行的(就算有多个cpu也不例外)!synchronized方法使用了类的内置锁, 锁住的是方法所属对象本身。
现在我改造一下上篇文章的第一个例子,使用synchronized修饰static方法,看看会发生什么。
Test.java
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 |
package com.test.testSynchronized; public class Test { // method1换成了static方法 public static synchronized void method1() { long start = System.currentTimeMillis(); System.out.println("method1 get the lock"); try { Thread.sleep(5000); } catch (InterruptedException e) { // TODO Auto-generated catch block e.printStackTrace(); } System.out.println("method1 release the lock"); System.out.println("method1 costs " + (System.currentTimeMillis() - start)); } public synchronized void method2() { long start = System.currentTimeMillis(); System.out.println("method2 get the lock"); System.out.println("method2 release the lock"); System.out.println("method2 costs " + (System.currentTimeMillis() - start)); } public static void main(String[] args) throws InterruptedException { Test test = new Test(); Thread1 thread1 = new Thread1(test); Thread2 thread2 = new Thread2(test); thread1.start(); // 先执行, 以便抢占锁 Thread.sleep(500); // 放弃cpu, 让thread1执行, 以便获的锁 thread2.start(); } } class Thread1 extends Thread { Test test; public Thread1(Test test) { this.test = test; } @Override public void run() { test.method1(); } } class Thread2 extends Thread { Test test; public Thread2(Test test) { this.test = test; } @Override public void run() { test.method2(); } } |
只改造了一下method1,改成了static方法。运行结果为:
1 2 3 4 5 6 |
method1 get the lock method2 get the lock method2 release the lock method2 costs 0 method1 release the lock method1 costs 5000 |
为什么可以实现并发了?难道static导致同步方法失效了吗?我们把method2也改成static方法,看看会发生什么。
1 2 3 4 5 6 7 8 9 |
public static synchronized void method2() { long start = System.currentTimeMillis(); System.out.println("method2 get the lock"); System.out.println("method2 release the lock"); System.out.println("method2 costs " + (System.currentTimeMillis() - start)); } |
运行结果为:
1 2 3 4 5 6 |
method1 get the lock method1 release the lock method1 costs 5000 method2 get the lock method2 release the lock method2 costs 1 |
两个方法又不能并发了…
现在我们现在可以确定,synchronized修饰的static方法彼此之间能够实现同步,但是static方法和非static方法之间不能实现同步。如果仍然使用“test对象的对象锁”来解释这种现象,肯定是解释不通的。
个人认为:synchronized修饰的static方法和非static方法分别使用了两种不同的锁,所以才没有实现同步。
这里借用别人总结的结论:
当synchronized修饰一个static方法时,获取的是类锁(即Class本身,注意:不是实例)
当synchronized修饰一个非static方法时,获取的是对象锁(即类的实例对象)
如果Thread1要访问static synchronized method1,就要获取Test类的类锁(method1所属的类),一但Thread1获得了类锁,在主动释放之前,(竞争类锁的)别的线程都需要等待。
假设现在Thread2想要访问static synchronized method2,就要先获得Test类的的类锁。因为当前类锁被Thread1占用了,所以Thread2只能进入Test类的锁池内,等待锁的释放。
另一种情况:假设现在Thread2想要访问synchronized method2,就要先获得test对象的对象锁。因为当前test的对象锁没有被任何线程占用,所以Thread2获取对象锁后,马上就可以执行synchronized method2,体现为Thread1、Thread2并发。
二、如何使static方法和非static方法同步?
既然static方法和非static方法竞争的是不同的锁,那么在同一个类中,如何使这两种方法同步?
用synchronized应该是做不到的,我们可以尝试使用Lock。
Test.java
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 80 81 82 83 84 85 86 87 88 89 90 91 92 93 94 95 96 97 |
package com.test.testSynchronized; import java.util.concurrent.locks.ReentrantLock; public class Test { ReentrantLock lock = new ReentrantLock(); public static void method1(ReentrantLock lock) { try { lock.lock(); long start = System.currentTimeMillis(); System.out.println("method1 get the lock"); try { Thread.sleep(5000); } catch (InterruptedException e) { // TODO Auto-generated catch block e.printStackTrace(); } System.out.println("method1 release the lock"); System.out.println("method1 costs " + (System.currentTimeMillis() - start)); } finally { // TODO: handle finally clause lock.unlock(); } } public void method2(ReentrantLock lock) { try { lock.lock(); long start = System.currentTimeMillis(); System.out.println("method2 get the lock"); System.out.println("method2 release the lock"); System.out.println("method2 costs " + (System.currentTimeMillis() - start)); } finally { // TODO: handle finally clause lock.unlock(); } } public static void main(String[] args) throws InterruptedException { Test test = new Test(); Thread1 thread1 = new Thread1(test); Thread2 thread2 = new Thread2(test); thread1.start(); // 先执行, 以便抢占锁 Thread.sleep(500); // 放弃cpu, 让thread1执行, 以便获的锁 thread2.start(); } } class Thread1 extends Thread { Test test; public Thread1(Test test) { this.test = test; } @Override public void run() { test.method1(test.lock); } } class Thread2 extends Thread { Test test; public Thread2(Test test) { this.test = test; } @Override public void run() { test.method2(test.lock); } } |
运行结果为:
1 2 3 4 5 6 |
method1 get the lock method1 release the lock method1 costs 5001 method2 get the lock method2 release the lock method2 costs 0 |
三、总结
个人认为本文最重要的一点是“类锁”的概念。
我在知乎上看了一些关于“synchronized原理”文章,上面提到“类锁”、“对象锁”只是概念,并不是真实存在的。想要理解synchronized的实现机制,还要继续深入下去。