1 ReentrantLock重入锁
和关键字synchronization相比,重入锁有着显示的操作过程。需要手动指定何时加锁,何时释放锁。重入锁对逻辑控制的灵活性要远远优于关键字synchronization。
在退出时,必须记得释放锁,否则其他线程就没有机会访问。
重入锁之所以叫重入锁,是因为这种锁能反复进入。但是只限于一个线程。
//锁定几个就要释放几个
try{
lock.lock();
lock.lock()
}finally{
lock.unlock();
lock.unlock();
}
复制代码
使用ReentrantLock对象的lock()方法获取锁,使用unlock()方法释放锁。
public class MyLock extends Thread{
private Lock lock = new ReentrantLock();
@Override
public void run(){
lock.lock();
for (int i = 0;i<5;i++){
System.out.println("ThreadName = "+Thread.currentThread().getName() + " " + i);
}
lock.unlock();
}
public static void main(String[] args) {
MyLock myLock = new MyLock();
Thread r = new Thread(myLock);
Thread r1 = new Thread(myLock);
Thread r2 = new Thread(myLock);
r.start();r1.start();r2.start();
}
}
复制代码
从结果来看,当前线程执行完毕后对锁进行释放,其他线程才可以继续打印。当调用lock.lock()方法后,该线程就持有了“对象监视器”,其他线程只有等待锁被释放时再次争抢。
ReentrantLock的几个重要方法:
- lock():获得锁,如果锁被占用则等待。
- lockInterruptibly():获得锁,但优先响应中断。
- tryLock():尝试获得锁,如果成功,则返回true;失败返回false。此方法不等待,立即返回。
- unlock():释放锁。
1.1 公平锁和非公平锁
锁Lock分为公平锁和非公平锁,公平锁表示线程获取锁的顺序是按照线程加锁的顺序来分配的,即先来先得先进先出的顺序。而非公平锁就是一种获取锁的抢占机制,是随机获得锁的,和公平锁不一样的就是先来的不一定先得到锁,这会造成某些线程一直拿不到锁,出现饥饿
现象。
公平锁虽好,但是实现公平锁需要系统维护一个有序队列,因此公平锁的实现成本比较高,性能低下。
synchronization关键字产生得锁是非公平锁。
通过ReentrantLock的构造函数可以指定公平锁还是非公平锁。
默认情况下ReentrantLock使用的是非公平锁。
public class FairLock extends Thread{
private ReentrantLock lock;
public FairLock(boolean isFair){
lock = new ReentrantLock(isFair);
}
@Override
public void run(){
try{
lock.lock();
System.out.println("⭐线程 "+ Thread.currentThread().getName() + "运行");
}finally {
lock.unlock();
}
}
public static void main(String[] args) {
FairLock fairLock = new FairLock(true);
Thread[] threads = new Thread[10];
for (int i = 0;i< 10;i++){
threads[i] = new Thread(fairLock);
}
Arrays.stream(threads).forEach(thread -> thread.start());
}
}
复制代码
运行结果
⭐线程 Thread-1运行
⭐线程 Thread-4运行
⭐线程 Thread-3运行
⭐线程 Thread-2运行
⭐线程 Thread-5运行
⭐线程 Thread-6运行
⭐线程 Thread-7运行
⭐线程 Thread-8运行
⭐线程 Thread-9运行
⭐线程 Thread-10运行
复制代码
打印的结果基本上是有序的,这就是公平锁。如果将参数改为false,那么打印结果基本上是乱序的。
⭐线程 Thread-1运行
⭐线程 Thread-4运行
⭐线程 Thread-2运行
⭐线程 Thread-5运行
⭐线程 Thread-3运行
⭐线程 Thread-10运行
⭐线程 Thread-8运行
⭐线程 Thread-7运行
⭐线程 Thread-9运行
⭐线程 Thread-6运行
复制代码
1.2 getHoldCount()方法
getHoldCount()方法的作用是查询当前线程保持此锁定的个数,也就是调用lock()方法的次数。
public class MyGetHoldCount {
private ReentrantLock lock = new ReentrantLock();
public void serviceLock1(){
try {
lock.lock();
System.out.println("serviceLock1 :" + lock.getHoldCount());
this.serviceLock2();
}finally {
lock.unlock();
}
}
public void serviceLock2(){
try {
lock.lock();
System.out.println("serviceLock2 :" + lock.getHoldCount());
}finally {
lock.unlock();
}
}
public static void main(String[] args) {
new MyGetHoldCount().serviceLock1();
}
}
复制代码
打印输出
serviceLock1 :1
serviceLock2 :2
复制代码
1.3 getQueueLength()方法
getQueueLength()方法的作用是返回正等待获取此锁定的线程数。
public class MyQueueLen {
public ReentrantLock lock = new ReentrantLock();
public void serviceMe(){
try {
lock.lock();
System.out.println("ThreadName = "+ Thread.currentThread().getName() + "获取锁");
Thread.sleep(Integer.MAX_VALUE);
}catch (InterruptedException e){
}finally {
lock.unlock();
}
}
public static void main(String[] args) throws InterruptedException {
MyQueueLen myQueueLen = new MyQueueLen();
Runnable runnable = new Runnable() {
@Override
public void run() {
myQueueLen.serviceMe();
}
};
Thread[] threads = new Thread[10];
for (int i = 0;i<10;i++){
threads[i] = new Thread(runnable);
}
Arrays.stream(threads).forEach(thread -> thread.start());
Thread.sleep(1000);
System.out.println("有" + myQueueLen.lock.getQueueLength() + "个线程在等待锁");
}
}
复制代码
打印输出
ThreadName = Thread-0获取锁
有9在等待锁
复制代码
1.4 hasQueueThread()方法
boolean hasQueueThread(Thread thread)的作用是查询指定线程是否正在等待获取此锁定。
boolean hasQueueThreads()的作用是查询是否有线程正在等待获取此锁定。
public class MyHasQTh {
public ReentrantLock lock = new ReentrantLock();
public void serviceMeth(){
try{
lock.lock();
Thread.sleep(Integer.MAX_VALUE);
} catch (InterruptedException e) {
}finally {
lock.unlock();
}
}
public static void main(String[] args) throws InterruptedException {
MyHasQTh myHasQTh = new MyHasQTh();
Runnable runnable = new Runnable() {
@Override
public void run() {
myHasQTh.serviceMeth();
}
};
Thread threadA = new Thread(runnable);
threadA.start();
Thread.sleep(1000);
Thread threadB = new Thread(runnable);
threadB.start();
Thread.sleep(500);
System.out.println(myHasQTh.lock.hasQueuedThread(threadA));
System.out.println(myHasQTh.lock.hasQueuedThread(threadB));
System.out.println(myHasQTh.lock.hasQueuedThreads());
}
}
复制代码
打印结果
false
true
true
复制代码
1.5 isFai()方法
isFair()方法的作用是判断是不是公平锁
public class IsFair {
private ReentrantLock lock;
public IsFair(boolean fair){
lock = new ReentrantLock(fair);
}
public void serviceMeth(){
try {
lock.lock();
System.out.println("是否是公平锁:" + lock.isFair());
}finally {
lock.unlock();
}
}
public static void main(String[] args) {
IsFair isFair = new IsFair(true);
Runnable runnable = new Runnable() {
@Override
public void run() {
isFair.serviceMeth();
}
};
new Thread(runnable).start();
}
}
复制代码
打印结果
是否是公平锁:true
复制代码
1.6 isHeldByCurrentThread()方法
boolean isHeldByCurrentThread()方法的作用是查询当前线程是否保持此锁定。
public class MyHeldThread {
private ReentrantLock lock;
public MyHeldThread(boolean fair){
lock = new ReentrantLock(fair);
}
public void serviceMethod(){
try{
System.out.println(lock.isHeldByCurrentThread());
lock.lock();
System.out.println(lock.isHeldByCurrentThread());
}finally {
lock.unlock();
}
}
public static void main(String[] args) {
MyHeldThread myHeldThread = new MyHeldThread(true);
new Thread(new Runnable() {
@Override
public void run() {
myHeldThread.serviceMethod();
}
}).start();
}
}
复制代码
打印结果
false
true
复制代码
1.7 isLocked()方法
Boolean isLocked()方法的作用是查询此锁定是否由任意线程保持。
public class MyIsLocked {
public ReentrantLock lock = new ReentrantLock();
public void serviceMethod(){
try{
System.out.println(lock.isLocked());
lock.lock();
System.out.println(lock.isLocked());
}finally {
lock.unlock();
}
}
public static void main(String[] args) {
MyIsLocked myIsLocked = new MyIsLocked();
new Thread(new Runnable() {
@Override
public void run() {
myIsLocked.serviceMethod();
}
}).start();
}
}
复制代码
打印结果
false
true
复制代码
1.8 tryLock()方法
boolean tryLock()方法的作用是仅在调用时锁定未被另一个线程保持的情况下返回true,若锁定以被保持则返回false。并且是立即执行,不会进行等待。
tryLock 是防止自锁的一个重要方式。
public class MyTryLock {
public ReentrantLock lock = new ReentrantLock();
public void serviceMethod(){
try {
if (lock.tryLock()) {
System.out.println(Thread.currentThread().getName() + "获取锁定 "+System.currentTimeMillis());
Thread.sleep(4000);
} else {
System.out.println(Thread.currentThread().getName() + "未获取锁定");
}
} catch (Exception e) {
e.printStackTrace();
}finally {
if (lock.isHeldByCurrentThread()) {
lock.unlock();
}
}
}
public static void main(String[] args) {
MyTryLock myTryLock = new MyTryLock();
Runnable runnable = new Runnable() {
@Override
public void run() {
myTryLock.serviceMethod();
}
};
Thread aa = new Thread(runnable, "aa");
aa.start();
Thread bb = new Thread(runnable, "bb");
bb.start();
}
}
复制代码
打印结果:
aa未获取锁定
bb获取锁定 1560237476296
复制代码
boolean tryLock(long timeout,TimeUnit unit)的作用是如果锁定在给定等待时间内没有被另一个线程保持,且当前线程未被中断,则获取该锁定。
会在指定时间内等待获取锁。
public class MyTryLock {
public ReentrantLock lock = new ReentrantLock();
public void serviceMethod(){
try {
if (lock.tryLock(3,TimeUnit.SECONDS)) {
System.out.println(Thread.currentThread().getName() + "获取锁定 "+System.currentTimeMillis());
Thread.sleep(2000);
} else {
System.out.println(Thread.currentThread().getName() + "未获取锁定");
}
} catch (Exception e) {
e.printStackTrace();
}finally {
if (lock.isHeldByCurrentThread()) {
lock.unlock();
}
}
}
public static void main(String[] args) {
MyTryLock myTryLock = new MyTryLock();
Runnable runnable = new Runnable() {
@Override
public void run() {
System.out.println(Thread.currentThread().getName() + " " + System.currentTimeMillis());
myTryLock.serviceMethod();
}
};
Thread aa = new Thread(runnable, "aa");
aa.start();
Thread bb = new Thread(runnable, "bb");
bb.start();
}
}
复制代码
打印结果:
aa 1560237873563
bb 1560237873563
bb获取锁定 1560237873563
aa获取锁定 1560237875579
复制代码
若将代码中Thread.sleep()的时间改为超过3秒,则会打印:
aa 1560237942950
bb 1560237942950
bb获取锁定 1560237942950
aa未获取锁定
复制代码
1.9 getWaitQueueLength()方法
int getWaitQueueLength(Condition con)方法的作用是返回等待与此锁定相关的给定Condition的线程数。就是有多少个指定的Condition实例在等待此锁定。
public class MyWaitQuere {
public ReentrantLock lock = new ReentrantLock();
public Condition con = lock.newCondition();
public void waitMethod(){
try{
lock.lock();
con.await();
} catch (InterruptedException e) {
e.printStackTrace();
} finally {
lock.unlock();
}
}
public void signalMethod(){
try{
lock.lock();
System.out.println("有 " + lock.getWaitQueueLength(con) + "个线程正在等待con");
con.signal();
}finally {
lock.unlock();
}
}
public static void main(String[] args) throws InterruptedException {
MyWaitQuere myWaitQuere = new MyWaitQuere();
Runnable runnable = new Runnable() {
@Override
public void run() {
myWaitQuere.waitMethod();
}
};
Thread[] threads = new Thread[10];
for(int i = 0;i< 10;i++){
threads[i] = new Thread(runnable);
}
Arrays.stream(threads).forEach(thread -> thread.start());
Thread.sleep(2000);
myWaitQuere.signalMethod();
}
}
复制代码
打印结果
有 10个线程正在等待con
复制代码
1.10 int hasWaiters(Condition con)
int hasWaiters(Condition con)的作用是查询是否有线程正在等待与此锁定有关的Condition条件。
public class MyHasWaiters {
public ReentrantLock lock = new ReentrantLock();
public Condition con = lock.newCondition();
public void myWait(){
try{
lock.lock();
con.await();
} catch (InterruptedException e) {
e.printStackTrace();
}finally {
lock.unlock();
}
}
public void hasWaits(){
try{
lock.lock();
System.out.println("是否有线程在等待con : " + lock.hasWaiters(con));
}finally {
lock.unlock();
}
}
public static void main(String[] args) throws InterruptedException {
MyHasWaiters myHasWaiters = new MyHasWaiters();
new Thread(new Runnable() {
@Override
public void run() {
myHasWaiters.myWait();
}
}).start();
Thread.sleep(2000);
myHasWaiters.hasWaits();
}
}
复制代码
打印结果
是否有线程在等待con : true
复制代码
1.11 lockInterruptibly()方法
void lockInterruptibly()方法的作用是,如果当前线程未被中断,则获取锁定,已被中断则抛异常。
public class MyLockInterr {
public ReentrantLock lock = new ReentrantLock();
public void waitMethod(){
try{
lock.lockInterruptibly();
System.out.println("lock begin " + Thread.currentThread().getName());
for (int i = 0;i< Integer.MAX_VALUE;i++){new String();}
System.out.println("lock end " + Thread.currentThread().getName());
} catch (InterruptedException e) {
e.printStackTrace();
} finally {
if (lock.isHeldByCurrentThread()){
lock.unlock();
}
}
}
public static void main(String[] args) throws InterruptedException {
MyLockInterr myLockInterr = new MyLockInterr();
Runnable runnable = new Runnable() {
@Override
public void run() {
myLockInterr.waitMethod();
}
};
Thread aaa = new Thread(runnable, "aaa");
Thread bbb = new Thread(runnable, "bbb");
aaa.start();
Thread.sleep(500);
bbb.start();
bbb.interrupt();
System.out.println("main end");
}
}
复制代码
打印结果
lock begin aaa
lock end aaa
java.lang.InterruptedException
main end
at java.util.concurrent.locks.AbstractQueuedSynchronizer.acquireInterruptibly(AbstractQueuedSynchronizer.java:1220)
at java.util.concurrent.locks.ReentrantLock.lockInterruptibly(ReentrantLock.java:335)
at com.wtj.lock.MyLockInterr.waitMethod(MyLockInterr.java:16)
at com.wtj.lock.MyLockInterr$1.run(MyLockInterr.java:34)
at java.lang.Thread.run(Thread.java:748)
复制代码
若将lock.lockInterruptibly()改为lock.lock()则不会报错。
2 Condition 实现等待/通知
synchronization,wait()和notfiy()/notifyAll()方法结合可以实现等待/通知模式,类ReentrantLock也可以实现同样的功能,但需要借助Condition对象,Condition有更好的灵活性,如实现多路通知等功能,也就是在一个lock对象中可以创建多个Condition(即对象监视器)实例,线程对象可以注册在指定的Condition中,从而可以有选择的进行线程通知,在调度上更加灵活。
而synchronization相当于整个Lock对象中只有一个单一的Condition实例,所有的线程都注册在这一个实例上。
使用Condition类中的await()方法使线程进行等待,同时释放当前锁。使用signal()方法唤醒。
使用Condition实例方法前需要先保持锁定。Lock.lock()
使用多个Condition实现通知部分线程
public class MyCondition {
private ReentrantLock lock = new ReentrantLock();
private Condition con1 = lock.newCondition();
private Condition con2 = lock.newCondition();
public void await1(){
try{
lock.lock();
System.out.println("await1 start : "+ System.currentTimeMillis() + " " + Thread.currentThread().getName());
con1.await();
System.out.println("await1 end : "+ System.currentTimeMillis() + " " + Thread.currentThread().getName());
} catch (InterruptedException e) {
e.printStackTrace();
}finally {
lock.unlock();
}
}
public void await2(){
try{
lock.lock();
System.out.println("await2 start : "+ System.currentTimeMillis() + " " + Thread.currentThread().getName());
con2.await();
System.out.println("await2 end : "+ System.currentTimeMillis() + " " + Thread.currentThread().getName());
} catch (InterruptedException e) {
e.printStackTrace();
}finally {
lock.unlock();
}
}
public void signalAll_1(){
try{
lock.lock();
System.out.println("signalAll_1 start : "+ System.currentTimeMillis() + " " + Thread.currentThread().getName());
con1.signalAll();
}finally {
lock.unlock();
}
}
public void signalAll_2(){
try{
lock.lock();
System.out.println("signalAll_2 start : "+ System.currentTimeMillis() + " " + Thread.currentThread().getName());
con2.signalAll();
}finally {
lock.unlock();
}
}
public static void main(String[] args) throws InterruptedException {
MyCondition myCondition = new MyCondition();
new Thread(new Runnable() {
@Override
public void run() {
myCondition.await1();
}
}, "aaa").start();
new Thread(new Runnable() {
@Override
public void run() {
myCondition.await2();
}
},"bbb").start();
Thread.sleep(3000);
myCondition.signalAll_1();
}
}
复制代码
打印结果
await1 start : 1560238945661 aaa
await2 start : 1560238945661 bbb
signalAll_1 start : 1560238948666 main
await1 end : 1560238948666 aaa
复制代码
从输出结果看,只有线程aaa被唤醒。
3 ReentrantReadWriteLock类
类ReentrantLock具有完全互斥排他的效果,即同一时间只有一个线程在执行lock()方法后的任务。这样虽然能保证线程安全,但是效率非常低。
JDK中提供了一种ReentrantReadWriteLock类,使用它可以加快运行效率,在某些不需要操作实例变量的方法中,可以使用读写锁ReentrantReadWriteLock类提升效率。
读写锁ReentrantReadWriteLock中有两个锁,一个是读操作相关的锁,也称共享锁;一个是写操作相关的锁,也称排他锁。多个读锁之间不互斥,读锁与写锁互斥,写锁与写锁互斥。
3.1 读读共享
public class MyReadLock {
private ReentrantReadWriteLock lock = new ReentrantReadWriteLock();
public void read(){
try{
lock.readLock().lock(); //读锁锁定
System.out.println("获取读锁" + Thread.currentThread().getName()+ " "+ System.currentTimeMillis());
Thread.sleep(4000);
} catch (InterruptedException e) {
e.printStackTrace();
} finally {
lock.readLock().unlock();
}
}
public static void main(String[] args) {
MyReadLock myReadLock = new MyReadLock();
new Thread(new Runnable() {
@Override
public void run() {
myReadLock.read();
}
},"aaa").start();
new Thread(new Runnable() {
@Override
public void run() {
myReadLock.read();
}
},"bbb").start();
}
}
复制代码
打印结果
获取读锁aaa 1560241121534
获取读锁bbb 1560241121534
复制代码
从结果可以看出两个线程是同时进入lock()方法后的代码,说明读锁允许多个线程同时执行lock()方法后的代码。
3.2 写写互斥
public class MyWriteLock {
private ReentrantReadWriteLock lock = new ReentrantReadWriteLock();
public void write(){
try{
lock.writeLock().lock(); //写锁锁定
System.out.println("获得写锁"+ Thread.currentThread().getName() + " " + System.currentTimeMillis());
Thread.sleep(4000);
} catch (InterruptedException e) {
e.printStackTrace();
} finally {
lock.writeLock().unlock();
}
}
public static void main(String[] args) {
MyWriteLock myWriteLock = new MyWriteLock();
new Thread(new Runnable() {
@Override
public void run() {
myWriteLock.write();
}
},"aaa").start();
new Thread(new Runnable() {
@Override
public void run() {
myWriteLock.write();
}
},"bbb").start();
}
}
复制代码
打印结果
获得写锁aaa 1560241405832
获得写锁bbb 1560241409834
复制代码
可以看到,在aaa线程执行4秒后bbb线程才进入,说明写锁同一时间只允许一个线程执行lock()后的方法。
3.3 读锁互斥
public class MyReadWriteLock {
public ReentrantReadWriteLock lock = new ReentrantReadWriteLock();
public void read(){
try{
lock.readLock().lock();
System.out.println("获取读锁 " + Thread.currentThread().getName() + " " + System.currentTimeMillis());
Thread.sleep(4000);
} catch (InterruptedException e) {
e.printStackTrace();
} finally {
lock.readLock().unlock();
}
}
public void write(){
try{
lock.writeLock().lock();
System.out.println("获取写锁 " + Thread.currentThread().getName() + " " + System.currentTimeMillis());
Thread.sleep(4000);
} catch (InterruptedException e) {
e.printStackTrace();
} finally {
lock.writeLock().unlock();
}
}
public static void main(String[] args) {
MyReadWriteLock myReadWriteLock = new MyReadWriteLock();
new Thread(new Runnable() {
@Override
public void run() {
myReadWriteLock.read();
}
},"aaa").start();
new Thread(new Runnable() {
@Override
public void run() {
myReadWriteLock.write();
}
},"bbb").start();
}
}
复制代码
打印结果
获取读锁 aaa 1560241741764
获取写锁 bbb 1560241745770
复制代码