本文主要介绍使用Java5中Lock对象也能实现同步的效果,并且在使用上更加方便。html
本文着重掌握以下2个知识点:编程
在Java多线程中,可使用 synchronized 关键字来实现线程之间同步互斥,但在JDK1.5中新增长了 ReentrantLock 类也能达到一样的效果,而且在扩展功能上也更增强大,好比具备嗅探锁定、多路分支通知等功能,并且在使用上也比 synchronized 更加的灵活。安全
调用ReentrantLock对象的lock()方法获取锁,调用unlock()方法释放锁。多线程
下面是初步的程序示例:并发
public class Demo { private Lock lock = new ReentrantLock(); public void test(){ lock.lock(); for (int i= 0;i<5;i++){ System.out.println(Thread.currentThread().getName()+" - "+i); } lock.unlock(); } public static void main(String[] args) { Demo demo = new Demo(); for (int i = 0;i<5;i++){ new Thread(new Runnable() { @Override public void run() { demo.test(); } }).start(); } } }
运行结果:ide
Thread-0 - 0 Thread-0 - 1 Thread-0 - 2 Thread-0 - 3 Thread-0 - 4 Thread-1 - 0 Thread-1 - 1 Thread-1 - 2 Thread-1 - 3 Thread-1 - 4 Thread-2 - 0 Thread-2 - 1 Thread-2 - 2 Thread-2 - 3 Thread-2 - 4 Thread-3 - 0 Thread-3 - 1 Thread-3 - 2 Thread-3 - 3 Thread-3 - 4 Thread-4 - 0 Thread-4 - 1 Thread-4 - 2 Thread-4 - 3 Thread-4 - 4
从运行的结果来看,当前线程打印完毕后将锁进行释放,其余线程才能够继续打印。学习
上面的示例是全部线程调用一个ReentrantLock实例对象实现同步,若是每一个线程都调用各自ReentrantLock实例对象的同一段代码呢?线程
示例代码:code
public class MyService implements Runnable{ private ReentrantLock lock = new ReentrantLock(); public void method(){ try { lock.lock(); System.out.println(Thread.currentThread().getName()+"锁定..."); Thread.sleep(2000); System.out.println(Thread.currentThread().getName()+"解锁。"); } catch (InterruptedException e) { e.printStackTrace(); } finally { lock.unlock(); } } public static void main(String[] args) { new Thread(new MyService()).start(); new Thread(new MyService()).start(); new Thread(new MyService()).start(); } @Override public void run() { method(); } }
运行结果:htm
Thread-0锁定... Thread-2锁定... Thread-1锁定... Thread-2解锁。 Thread-0解锁。 Thread-1解锁。
从运行结果来看,并无实现想要的方法同步的效果。若是咱们想要实现相似synchronized(class),也就是给Class类上锁,能够把 ReentrantLock 声明为 static 静态变量。
示例代码:
public class MyService implements Runnable{ private static ReentrantLock lock = new ReentrantLock(); public void method(){ try { lock.lock(); System.out.println(Thread.currentThread().getName()+"锁定..."); Thread.sleep(2000); System.out.println(Thread.currentThread().getName()+"解锁。"); } catch (InterruptedException e) { e.printStackTrace(); } finally { lock.unlock(); } } public static void main(String[] args) { new Thread(new MyService()).start(); new Thread(new MyService()).start(); new Thread(new MyService()).start(); } @Override public void run() { method(); } }
运行结果:
Thread-0锁定... Thread-0解锁。 Thread-1锁定... Thread-1解锁。 Thread-2锁定... Thread-2解锁。
从运行结果来看,成功实现了预期的结果。
关键字 synchronized 与 wait() 和 notify() / notifyAll() 方法相结合能够实现等待 / 通知模式,类 ReentrantLock 也能够实现一样的功能,但须要借助于 Condition(即对象监视器)实例,线程对象能够注册在指定的 Condition 中,从而能够有选择性地进行线程通知,在调度线程上更加灵活。
在使用 notify() / notifyAll() 方法进行通知时,被通知的线程倒是由JVM随机选择的。但使用 ReentrantLock 结合 Condition 类是能够实现前面介绍过的“选择性通知”,这个功能是很是重要的,并且在 Condition 类中是默认提供的。
示例代码:
public class Demo { private Lock lock = new ReentrantLock(); private Condition condition = lock.newCondition(); public void await() { try { lock.lock(); System.out.println("开始等待:" + System.currentTimeMillis()); condition.await(); } catch (InterruptedException e) { e.printStackTrace(); } finally { lock.unlock(); } } public void signal() { try { lock.lock(); System.out.println("结束等待:" + System.currentTimeMillis()); condition.signal(); } finally { lock.unlock(); } } public static void main(String[] args) throws InterruptedException { Demo demo = new Demo(); new Thread(new Runnable() { @Override public void run() { demo.await(); } }).start(); Thread.sleep(3000); demo.signal(); } }
运行结果:
开始等待:1537352883839 结束等待:1537352886839
成功实现等待 / 通知模式。
在Object中,有wait() 、wait(long)、notify()、notifyAll()方法。
在Condition类中,有 await()、await(long)、signal()、signalAll()方法。
示例代码:
public class Demo { private Lock lock = new ReentrantLock(); private Condition conditionA = lock.newCondition(); private Condition conditionB = lock.newCondition(); public void awaitA() { try { lock.lock(); System.out.println("A开始等待:" + System.currentTimeMillis()); conditionA.await(); System.out.println("A结束等待:" + System.currentTimeMillis()); } catch (InterruptedException e) { e.printStackTrace(); } finally { lock.unlock(); } } public void awaitB() { try { lock.lock(); System.out.println("B开始等待:" + System.currentTimeMillis()); conditionB.await(); System.out.println("B结束等待:" + System.currentTimeMillis()); } catch (InterruptedException e) { e.printStackTrace(); } finally { lock.unlock(); } } public void signalAll_B() { try { lock.lock(); conditionB.signalAll(); } finally { lock.unlock(); } } public static void main(String[] args) throws InterruptedException { Demo demo = new Demo(); new Thread(new Runnable() { @Override public void run() { demo.awaitA(); } }).start(); new Thread(new Runnable() { @Override public void run() { demo.awaitB(); } }).start(); Thread.sleep(3000); demo.signalAll_B(); } }
运行结果:
A开始等待:1537354021740 B开始等待:1537354021741 B结束等待:1537354024738
能够看到,只有B线程被唤醒了。
经过此实验可知,使用 ReentrantLock 对象能够唤醒指定种类的线程,这是控制部分线程行为的方便行为。
锁Lock分为”公平锁“和“非公平锁”,公平锁表示线程获取锁的顺序是按照线程加载的顺序来分配的,即先来先得的FIFO先进先出顺序。而非公平锁就是一种获取锁的抢占机制,是随机得到锁的,和公平锁不同的就是先来的不必定先获得锁,这个方式可能形成某些线程一直拿不到锁,结果也就是不公平的了。
设置公平锁:
Lock lock = new ReentrantLock(true);
使用ReentrantLock类设置公平锁只须要在构造时传入boolean参数便可。默认false。须要明白的是,即便设置为true也不能保证百分百公平。
总结:
公平锁:先去判断等待队列是否为空,也就是是否有线程在等待,没有就去获取锁,不然把本身加入等待队列。
非公平锁:先去尝试获取锁,若是失败再加入到等待队列。
1.方法getHoldCount() 的做用是查询当前线程保持此锁定的个数,也就是调用 lock() 方法的次数。
示例代码:
public class Service { private ReentrantLock lock = new ReentrantLock(); public void method() { try { lock.lock(); System.out.println("getHoldCount() " + lock.getHoldCount()); method2(); } finally { lock.unlock(); } } public void method2() { try { lock.lock(); System.out.println("getHoldCount() " + lock.getHoldCount()); } finally { lock.unlock(); } } public static void main(String[] args) { Service service = new Service(); service.method(); } }
运行结果:
getHoldCount() 1 getHoldCount() 2
2.方法getQueryLength() 的做用是返回正等待获取此锁定的线程估计数。好比有5个方法,1个线程首先执行 await()方法,那么在调用getQueueLength()方法后返回值是4,说明有4个线程同时在等待 lock 的释放。
示例代码:
public class Service { private ReentrantLock lock = new ReentrantLock(); public void method() { try { lock.lock(); System.out.println("Name: " + Thread.currentThread().getName()); Thread.sleep(Integer.MAX_VALUE); } catch (InterruptedException e) { e.printStackTrace(); } finally { lock.unlock(); } } public static void main(String[] args) throws InterruptedException { Service service = new Service(); Runnable runnable = new Runnable() { @Override public void run() { service.method(); } }; for (int i = 0; i < 5; i++) { new Thread(runnable).start(); } Thread.sleep(1000); ReentrantLock lock = service.getLock(); System.out.println("有多少线程在等待:"+lock.getQueueLength()); } private ReentrantLock getLock() { return lock; } }
运行结果:
Name: Thread-1 有多少线程在等待:4
3.方法getWaitQueryLength(condition) 的做用是返回等待与此锁定相关的给定条件Condition的线程估计数,好比有5个线程,每一个线程都执行了同一个condition 对象的await() 方法,则调用 getWaitQueryLength(condition) 方法时返回的int值是5。
示例代码:
public class Service { private ReentrantLock lock = new ReentrantLock(); private Condition condition = lock.newCondition(); public void method() { try { lock.lock(); condition.await(); } catch (InterruptedException e) { e.printStackTrace(); } finally { lock.unlock(); } } public void notifyMethod() { try { lock.lock(); System.out.println("等待condition的线程数" + lock.getWaitQueueLength(condition)); condition.signalAll(); } finally { lock.unlock(); } } public static void main(String[] args) throws InterruptedException { Service service = new Service(); Runnable runnable = new Runnable() { @Override public void run() { service.method(); } }; for (int i = 0; i < 5; i++) { new Thread(runnable).start(); } Thread.sleep(1000); service.notifyMethod(); } }
运行结果:
等待condition的线程数5
1.方法 boolean hasQueuedThread(Thread thread) 的做用是查询指定的线程是否正在等待获取此锁定。
2.方法 boolean hasQueuedThreads() 的做用是查询是否有线程正在等待获取此锁定。
一、2示例代码:
public class Service { private ReentrantLock lock = new ReentrantLock(); private Condition condition = lock.newCondition(); public void waitMethod(){ try { lock.lock(); Thread.sleep(Integer.MAX_VALUE); }catch (InterruptedException e){ e.printStackTrace(); }finally { lock.unlock(); } } public ReentrantLock getLock(){ return lock; } public static void main(String[] args) throws InterruptedException { final Service service = new Service(); Runnable runnable = new Runnable() { @Override public void run() { service.waitMethod(); } }; Thread thread1 = new Thread(runnable); Thread thread2 = new Thread(runnable); thread1.start(); thread2.start(); Thread.sleep(1000); ReentrantLock lock = service.getLock(); System.out.println(lock.hasQueuedThreads()); System.out.println(lock.hasQueuedThread(thread1)); System.out.println(lock.hasQueuedThread(thread2)); } }
运行结果:
true false true
3.方法 boolean hasWaiters(Condition condition) 的做用是查询是否有线程正在等待与此锁定有关的 condition 条件。
示例代码:
public class Service { private ReentrantLock lock = new ReentrantLock(); private Condition condition = lock.newCondition(); public void waitMethod(){ try { lock.lock(); condition.await(); }catch (InterruptedException e){ e.printStackTrace(); }finally { lock.unlock(); } } public void notifyMethod(){ try { lock.lock(); System.out.println("有没有线程正在等待 condition ?" + lock.hasWaiters(condition) + " 线程数是多少?" + lock.getWaitQueueLength(condition)); condition.signalAll(); }finally { lock.unlock(); } } public static void main(String[] args) throws InterruptedException { Service service = new Service(); Runnable runnable = new Runnable() { @Override public void run() { service.waitMethod(); } }; for (int i = 0; i < 10; i++) { new Thread(runnable).start(); } Thread.sleep(2000); service.notifyMethod(); } }
运行结果:
有没有线程正在等待 condition ?true 线程数是多少?10
更改上面的部分代码:
System.out.println(lock.isHeldByCurrentThread()); System.out.println(lock.isLocked()); lock.lock(); System.out.println(lock.isLocked()); System.out.println(lock.isHeldByCurrentThread());
运行结果:
false false true true
下面的三个方法都是对lock.lock()方法的另外一种变形:
方法void lockInterruptibly()的做用是:若是当前线程未被中断,则获取锁定,若是已经被中断则出现异常。
而使用 lock() 方法,即便线程被中断(调用thread.interrupt()方法),也不会出现异常。
方法boolean tryLock() 的做用是,仅在未被另外一个线程保持的状况下,才获取该锁定。
假设有两个线程同时调用同一个lock对象的tryLock()方法,那么除了第一个得到锁(返回true),其它都获取不到锁(返回false)。
方法 boolean tryLock(long timeout, TimeUnit unit) 的做用是,若是锁定在给定等待时间内没有被另外一个线程保持,且当前线程未被中断,则获取该锁定。
前面讲到,执行condition.await()方法后,线程进入等待状态,若是这时线程被中断(调用thread.interrupt()方法)则会抛出异常。而使用 condition.awaitUninterruptibly() 方法代替 condition.await() 方法则不会抛出异常。
使用方法 condition.awaitUntil(Date deadline) 能够代替 await(long time, TimeUnit unit) 方法进行线程等待,该方法在等待时间到达前是能够被提早唤醒的。
使用Condition对象能够对线程执行的业务进行排序规划。
示例代码:
public class DThread{ volatile private static int nextPrintWho = 1; private static ReentrantLock lock = new ReentrantLock(); final private static Condition conditionA = lock.newCondition(); final private static Condition conditionB = lock.newCondition(); final private static Condition conditionC = lock.newCondition(); public static void main(String[] args) { Thread threadA = new Thread(){ @Override public void run() { try { lock.lock(); while (nextPrintWho != 1){ conditionA.await(); } for (int i = 0;i<3;i++){ System.out.println("ThreadA "+(i+1)); } nextPrintWho = 2; conditionB.signalAll(); } catch (InterruptedException e) { e.printStackTrace(); }finally { lock.unlock(); } } }; Thread threadB = new Thread(){ @Override public void run() { try { lock.lock(); while (nextPrintWho != 2){ conditionA.await(); } for (int i = 0;i<3;i++){ System.out.println("ThreadB "+(i+1)); } nextPrintWho = 3; conditionB.signalAll(); } catch (InterruptedException e) { e.printStackTrace(); }finally { lock.unlock(); } } }; Thread threadC = new Thread(){ @Override public void run() { try { lock.lock(); while (nextPrintWho != 3){ conditionA.await(); } for (int i = 0;i<3;i++){ System.out.println("ThreadC "+(i+1)); } nextPrintWho = 1; conditionB.signalAll(); } catch (InterruptedException e) { e.printStackTrace(); }finally { lock.unlock(); } } }; for (int i= 0;i<5;i++){ new Thread(threadA).start(); new Thread(threadB).start(); new Thread(threadC).start(); } } }
打印结果:
ThreadA 1 ThreadA 2 ThreadA 3 ThreadB 1 ThreadB 2 ThreadB 3 ThreadC 1 ThreadC 2 ThreadC 3 ....
类 ReentrantLock 具备彻底互斥排他的效果,即同一时间只有一个线程在执行ReentrantLock.lock() 方法后面的任务。这样作虽然保证了实例变量的线程安全性,但效率倒是很是低下的。因此在JDK中提供了一种读写锁 ReentrantReadWriteLock 类,使用它能够加快运行效率,在某些不须要操做实例变量的方法中,彻底可使用读写 ReentrantReadWriteLock 来提高该方法的代码运行速度。
读写锁表示也有两个锁,一个是读操做相关的锁,也称为共享锁;另外一个是写操做相关的锁,也叫排他锁。也就是多个读锁之间不互斥,读锁与写锁互斥,写锁与写锁互斥。在没有线程 Thread进行写入操做时,进行读取操做的多个 Thread 均可以获取读锁,而进行写入操做的 Thread 只有在获取写锁后才能进行写入操做。即多个 Thread能够同时进行读取操做可是同一时刻只容许一个 Thread 进行写入操做。
总结起来就是:读读共享,写写互斥,读写互斥,写读互斥。
声明读写锁:
ReentrantReadWriteLock lock = new ReentrantReadWriteLock();
获取读锁:
lock.readLock().lock();
获取写锁:
lock.writeLock().lock();
学习完本文彻底可使用Lock对象将 synchronized关键字替换掉,并且其具备的独特功能也是 synchronized 所不具备的。在学习并发时,Lock是synchronized关键字的进阶,掌握Lock有助于学习并发包中源代码的实现原理,在并发包中大量的类使用了Lock 接口做为同步的处理方式。
《Java多线程编程核心技术》高洪岩著