思维导图:
1 ReentrantLock重入锁
和关键字synchronization相比,重入锁有着显示的操作过程。需要手动指定何时加锁,何时释放锁。重入锁对逻辑控制的灵活性要远远优于关键字synchronization。
在退出时,必须记得释放锁,否则其他线程就没有机会访问。
重入锁之所以叫重入锁,是因为这种锁能反复进入。但是只限于一个线程。font color=red 可重入就是说某个线程已经获得某个锁,可以再次获取锁而不会出现死锁
//锁定几个就要释放几个
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 isFair()方法
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方法
2.1awaitUniterruptibly(等待可中断)
造成当前线程在接到信号之前一直处于等待状态,我们知道线程在调用await()处于等待的状态,此时调用thread.interrupt()会报错,但是awaitUniterruptibly不会报错
2.2awaitUntil(time)(等待自动唤醒)
造成当前线程在接收到信号,被中断或者到达指定最后期限之前一直处于等待状态。
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
链接:https://juejin.cn/post/6844903869806460942