概述:
在java多线程当中,我们知道可以使用synchronized关键字来实现线程间的同步互斥工作,那么其实还有一个更加优秀的机制去完成这个“同步互斥”工作,他就是Lock对象,我们主要学习两种锁,重入锁(ReentrantLock)和读写锁(ReentrantReadWriteLock)。他们具有比synchronize更为强大的功能,并且有嗅探锁定、多路分支等功能。
实现:
首先最大的不同:synchronized是基于JVM层面实现的,而Lock是基于JDK层面实现的。曾经反复的找过synchronized的实现,可惜最终无果。但Lock却是基于JDK实现的,我们可以通过阅读JDK的源码来理解Lock的实现。
使用:
对于使用者的直观体验上Lock是比较复杂的,需要lock和unlock,如果忘记释放锁就会产生死锁的问题,所以,通常需要在finally中进行锁的释放。但是synchronized的使用十分简单,只需要对自己的方法或者关注的同步对象或类使用synchronized关键字即可。但是对于锁的粒度控制比较粗,同时对于实现一些锁的状态的转移比较困难。例如:
特点:
tips | synchronized | Lock |
---|---|---|
锁获取超时 | 不支持 | 支持 |
获取锁响应中断 | 不支持 | 支持 |
代码如下:
package com.bjsxt.height.lock020;
import java.util.concurrent.CopyOnWriteArrayList;
import java.util.concurrent.locks.Lock;
import java.util.concurrent.locks.ReentrantLock;
public class UseReentrantLock {
private Lock lock = new ReentrantLock();
public void method1(){
try {
lock.lock();
System.out.println("当前线程:" + Thread.currentThread().getName() + "进入method1..");
Thread.sleep(1000);
System.out.println("当前线程:" + Thread.currentThread().getName() + "退出method1..");
Thread.sleep(1000);
} catch (InterruptedException e) {
e.printStackTrace();
} finally {
lock.unlock();
}
}
public void method2(){
try {
lock.lock();
System.out.println("当前线程:" + Thread.currentThread().getName() + "进入method2..");
Thread.sleep(2000);
System.out.println("当前线程:" + Thread.currentThread().getName() + "退出method2..");
Thread.sleep(1000);
} catch (InterruptedException e) {
e.printStackTrace();
} finally {
lock.unlock();
}
}
public static void main(String[] args) {
final UseReentrantLock ur = new UseReentrantLock();
Thread t1 = new Thread(new Runnable() {
@Override
public void run() {
ur.method1();
ur.method2();
}
}, "t1");
t1.start();
try {
Thread.sleep(10);
} catch (InterruptedException e) {
e.printStackTrace();
}
//System.out.println(ur.lock.getQueueLength());
}
}
运行结果:
当前线程:t1进入method1.。。
当前线程:t1退出method1.。。
当前线程:t1退出method2.。。
当前线程:t1退出method2.。。
2.锁与等待/通知
还记得在使用synchronize的时候,如果需要多线程间进行协作工作则需要Object的wait()和notify()、notifyAll()方法进行配合工作
那么同样,我们在使用Lock的时候,可以使用一个新的等待、通知的类,他就是Condition。这个Condition一定是针对具体某一把锁的。也就是在只有锁的基础之上才会产生Condition。
package com.bjsxt.height.lock020; import java.util.concurrent.CopyOnWriteArrayList; import java.util.concurrent.locks.Condition; import java.util.concurrent.locks.Lock; import java.util.concurrent.locks.ReentrantLock; public class UseCondition { private Lock lock = new ReentrantLock(); private Condition condition = lock.newCondition(); public void method1(){ try { lock.lock(); System.out.println("当前线程:" + Thread.currentThread().getName() + "进入等待状态.."); Thread.sleep(3000); System.out.println("当前线程:" + Thread.currentThread().getName() + "释放锁.."); condition.await(); // await方法是用于释放锁的。 System.out.println("当前线程:" + Thread.currentThread().getName() +"继续执行..."); } catch (Exception e) { e.printStackTrace(); } finally { lock.unlock(); } } public void method2(){ try { lock.lock(); System.out.println("当前线程:" + Thread.currentThread().getName() + "进入.."); Thread.sleep(3000); System.out.println("当前线程:" + Thread.currentThread().getName() + "发出唤醒.."); condition.signal(); //Object notify } catch (Exception e) { e.printStackTrace(); } finally { lock.unlock(); } } public static void main(String[] args) { final UseCondition uc = new UseCondition(); Thread t1 = new Thread(new Runnable() { @Override public void run() { uc.method1(); } }, "t1"); Thread t2 = new Thread(new Runnable() { @Override public void run() { uc.method2(); } }, "t2"); t1.start(); t2.start(); } }
运行结果:
当前线程:t1进入等待状态。。
当前线程:t1释放锁。。
当前线程:t2进入。。
当前线程:t2发出唤醒。。
当前线程:t1继续执行。。
多Condition
我们可以通过一个Lock对象产生多个Condition进行多线程间的交互,非常的灵活。可以使得部分需要唤醒的线程唤醒,其他线程则需要继续等待通知。
package com.bjsxt.height.lock020; import java.util.concurrent.locks.Condition; import java.util.concurrent.locks.Lock; import java.util.concurrent.locks.ReentrantLock; public class UseManyCondition { private ReentrantLock lock = new ReentrantLock(); private Condition c1 = lock.newCondition(); private Condition c2 = lock.newCondition(); public void m1(){ try { lock.lock(); System.out.println("当前线程:" +Thread.currentThread().getName() + "进入方法m1等待.."); c1.await(); System.out.println("当前线程:" +Thread.currentThread().getName() + "方法m1继续.."); } catch (Exception e) { e.printStackTrace(); } finally { lock.unlock(); } } public void m2(){ try { lock.lock(); System.out.println("当前线程:" +Thread.currentThread().getName() + "进入方法m2等待.."); c1.await(); System.out.println("当前线程:" +Thread.currentThread().getName() + "方法m2继续.."); } catch (Exception e) { e.printStackTrace(); } finally { lock.unlock(); } } public void m3(){ try { lock.lock(); System.out.println("当前线程:" +Thread.currentThread().getName() + "进入方法m3等待.."); c2.await(); System.out.println("当前线程:" +Thread.currentThread().getName() + "方法m3继续.."); } catch (Exception e) { e.printStackTrace(); } finally { lock.unlock(); } } public void m4(){ try { lock.lock(); System.out.println("当前线程:" +Thread.currentThread().getName() + "唤醒.."); c1.signalAll(); } catch (Exception e) { e.printStackTrace(); } finally { lock.unlock(); } } public void m5(){ try { lock.lock(); System.out.println("当前线程:" +Thread.currentThread().getName() + "唤醒.."); c2.signal(); } catch (Exception e) { e.printStackTrace(); } finally { lock.unlock(); } } public static void main(String[] args) { final UseManyCondition umc = new UseManyCondition(); Thread t1 = new Thread(new Runnable() { @Override public void run() { umc.m1(); } },"t1"); Thread t2 = new Thread(new Runnable() { @Override public void run() { umc.m2(); } },"t2"); Thread t3 = new Thread(new Runnable() { @Override public void run() { umc.m3(); } },"t3"); Thread t4 = new Thread(new Runnable() { @Override public void run() { umc.m4(); } },"t4"); Thread t5 = new Thread(new Runnable() { @Override public void run() { umc.m5(); } },"t5"); t1.start(); // c1 t2.start(); // c1 t3.start(); // c2 try { Thread.sleep(2000); } catch (InterruptedException e) { e.printStackTrace(); } t4.start(); // c1 try { Thread.sleep(2000); } catch (InterruptedException e) { e.printStackTrace(); } t5.start(); // c2 } }
运行结果:
当前线程:t进入方法m1等待。。
当前线程:t2进入方法m2等待。。
当前线程:t3进入方法m3等待。。
当前线程:t4唤醒。。
当前线程:t1方法m1继续。。
当前线程:t2方法m2继续。。
当前线程:t5唤醒。。
当前线程:t3方法m3继续。。
公平锁与非公平锁
非公平锁实现
在非公平锁中,每当线程执行lock方法时,都尝试利用CAS把state从0设置为1。
那么Doug lea是如何实现锁的非公平性呢?
我们假设这样一个场景:
- 持有锁的线程A正在running,队列中有线程BCDEF被挂起并等待被唤醒;
- 在某一个时间点,线程A执行unlock,唤醒线程B;
- 同时线程G执行lock,这个时候会发生什么?线程B和G拥有相同的优先级,这里讲的优先级是指获取锁的优先级,同时执行CAS指令竞争锁。如果恰好线程G成功了,线程B就得重新挂起等待被唤醒。
通过上述场景描述,我们可以看书,即使线程B等了很长时间也得和新来的线程G同时竞争锁,如此的不公平。
公平锁
在公平锁中,每当线程执行lock方法时,如果同步器的队列中有线程在等待,则直接加入到队列中。
场景分析:
- 持有锁的线程A正在running,对列中有线程BCDEF被挂起并等待被唤醒;
- 线程G执行lock,队列中有线程BCDEF在等待,线程G直接加入到队列的对尾。
所以每个线程获取锁的过程是公平的,等待时间最长的会最先被唤醒获取锁。
默认是非公平锁的,因为它的内部没有实现队列排序,所以它的性能比公平锁高。
ReentrantReadWriteLock(读写锁)
读写锁,其核心就是实现读写分离的锁。在高并发访问下,尤其是读多写少的情况下,性能要远高于重入锁。
之前学Synchronized、ReentrantLock时,我们知道,同一时间内,只能有一个线程进行访问被锁定的代码,那么读写锁则不同,其本质是分成两个锁,即读锁、写锁。在读锁下,多个线程可以并发的进行访问,但是在写锁的时候,只能一个一个的顺序访问。
package com.bjsxt.height.lock021; import java.util.concurrent.locks.ReentrantReadWriteLock; import java.util.concurrent.locks.ReentrantReadWriteLock.ReadLock; import java.util.concurrent.locks.ReentrantReadWriteLock.WriteLock; public class UseReentrantReadWriteLock { private ReentrantReadWriteLock rwLock = new ReentrantReadWriteLock(); private ReadLock readLock = rwLock.readLock(); private WriteLock writeLock = rwLock.writeLock(); public void read(){ try { readLock.lock(); System.out.println("当前线程:" + Thread.currentThread().getName() + "进入..."); Thread.sleep(3000); System.out.println("当前线程:" + Thread.currentThread().getName() + "退出..."); } catch (Exception e) { e.printStackTrace(); } finally { readLock.unlock(); } } public void write(){ try { writeLock.lock(); System.out.println("当前线程:" + Thread.currentThread().getName() + "进入..."); Thread.sleep(3000); System.out.println("当前线程:" + Thread.currentThread().getName() + "退出..."); } catch (Exception e) { e.printStackTrace(); } finally { writeLock.unlock(); } } public static void main(String[] args) { final UseReentrantReadWriteLock urrw = new UseReentrantReadWriteLock(); Thread t1 = new Thread(new Runnable() { @Override public void run() { urrw.read(); } }, "t1"); Thread t2 = new Thread(new Runnable() { @Override public void run() { urrw.read(); } }, "t2"); Thread t3 = new Thread(new Runnable() { @Override public void run() { urrw.write(); } }, "t3"); Thread t4 = new Thread(new Runnable() { @Override public void run() { urrw.write(); } }, "t4"); //1。 t1.start(); // t2.start(); //2. t1.start(); // R // t3.start(); // W } }
运行1时,结果:(两个读操作是并发执行的。两个写操作也是并发执行)
当前线程;t1进入。。
当前线程:t2进入。。
当前线程:t1退出。。
当前线程:t2退出。。
运行2时,结果:(t1是读操作,t3是写操作,因为这两个线程共用一把锁,但是这两个操作是互斥的,必须read执行完才会执行write)
当前线程:t1进入。。
当前线程:t1退出。。
当前线程:t2进入。。
当前线程:t2退出。。