ReentranLock 重入锁

重入锁可以完全替代关键字synchronize。在jdk5.0的早期版本中,重入锁的性能远超于关键字synchronize,但是在jdk6.0开始,jdk在关键字synchronize上做了大量的优化,似的两者间的差距并不是很大。

1.中断响应

对于关键字来说,如果一个线程在等待锁,那么结果只有两种情况,要么获得锁继续执行,要么保持等待。而使用重入锁,则提供了另外一种可能,就是线程可以中断。

代码示例:

package com.gpdi.operatingunit.test;


import java.util.concurrent.locks.ReentrantLock;

/**
 * @author Lxq
 * @date 2020/2/11 11:44
 * @describtion
 **/
public class IntLock implements Runnable{

    public static ReentrantLock lock1 = new ReentrantLock();
    public static ReentrantLock lock2 = new ReentrantLock();
    int lock;

    /**
     * 加锁控制顺序,方便构成死锁
     * @param lock
     */
    public IntLock(int lock) {
        this.lock = lock;
    }

    @Override
    public void run() {
        try {
            if (lock == 1){
                lock1.lockInterruptibly();
                try {
                    Thread.sleep(500);
                }catch (InterruptedException e){}
                lock2.lockInterruptibly();
            }else {
                lock2.lockInterruptibly();
                try {
                    Thread.sleep(500);
                }catch (InterruptedException e){}
                lock1.lockInterruptibly();
            }

        }catch (InterruptedException e){
            e.printStackTrace();
        }finally {
            // lock.isHeldByCurrentThread()的作用是查询当前线程是否保持此锁定
            if (lock1.isHeldByCurrentThread()){
                System.out.println("lock1" + lock1.isHeldByCurrentThread());
                lock1.unlock();
            }
            if (lock2.isHeldByCurrentThread()){
                System.out.println("lock2" + lock2.isHeldByCurrentThread());
                lock2.unlock();
            }
            System.out.println(Thread.currentThread() + "线程退出");
        }

    }

    public static void main(String[] args) throws InterruptedException {
        IntLock r1 = new IntLock(1);
        IntLock r2 = new IntLock(2);
        Thread t1 = new Thread(r1);
        Thread t2 = new Thread(r2);
        t1.start();
        t2.start();
        Thread.sleep(1000);
        t2.interrupt();
    }
}
java.lang.InterruptedException
	at java.base/java.util.concurrent.locks.AbstractQueuedSynchronizer.doAcquireInterruptibly(AbstractQueuedSynchronizer.java:944)
	at java.base/java.util.concurrent.locks.AbstractQueuedSynchronizer.acquireInterruptibly(AbstractQueuedSynchronizer.java:1263)
	at java.base/java.util.concurrent.locks.ReentrantLock.lockInterruptibly(ReentrantLock.java:317)
	at com.gpdi.operatingunit.test.IntLock.run(IntLock.java:40)
	at java.base/java.lang.Thread.run(Thread.java:834)
lock2true
lock1true
lock2true
Thread[Thread-1,5,main]线程退出
Thread[Thread-0,5,main]线程退出

线程t1和线程t2启动后,t1先占用lock1,再占用lock2;t2先占用lock2,再请求lock1.因此形成了t1和t2之前互相等待,对锁的请求统一使用lockInterruptibly(),这是一个可以中断响应的锁申请动作,即再等待的过程中,可以响应中断。

再main线程 处于休眠状态,此时,两个线程都处于死锁的状态,当main 线程中t2线程中断,故t2放弃对lock1的申请,同时释放了lock2.这个操作导致t1的线程可以顺利得到lock2而继续执行下去。

2.锁申请等待限时

除了等待外部的通知之外,要避免死锁还用另外一种方法,就是限时等待。如果给定一个时间,让线程自动放弃,那么对于系统来说是有意义的,我们使用tryLock()方法进行一次限时等待。

代码示例:

package com.gpdi.operatingunit.test;

import java.util.concurrent.TimeUnit;
import java.util.concurrent.locks.ReentrantLock;

/**
 * @author Lxq
 * @date 2020/2/11 12:19
 * @describtion
 **/
public class TimeLock implements Runnable {

    public static ReentrantLock lock = new ReentrantLock();

    @Override
    public void run() {
        try {
            if (lock.tryLock(5, TimeUnit.SECONDS)){
                Thread.sleep(6000);
            }else {
                System.out.println("get lock failed");
            }
        }catch (InterruptedException e){
            e.printStackTrace();
        }finally {
            if (lock.isHeldByCurrentThread()){
                lock.unlock();
            }
        }
    }

    public static void main(String[] args) {
        TimeLock lock = new TimeLock();
        Thread t1 = new Thread(lock);
        Thread t2 = new Thread(lock);
        t1.start();
        t2.start();
    }
}
get lock failed

tryLock 接受两个参数,一个表示等待时长,一个表示计时单位,这里的单位是秒,时长为5,表示线程在请求锁的最多时长为5秒,如果超过了5秒还没获取锁,就会返回false,如果成功获取锁,则返回true。

ReentrantLock.tryLock() 方法也可以不带参数直接运行。在这种情况下,当前线程会尝试获取锁,如果锁并未被其他线程占用,则申请锁成功,并返回true。如果锁被其他线程占用,则当前线程不会继续等待,而是立即放回false,这种模式下不会引起线程等待,因此也不会产生死锁。

代码示例:

package com.gpdi.operatingunit.test;

import java.util.concurrent.locks.ReentrantLock;

/**
 * @author Lxq
 * @date 2020/2/11 12:34
 * @describtion
 **/
public class TryLock implements Runnable {

    public static ReentrantLock lock1 = new ReentrantLock();
    public static ReentrantLock lock2 = new ReentrantLock();

    int lock;

    public TryLock(int lock) {
        this.lock = lock;
    }

    @Override
    public void run() {
        if (lock == 1) {
            while (true) {
                try {
                    if (lock1.tryLock()) {
                        try {
                            Thread.sleep(500);
                        } catch (InterruptedException e) {
                            e.printStackTrace();
                        }
                        if (lock2.tryLock()) {
                            try {
                                System.out.println(Thread.currentThread() + " my job done");
                                return;
                            }finally {
                                lock2.unlock();
                            }
                        }
                    }
                } finally {
                    lock1.unlock();
                }
            }
        }else {
            while (true) {
                try {
                    if (lock2.tryLock()) {
                        try {
                            Thread.sleep(500);
                        } catch (InterruptedException e) {
                            e.printStackTrace();
                        }
                        if (lock1.tryLock()) {
                            try {
                                System.out.println(Thread.currentThread() + " my job done");
                                return;
                            }finally {
                                lock1.unlock();
                            }
                        }
                    }
                } finally {
                    lock2.unlock();
                }
            }
        }
    }

    public static void main(String[] args) {
        TryLock r1 = new TryLock(1);
        TryLock r2 = new TryLock(2);
        Thread t1 = new Thread(r1);
        Thread t2 = new Thread(r2);
        t1.start();
        t2.start();
    }
}

上面代码很容易产生死锁,但是使用了tryLock()方法后,这种情况就大大改善。由于线程不会傻傻等待,而是不停尝试,因此只要有足够长的时间,线程最终会获得所需要的资源,从而正常执行。

3.公平锁

大多数情况下,锁的申请都是非公平的。如果我们使用synchronize关键字进行控制的话,那么产生的锁就是非公平锁。而重入锁允许我们对其公平性进行设置。构造函数如下:

 public ReentrantLock(boolean fair) {
        this.sync = (ReentrantLock.Sync)(fair ? new ReentrantLock.FairSync() : new ReentrantLock.NonfairSync());
    }

 

当参数为fair为true时,表示锁时公平的。公平锁看起来很优美,但是要实现公平锁必然要维护一个有序的队列,因此公平锁实现的成本比较高,性能却非常低下,在默认的情况下,锁时非公平的。如果没有特殊的要求,则不要使用公平锁。

代码示例:

package com.gpdi.operatingunit.test;

import org.apache.poi.ss.formula.functions.T;

import java.util.concurrent.locks.ReentrantLock;

/**
 * @author Lxq
 * @date 2020/2/11 12:58
 * @describtion
 **/
public class FairLock implements Runnable{
    public static ReentrantLock lock = new ReentrantLock(true);

    @Override
    public void run() {
        while (true){
            try {
                lock.lock();
                System.out.println(Thread.currentThread().getName() + " 获取锁");
            }finally {
                lock.unlock();
            }
        }
    }

    public static void main(String[] args) {
        FairLock r1 = new FairLock();
        Thread t1 = new Thread(r1,"Thread_t1");
        Thread t2 = new Thread(r1,"Thread_t2");
        t1.start();
        t2.start();
    }
}

总结:

  • lock()获取锁,如果锁被占用,则等待。
  • lockInterruptibly():获取锁,优先响应中断。
  • tryLock()获取锁,如果成功,则返回true,失败返回false。该方法不等待,立即返回。
  • tryLock(long time,TimeUnit unit)给定时间内尝试获取锁。
  • unlock()释放锁

 

 

 

 

 

 

 

 

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值