如果一些知识,技术或者叫做技巧可以被每个人轻松掌握,那他就没什么价值了,当然不是要去探索一些无人掌握的知识,仅仅想告诉自己,学习并不是轻松的事,贵在坚持。
这篇开始使用可重入锁,和之前学过的知识一点都不冲突,了解一种更加优雅的加锁方式,了解公平锁于非公平锁,介绍一些常用的api,为之后的并发实战铺路。
ReentrantLock分析
在Java多线程中,可以使用synchronized关键字来实现线程之间的同步互斥,但在JDK1.5中新增加了ReentrantLock类也能达到同样的效果,并在拓展功能上也更加强大,比如具有嗅探锁定,多路分支通知等功能,而且在使用上也比synchronized更加的灵活。
lock与unlock
之前synchronized关键字中,将对象阔起来来加锁,同步代码块运行结束后自动释放锁;
import java.util.concurrent.TimeUnit;
import java.util.concurrent.locks.Lock;
import java.util.concurrent.locks.ReentrantLock;
public class LockUnlock {
public static void main(String[] args) {
Lock lock = new ReentrantLock();
Runnable run = () -> {
lock.lock();
System.out.println(Thread.currentThread().getName() + " start");
try {
TimeUnit.SECONDS.sleep(1);
} catch (InterruptedException e) {
e.printStackTrace();
}
System.out.println(Thread.currentThread().getName() + " end");
lock.unlock();
};
new Thread(run).start();
new Thread(run).start();
new Thread(run).start();
new Thread(run).start();
}
}
程序的运行结果如下:
可以看到加锁后必须等线程结束后,下一个线程才能运行
wait/notify
notify方法并不能确定唤醒哪些线程,比如之前的生产者消费者模型中线程假死就是因为生产者唤醒了生产者,消费者唤醒了消费者,下面将会介绍一种更灵活的方式–借助Condition对象,见下面消费者生产者模型
import java.util.concurrent.TimeUnit;
import java.util.concurrent.locks.Condition;
import java.util.concurrent.locks.Lock;
import java.util.concurrent.locks.ReentrantLock;
public class Run {
public static void main(String[] args) {
// 锁
Lock lock = new ReentrantLock();
// 工厂
Factory factory = new Factory(lock);
new Thread(() -> {
while (true) {
factory.product();
}
}, "p1").start();
new Thread(() -> {
while (true) {
factory.product();
}
}, "p2").start();
new Thread(() -> {
while (true) {
factory.consume();
}
}, "c1").start();
new Thread(() -> {
while (true) {
factory.consume();
}
}, "c2").start();
}
}
class Factory {
private int item = 0;
private Lock lock;
// 唤醒生产者的条件
private Condition p;
// 唤醒消费者的条件
private Condition c;
public Factory(Lock lock) {
this.lock = lock;
p = lock.newCondition();
c = lock.newCondition();
}
public void product() {
try {
lock.lock();
while (item == 1) {
p.await();
System.out.println(Thread.currentThread().getName() + "阻塞");
}
item = 1;
System.out.println(Thread.currentThread().getName() + "生产");
c.signal();
TimeUnit.SECONDS.sleep(1);
} catch (InterruptedException e) {
e.printStackTrace();
} finally {
lock.unlock();
}
}
public void consume() {
try {
lock.lock();
while (item == 0) {
c.await();
System.out.println(Thread.currentThread().getName() + "阻塞");
}
item = 0;
System.out.println(Thread.currentThread().getName() + "消费");
p.signal();
TimeUnit.SECONDS.sleep(1);
} catch (InterruptedException e) {
e.printStackTrace();
} finally {
lock.unlock();
}
}
}
这样设计只能由消费者唤醒阻塞的生产者,只能由生产者唤醒阻塞的消费者,不再出现生产者消费者共用一把锁导致生产者唤醒生产者消费者唤醒消费者而假死的现象了
公平锁与非公平锁
锁Lock分为“公平锁”和“非公平锁”,公平锁表示线程获取锁的顺序是按照线程加锁的顺序来分配的,即先来先得的FIFO先进先出顺序。而非公平锁就是一种获取锁的抢占机制,是随机获得锁的,和公平锁不一样的就是先来的不一定先得到锁,这个方式可能造成某些线程一直拿不到锁,结果也就是不公平的了。
API
getHoldCount()
查询当前线程保持此锁定的次数,也就是调用lock()的次数
getQueueLength()
返回正在等待获取此锁定的线程估计数
getWaitQueueLength(Condition condition)
返回等待与此锁定相关的给定条件condition的线程的估计数
hasQueuedThread(Thread thread)
查询指定线程是否正在等待获取此锁定,返回值boolean
hasQueuedThreads()
查询是否有线程正在等待获取此锁定,返回值boolean
hasWaiters(Condition condition)
查询是否有线程正在等待与此锁定有关的condition条件
isFair()
判断是不是公平锁,默认情况下,ReentrantLock类使用的是非公平锁
isHeldByCurrentThread()
查询当前线程是否保持此锁定
isLocked()
查询此锁定是否由任意线程保持
lockInterruptibly()
如果当前线程未被中断,则获取锁定,如果已经被中断则出现异常
tryLock
仅在调用时锁定未被另一个线程保持的情况下,才获取该锁定(说得好像其他线程保持的情况下你能得到似的)
tryLock(long timeout,TimeUnit unit)
如果锁定在给定等待时间内没有被另一个 线程保持,且当前线程未被中断,则获取该锁定