[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
可以实现选择性通知。
代码如下:
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实现选择性通知。
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值设置锁的类型。
通过源码,我们可以知道,在不传入值的情况下默认是非公平锁。
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
读写锁,在某些不需要操作实例变量中,完全可以使用读写锁来提升代码运行效率。
读写锁有两个锁,一个是读相关的锁,叫做共享锁,另一个是写操作相关的锁,也称排他锁。写锁与任何锁都互斥。
同一个时刻读锁允许多个线程获取锁,而写锁只允许一个线程获取锁
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();
}
}
}
}
结果如下:
获取读锁: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基本上一致。