2.1.4 多个对象多个锁
再来看一个实验,创建项目twoObjectTwoLock,创建HasSelfPrivateNum.java类,代码如下:
package service; public class HasSelfPrivateNum { synchronized public void testMethod() { try { System.out.println(Thread.currentThread().getName() + " begin " + System.currentTimeMillis()); Thread.sleep(3000); System.out.println(Thread.currentThread().getName() + " end " + System.currentTimeMillis()); } catch (InterruptedException e) { // TODO Auto-generated catch block e.printStackTrace(); } } }
上面的代码中有同步方法testMethod,说明此方法在正常情况下应该被顺序调用。
创建线程ThreadA.java和ThreadB.java代码,如图2-4所示。
图2-4 两个线程类代码
类Run.java代码如下:
package test; import service.HasSelfPrivateNum; import extthread.ThreadA; import extthread.ThreadB; public class Run { public static void main(String[] args) { HasSelfPrivateNum numRef1 = new HasSelfPrivateNum(); HasSelfPrivateNum numRef2 = new HasSelfPrivateNum(); ThreadA athread = new ThreadA(numRef1); athread.start(); ThreadB bthread = new ThreadB(numRef2); bthread.start(); } }
图2-5 无同步各有各锁
创建了两个HasSelfPrivateNum.java类的对象,即产生两把锁,程序运行结果如图2-5所示。
上面示例是两个线程分别访问同一个类的两个不同实例的相同名称的同步方法,控制台中输出两个begin和end,且不是begin-end成对的输出,呈现了两个线程交叉输出的效果,说明两个线程以异步方式同时运行。
本示例创建了两个业务对象,在系统中产生了两个锁,线程和业务对象属于一对一的关系,每个线程执行自己所属业务对象中的同步方法,不存在锁的争抢关系,所以运行结果是异步的。另外,在这种情况下synchronized可以不需要,因为不会出现非线程安全问题。
只有多个线程执行同一个业务对象中的同步方法时,线程和业务对象属于多对一的关系,为了避免出现非线程安全问题,所以使用了synchronized。
从上面程序运行结果来看,虽然在HasSelfPrivateNum.java中使用了synchronized关键字,但打印的顺序不是同步的,是交叉的,为什么是这样的结果呢?关键字synchronized取得的锁都是对象锁,而不是把一段代码或方法当作锁,所以在上面的示例中,哪个线程先执行带synchronized关键字的方法,哪个线程就持有该方法所属对象作为锁,其他线程只能等待,前提是多个线程访问的是同一个对象。但如果多个线程访问多个对象,也就是每个线程访问自己所属的业务对象(上面的示例就是此种情况),则JVM会创建多个锁,不存在锁争抢的情况。更具体来讲,由于本示例创建的是两个业务对象,所以产生两份实例变量,每个线程访问自己的实例变量,所以加不加synchronized关键字都是线程安全的。
总结:多个线程对共享的资源有写操作,则必须同步,如果只是读取操作,则不需要同步。