在Java多线程中,可以使用synchronized关键字来实现线程之间的同步互斥,但在JDK1.5中新增加的ReentranLock也能达到同样的效果,并且在扩展功能上也更加强大。
一、使用lock方法和unlock方法实现同步
ReentranLock的lock方法用来获取锁,而调用unlock的方法是释放锁。
public class Test1 { public static void main(String[] args) throws InterruptedException { Service service = new Service(); ThreadA a = new ThreadA(service); a.setName("A"); ThreadA aa = new ThreadA(service); aa.setName("AA"); Thread.sleep(100); ThreadB b = new ThreadB(service); b.setName("B"); ThreadB bb = new ThreadB(service); bb.setName("BB"); bb.start(); a.start(); aa.start(); b.start(); } } class ThreadA extends Thread{ private Service service; public ThreadA(Service service){ this.service = service; } @Override public void run() { service.methodA(); } } class ThreadB extends Thread{ private Service service; public ThreadB(Service service){ this.service = service; } @Override public void run() { service.methodB(); } } class Service{ private ReentrantLock lock = new ReentrantLock(); public void methodA(){ try { lock.lock(); System.out.println("A ThreadName = "+Thread.currentThread().getName()); Thread.sleep(2000); System.out.println("A ThreadName = "+Thread.currentThread().getName()); }catch (Exception e){ e.printStackTrace(); }finally { lock.unlock(); } } public void methodB(){ try { lock.lock(); System.out.println("B ThreadName = "+Thread.currentThread().getName()); Thread.sleep(2000); System.out.println("B ThreadName = "+Thread.currentThread().getName()); }catch (Exception e){ e.printStackTrace(); }finally { lock.unlock(); } } }
上述代码使用lock方法和unlock方法实现了线程间的同步。输出结果如下
结果说明,调用lock方法的代码的线程就持有了对象监视器,其他线程只有等待锁被释放时再次争抢,效果和使用sunchronized关键字一样。
二、使用Condition实现等待/通知。
使用synchronized关键字以及wait方法和notify方法相结合可以实现等待/通知模式,而ReentranLock也可以实现同样的功能,但需要借助于Condition对象。
你可以在一个ReentranLock对象里面创建多个Condition实例(对象监视器),线程对象可以注册在指定的Condition中,从而可以有选择性的进行线程通知,在调度线程上更加灵活。
public class Test2 { public static void main(String[] args) throws InterruptedException { Service1 service1 = new Service1(); ThreadAA threadAA = new ThreadAA(service1); threadAA.setName("A"); threadAA.start(); Thread.sleep(1000); service1.singal(); } } class Service1{ private ReentrantLock lock = new ReentrantLock(); public Condition condition = lock.newCondition(); public void await(){ try { lock.lock(); System.out.println(Thread.currentThread().getName()+"我在等待通知的到来1"); condition.await(); System.out.println(Thread.currentThread().getName()+"等到通知了,我继续执行"); }catch (Exception e){ e.printStackTrace(); }finally { lock.unlock(); } } public void singal(){ try { lock.lock(); System.out.println("我要通知在等待的线程"); condition.signal(); }finally { lock.unlock(); } } } class ThreadAA extends Thread{ private Service1 service1; public ThreadAA(Service1 service1){ this.service1 = service1; } @Override public void run() { service1.await(); } }
上述代码就描述了Condition的用法,Condition在调用await方法之前必须调用lock方法来获得同步监视器。否则会报错。
而singal方法可以唤醒同一个Condition(对象监视器)监视下的线程。
输出结果如下:
等待一秒后通知
线程继续执行未完的代码。其中wait方法相当于await方法,notify方法相当于singal方法,notifyAll方法相当于singalAll方法。
刚才我们只是使用一个Condition方法通知一个线程,这次我们使用多个Condition通知多个线程。
public class Test3 { public static void main(String[] args) throws InterruptedException { Service2 service2 = new Service2(); Thread1 thread1 = new Thread1(service2); thread1.start(); Thread2 thread2 = new Thread2(service2); thread2.start(); Thread.sleep(2000); service2.singalA(); Thread.sleep(2000); service2.singalB(); } } class Thread1 extends Thread{ private Service2 service2 = new Service2(); public Thread1(Service2 service2){ this.service2 = service2; } @Override public void run() { service2.awaitA(); } } class Thread2 extends Thread{ private Service2 service2 = new Service2(); public Thread2(Service2 service2){ this.service2 = service2; } @Override public void run() { service2.awaitB(); } } class Service2{ private ReentrantLock lock = new ReentrantLock(); public Condition condition1 = lock.newCondition(); public Condition condition2 = lock.newCondition(); public void awaitA(){ try { lock.lock(); System.out.println("awaitA begin 在等待通知 "); condition1.await(); System.out.println("awaitA end 等到通知"); }catch (Exception e){ e.printStackTrace(); }finally { lock.unlock(); } } public void awaitB(){ try { lock.lock(); System.out.println("awaitB begin 在等待通知 "); condition2.await(); System.out.println("awaitB end 等到通知"); }catch (Exception e){ e.printStackTrace(); }finally { lock.unlock(); } } public void singalA(){ try { lock.lock(); System.out.println("我要通知awaitA"); condition1.signal(); }finally { lock.unlock(); } } public void singalB(){ try { lock.lock(); System.out.println("我要通知awaitB"); condition2.signal(); }finally { lock.unlock(); } } }上述代码有两个线程,线程1执行awaitA方法,线程2执行awaitB方法,而singalA只唤醒conditon1监视的线程,而singalB只唤醒condition2监视的线程。运行结果如下
开始,线程1和线程2都在等待通知
两秒之后,调用singalA唤醒线程1,线程1收到通知并执行未完成的代码,而线程2仍然在等待
再过两秒,调用singalB方法,通知线程2,线程2收到通知,并完成执行
三、公平锁与非公平锁
ReentranLock分为公平锁和非公平锁,公平锁表示线程获取锁的顺序是按照线程加锁的顺序来分配的,即先来先得的顺序。而非公平锁就是一种锁的抢占机制,是随机获得锁的,可能会导致某些线程一致拿不到锁,所以是不公平的。
public class Test4 { public static void main(String[] args) { Service3 service3 = new Service3(true); Thread[] threads = new Thread[10]; for (int i=0;i<10;i++){ threads[i] = new Thread(){ @Override public void run() { service3.testMethod(); } }; threads[i].setName("线程"+(i+1)); } for (int i=0;i<10;i++){ threads[i].start(); } } } class Service3{ private ReentrantLock lock; public Service3(boolean isFair){ lock = new ReentrantLock(isFair); } public void testMethod(){ try { lock.lock(); System.out.println(Thread.currentThread().getName()+"获得锁"); }finally { lock.unlock(); } } }
上述代码是使用公平锁,在构造ReentranLock时,有一个布尔值,为true代表公平锁,默认为非公平锁。
从结果来看,基本呈有序状态,这就是公平锁
而这个是非公平锁,可以看出基本是乱序。
总结:
1、ReentranLock使用lock和unlock来获得锁和释放锁
2、unlock最好的地方就是finally中,因为这样正常运行或者异常运行都会释放锁
3、使用Condition可以进行线程间通信,而且使用非常灵活,记住,使用condition的await和singal方法之前,必须调用lock方法获得对象监视器
4、公平锁和非公平锁基本没什么说的啦