[TOC]
Lock ReentrantLock 在JDK1.5的版本中新加入了很多特性。其中就有ReentrantLock
。ReentrantLock
比synchronized
更灵活。功能也更强大。学习线程的过程中也是不可避免的。new
一个ReentrantLock
返回一个Lock
接口对象。 通过lock
加锁。unlock
解锁。在lock与unlock之间的代码就是同步区域。
使用Condition实现等待/通知 关键字synchronized
与wait
和notify
方法相结合可以实现等待通知模式,类ReentrantLock
也可以实现同样的功能,但是要借助于Condition
对象,它有更好的灵活性,比如实现多路通知功能,也就是说在一个lock对象创建多个Condition
实例,线程对象可以注册在指定的Condition
中,从而可以有选择的进行线程通知,在调度线程上更灵活。 在notify中,被通知的线程是JVM随机选择的。但使用ReentrantLock
结合Condition
可以实现选择性通知。代码如下 :
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 public class ThreadCondition { public static void main (String[] args) throws InterruptedException { Service service = new Service(); new Thread(()->{ service.await(); }).start(); Thread.sleep(1000 ); service.signal(); } } class Service { private Lock lock = new ReentrantLock(); private Condition condition = lock.newCondition(); public void await () { try { lock.lock(); System.out.println("A" ); condition.await(); System.out.println("A" ); }catch (Exception e){ e.printStackTrace(); }finally { lock.unlock(); } } public void signal () { try { lock.lock(); System.out.println("解锁" ); condition.signal(); }finally { lock.unlock(); } } }
其中Condition
的await
等于Object
的wait
,signal
等于notify
。signalAll
等于notifyAll
。
使用Condition实现选择性通知。 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 public class ThreadConditionSelect { public static void main (String[] args) throws InterruptedException { ThreadConditionSelectService service1 = new ThreadConditionSelectService(); new Thread(() -> { service1.awaitA(); }).start(); new Thread(() -> { service1.awaitB(); }).start(); Thread.sleep(500 ); service1.signalAll_A(); } } class ThreadConditionSelectService { 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:wait" ); conditionA.await(); System.out.println("A:run" ); } catch (InterruptedException e) { e.printStackTrace(); } finally { lock.unlock(); } } public void awaitB () { try { lock.lock(); System.out.println("B:wait" ); conditionB.await(); System.out.println("B:run" ); } catch (InterruptedException e) { e.printStackTrace(); } finally { lock.unlock(); } } public void signalAll_A () { try { lock.lock(); System.out.println("A:signalAll" ); conditionA.signalAll(); System.out.println("A:signalAll" ); } catch (Exception e) { e.printStackTrace(); } finally { lock.unlock(); } } public void signalAll_B () { try { lock.lock(); System.out.println("B:signalAll" ); conditionB.signalAll(); System.out.println("B:signalAll" ); } catch (Exception e) { e.printStackTrace(); } finally { lock.unlock(); } } }
在这代码中最终只有A被唤醒。 通过这种方式我们可以唤醒指定种类的线程。
公平锁与非公平锁 锁lock分为公平锁与非公平锁。公平锁表示线程获取锁的顺序是安装线程加锁的顺序来分配的,即FIFO先进先出的顺序。而非公平锁则是抢占。随机获取锁。所以可能会有一些线程一直拿不到锁。 通过创建ReentrantLock的构造函数,传入Boolean值设置锁的类型。 通过源码,我们可以知道,在不传入值的情况下默认是非公平锁。
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 public class ThreadGPLock { public static void main (String[] args) { ThreadGPLockService gpLockService = new ThreadGPLockService(); for (int i = 0 ; i < 100 ; i++) { new Thread(() -> { System.out.println(Thread.currentThread().getName() + ":运行了" ); gpLockService.await(); }).start(); } } } class ThreadGPLockService { private Lock lock = new ReentrantLock(true ); public void await () { try { lock.lock(); System.out.println(Thread.currentThread().getName() + ":获得锁" ); } finally { lock.unlock(); } } }
通过结果我们可以看出,当锁是公平锁时,谁先等待那么谁就先获取到锁。而非公平锁则是相互竞争。
ReentrantLock类的方法 getHoldCount()
的作用时查询当前线程锁保持锁定的个数。也就是调用lock()方法的次数。getQueueLength()
是返回正在等待获取锁的线程数,表示没有调用await的线程。getWaitQueueLength()
与getQueueLength()
相反,它表示已经调用了await方法的线程数。hasQueuedThread(Thread)
的作用是查询指定线程释放在等待获取此锁定。HasQueuedThreads()
的作用是查询是否有线程正在等待获取此锁定。hasWaiters(Condition condition)
查询是否有线程正在等待与此锁定有关的condition条件。 …
lock接口中的方法。 lockInterruptibly()
的作用是如果当前线程未被中断,则获取锁定。如果已经被中断则出现异常0
tryLock()
的作用是如果锁没被其他线程获取就获得锁。
tryLock(long time, TimeUnit unit)
在给定时间内没有被另一个线程保持,且当前线程未被中断,则获取该锁。
ReentrantReadWriteLock ReentrantLock
类具有完全排他的效果,在同一时间下只有一个线程在执行lock方法后面的任务,虽然保证了安全性,但是效率相对低下。我们可以使用ReentrantReadWriteLock
读写锁,在某些不需要操作实例变量中,完全可以使用读写锁来提升代码运行效率。 读写锁有两个锁,一个是读相关的锁,叫做共享锁,另一个是写操作相关的锁,也称排他锁。写锁与任何锁都互斥。 同一个时刻读锁允许多个线程获取锁,而写锁只允许一个线程获取锁
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 public class ThreadReadWriteLock { public static void main (String[] args) { Service service = new Service(); for (int i = 0 ; i < 10 ; i++) { new Thread(()->{ service.read(); }).start(); } } static class Service { private ReentrantReadWriteLock lock=new ReentrantReadWriteLock(); public void read () { try { lock.readLock().lock(); System.out.println("获取读锁:" +Thread.currentThread().getName()); Thread.sleep(500 ); } catch (InterruptedException e) { e.printStackTrace(); } finally { lock.readLock().unlock(); } } } }
结果如下:
1 2 3 4 5 6 7 8 9 10 获取读锁:Thread-0 获取读锁:Thread-3 获取读锁:Thread-5 获取读锁:Thread-2 获取读锁:Thread-1 获取读锁:Thread-6 获取读锁:Thread-4 获取读锁:Thread-8 获取读锁:Thread-7 获取读锁:Thread-9
通过lock.readLock().lock()加一个读锁。 通过lock.writeLock().lock()加一个写锁 如果这个锁是一个ReentrantLock锁,则同一时刻只能允许一个线程运行,所以如果有10个线程。每个线程睡眠0.5秒,十个线程则需要睡5秒。在使用读锁之后,所有线程都可以同时运行。 可见效率要高很多。 写线程与ReentrantLock基本上一致。