前面几章都是在说synchronized用于对象锁,无论是修饰方法也好修饰代码块也好,然而关键字synchronized还可以应用到static静态方法上,如果这样写,那就是对当前的*.java文件所对应的Class类进行加锁。那么接下来就来说说synchronized修饰static的最基本用法和最显而易见的特点。我写了一个小例子,代码如下:
public static void main(String[] args) { ThreadA a = new ThreadA(); a.setName("A"); a.start(); ThreadB b = new ThreadB(); b.setName("B"); b.start(); } public static class ThreadB extends Thread { private Servic1 service; @Override public void run() { // TODO Auto-generated method stub super.run(); service.printB(); } } public static class ThreadA extends Thread { private Servic1 service; @Override public void run() { // TODO Auto-generated method stub super.run(); service.printA(); } } } class Servic1 { synchronized public static void printA() { try { System.out.println("线程" + Thread.currentThread().getName() + "在" + System.currentTimeMillis() + "进入printA"); Thread.sleep(3000); System.out.println("线程" + Thread.currentThread().getName() + "在" + System.currentTimeMillis() + "离开printA"); } catch (InterruptedException e) { e.printStackTrace(); } } synchronized public static void printB() { System.out.println("线程" + Thread.currentThread().getName() + "在" + System.currentTimeMillis() + "进入printB"); System.out.println("线程" + Thread.currentThread().getName() + "在" + System.currentTimeMillis() + "离开printB"); }
}
运行结果如下:从结果来看,和synchronized修饰普通方法时候的执行效果没什么不同,其实本质是不一样的,synchronized修饰static方法是给改方法所在的Class类加锁,而synchronized修饰普通方法是当前方法所在的类的对象加锁。单单用一句话来描述他们的本质对有些人来说也不会有太深的体会,那么我会写几个不同场景的例子,让大家体会一下用在代码里到底有什么不一样的地方。
一开始学习多线程对象锁和类锁的时候,我自以为类比对象的范围大,所以认为用类锁既可以对类锁起作用又可以对这个类所对应的对象锁起作用,后来写了一个例子,我发现这个想法完全不对,下面我就附上这个例子,如下:
public static void main(String[] args) { Service2 service = new Service2(); ThreadA a = new ThreadA(service); a.setName("A"); a.start(); ThreadB b = new ThreadB(service); b.setName("B"); b.start(); ThreadC c = new ThreadC(service); c.setName("C"); c.start(); } public static class ThreadA extends Thread { private Service2 service; public ThreadA(Service2 service) { super(); this.service = service; } @Override public void run() { // TODO Auto-generated method stub super.run(); service.printA(); } } public static class ThreadB extends Thread { private Service2 service; public ThreadB(Service2 service) { super(); this.service = service; } @Override public void run() { // TODO Auto-generated method stub super.run(); service.printB(); } } public static class ThreadC extends Thread { private Service2 service; public ThreadC(Service2 service) { super(); this.service = service; } @Override public void run() { // TODO Auto-generated method stub super.run(); service.printC(); } } } class Service2 { synchronized public static void printA() { try { System.out.println("线程:" + Thread.currentThread().getName() + "在" + System.currentTimeMillis() + "进入printA"); Thread.sleep(3000); System.out.println("线程:" + Thread.currentThread().getName() + "在" + System.currentTimeMillis() + "离开printA"); } catch (InterruptedException e) { // TODO Auto-generated catch block e.printStackTrace(); } } synchronized public static void printB() { System.out.println("线程:" + Thread.currentThread().getName() + "在" + System.currentTimeMillis() + "进入printB"); System.out.println("线程:" + Thread.currentThread().getName() + "在" + System.currentTimeMillis() + "离开printB"); } synchronized public void printC() { System.out.println("线程:" + Thread.currentThread().getName() + "在" + System.currentTimeMillis() + "进入printC"); System.out.println("线程:" + Thread.currentThread().getName() + "在" + System.currentTimeMillis() + "离开printC"); } }
运行结果如下:代码中在printA()方法中有一段3000毫秒的休眠,在休眠期间,线程C执行了printC()方法,说明这两个方法的锁并不是同一把锁,所以线程A和线程C之间是异步的,也说明了一个问题,类锁虽然比对象锁的范围大,但是不是一把锁也不会起作用,再看结果,线程A和线程B一定永远都是同步的,表现在,一定是某个线程执行完释放掉这把类锁另一个线程才能得到执行的机会,所以printA()和printC()用的是同一把锁。
写到这里,第一个例子我说了类锁的用法,第二例子我证明了类锁和对象锁并是一把锁,相互不会干扰,那么下面我要说一下,“类锁”的“锁”的范围到底是多大,到底哪些锁用的是同一把“类锁”。下面我附上一段代码,如下:
public static void main(String[] args) { Service3 s1 = new Service3(); Service3 s2 = new Service3(); ThreadA a = new ThreadA(s1); ThreadB b = new ThreadB(s2); a.setName("A"); b.setName("B"); a.start(); b.start(); } public static class ThreadA extends Thread { private Service3 service; public ThreadA(Service3 service) { super(); this.service = service; } @Override public void run() { // TODO Auto-generated method stub super.run(); service.printA(); } } public static class ThreadB extends Thread { private Service3 service; public ThreadB(Service3 service) { super(); this.service = service; } @Override public void run() { // TODO Auto-generated method stub super.run(); service.printB(); } } } class Service3 { synchronized static public void printA() { try { System.out.println("线程:" + Thread.currentThread().getName() + "在" + System.currentTimeMillis() + "进入printA"); Thread.sleep(3000); System.out.println("线程:" + Thread.currentThread().getName() + "在" + System.currentTimeMillis() + "离开printA"); } catch (InterruptedException e) { // TODO Auto-generated catch block e.printStackTrace(); } } synchronized static public void printB() { System.out.println("线程:" + Thread.currentThread().getName() + "在" + System.currentTimeMillis() + "进入printB"); try { Thread.sleep(3000); } catch (InterruptedException e) { // TODO Auto-generated catch block e.printStackTrace(); } System.out.println("线程:" + Thread.currentThread().getName() + "在" + System.currentTimeMillis() + "离开printB"); } }
运行结果如下:在前几篇讲对象锁的时候,我写过一个和这个基本一样的例子,用语言描述就是有多个线程同时调用同一个类的不同对象的不同的synchronized修饰的普通方法,但是执行结果确实异步的,原因就是虽然是同一个类,但是确实不同的对象,所以这些锁之间相互之间不会影响,如果大家不明白要对比的话,可以去上http://www.cnblogs.com/chentong/p/5660801.html这里找最后一个例子。但是现在的这个例子,也是有多个线程调用同一个类的不同对象的不同synchronized修饰的static方法,执行结果确实同步的,说明这些方法的锁是同一把锁。那么也就知道了Class锁的有效范围了,Class锁可以对该类(Service3)的所以对象实例(s1、s2)起作用,前提是该类里的方法(printA、printB)用的Class锁。如果有的方法用的对象锁,也不会起作用,上一个例子已经证明了。
用关键字synchronized修饰static方法实现“Class锁”,只是其中的一种写法,还有一种实现“Class锁”的方式,下篇我会详细的介绍。