听说你不会Lock,我发了3个夜晚写给你

我们知道 synchronized 是java内部关键字,比较重量级的独占锁,好处就是使用方便,不需要手动释放锁;然而

Lock 则需要手动加锁,手动释放锁;

一ReentrantLock使用

ReentrantLock 意为可重入锁,方法预览如下

//创建一个 ReentrantLock 的实例
ReentrantLock() 
 //创建一个具有给定公平策略的 ReentrantLock     
ReentrantLock(boolean fair)
//查询当前线程持有锁的个数
int getHoldCount() 
//返回目前拥有此锁的线程,如果此锁不被任何线程拥有,则返回 null     
protected Thread getOwner() 
//返回一个collection,它包含可能正等待获取此锁的线程     
protected Collection<Thread> getQueuedThreads() 
int getQueueLength() //返回正等待获取此锁的线程估计数 
 //返回一个 collection,它包含可能正在等待与此锁相关给定条件的那些线程     
protected Collection<Thread> getWaitingThreads(Condition condition)
//返回等待与此锁相关的给定条件的线程估计数    
int getWaitQueueLength(Condition condition) 
//查询给定线程是否正在等待获取此锁    
boolean hasQueuedThread(Thread thread) 
//查询是否有些线程正在等待获取此锁    
boolean hasQueuedThreads() 
//查询是否有些线程正在等待与此锁有关的给定条件    
boolean hasWaiters(Condition condition) 
//如果此锁的公平设置为 true,则返回true 
boolean isFair() 
//查询当前线程是否保持此锁    
boolean isHeldByCurrentThread()
//查询此锁是否由任意线程保持    
boolean isLocked()
//获取锁    
void lock()
//如果当前线程未被中断,则获取锁。    
void lockInterruptibly() 
//返回用来与此 Lock 实例一起使用的 Condition 实例     
Condition newCondition() 
 //仅在调用时锁未被另一个线程保持的情况下,才获取该锁    
boolean tryLock()
//如果锁在给定等待时间内没有被另一个线程保持,且当前线程未被中断,则获取该锁 
boolean tryLock(long timeout, TimeUnit unit)
//释放此锁
void unlock() 

1.1Lock与unLock 简单使用

使用 lock() 加锁, unlock()释放锁;

public class RLook {

    private Lock lock = new ReentrantLock();

    private void testLock(){
        // 加锁
        lock.lock();
        // 执行业务逻辑
        for (int i=0; i<8; i++){
            System.out.println(i+"==="+Thread.currentThread().getName());
        }
        // 释放锁
        lock.unlock();
    }

    public static void main(String[] args) {
        // 线程1
        RLook rLook = new RLook();
        new Thread(()-> {
            rLook.testLock();
        }).start();
        // 线程2 
        new Thread(()-> {
            rLook.testLock();
        }).start();
    }
}

输出如下,不同线程之间应是分组打印;

0===Thread-0
1===Thread-0
2===Thread-0
3===Thread-0
4===Thread-0
5===Thread-0
6===Thread-0
7===Thread-0
0===Thread-1
1===Thread-1
2===Thread-1
3===Thread-1
4===Thread-1
5===Thread-1
6===Thread-1
7===Thread-1

1.2Lock与unLock 正确使用方式

上面的代码有个缺点,如果在执行业务代码的时候发生了异常就会发生死锁的现象,所以通常情况下我们会将业务代码放在try{},catch{} 代码块中, 最后使用 finally 释放锁;

代码格式应如下

Lock lock =new ReentrantLock();
....
lock.lock();
try{
    //处理任务
}catch(Exception ex){
     
}finally{
    lock.unlock();   //释放锁
}
....

1.3源码角度说明Lock

public interface Lock {
	// 加锁
    void lock();
    // 中断
    void lockInterruptibly() throws InterruptedException;
    // 尝试获取锁
    boolean tryLock();
    // 尝试获取锁
    boolean tryLock(long time, TimeUnit unit) throws InterruptedException;
    // 释放锁
    void unlock();
    // 信号通知
    Condition newCondition();
}
  • lock()方法是用来获取锁。如果锁已被其他线程获取,则进行锁等待。采用Lock必须手动释放锁,并且在发生异常时,不会自动释放锁,所以需要放在try,cath代码块中, 最后使用 finally 释放锁;

  • tryLock()方法用来表示尝试获取锁,如果获取成功,则返回true,如果获取失败,则返回false;

  • tryLock(long time, TimeUnit unit)方法和tryLock()方法是类似的,区别在于可以设置锁等待时间,在等待时间内为获取到锁,则返回false,获取到则返回true;

Lock lock=new ReentrantLock();
....
if(lock.tryLock()) {
     try{
         //业务逻辑
     }catch(Exception ex){
         
     }finally{
     //释放锁
         lock.unlock();   
     } 
}else {
    // 未获取锁逻辑
}
  • lockInterruptibly()为中断方法,当通过这个方法去获取锁时,如果线程正在处于获取锁状态,则该线程能够响应中断(中断线程的等待状态),抛出中断异常。也就使说,当两个线程同时通过lock.lockInterruptibly()获取某个锁时,如果线程A获取到了锁,而线程B在等待状态,那么对线程B调用threadB.interrupt()方法能够中断线程B的等待状态;
  • Condition类是JDK5的新API,可以实现多路通知功能,一个Lock对象可以创建多个Condition, 通过注入不同的Condition灵活实现不同的线程通知功能;然而synchronized 的 wait(), notify(), notify All() 通知机制是随机无法实现选择性通知功能;

1.4使用lockInterruptibly

public class InterruptTest {

    private Lock lock = new ReentrantLock();

    public void interrupt() throws InterruptedException {
        lock.lockInterruptibly();
        try {
            System.out.println(Thread.currentThread().getName() + "得到了锁");
            long startTime = System.currentTimeMillis();

            for (; ; ) {
                if (System.currentTimeMillis() - startTime >= Integer.MAX_VALUE)
                    break;
            }
        } finally {
            lock.unlock();
            System.out.println(Thread.currentThread().getName() + "释放锁");
        }
    }

    public static void main(String[] args) {
        // 线程1
        InterruptTest rLook = new InterruptTest();
        Thread threadA = new Thread(() -> {
            try {
                rLook.interrupt();
            } catch (InterruptedException e) {
                System.out.println("线程A进行了中断");
                e.printStackTrace();
            }
        });
        // 线程2
        Thread threadB = new Thread(() -> {
            try {
                rLook.interrupt();
            } catch (InterruptedException e) {
                System.out.println("线程B进行了中断");
                e.printStackTrace();
            }
        });
        threadA.setName("A");
        threadB.setName("B");
        threadA.start();
        threadB.start();
        try {
            Thread.sleep(2000);
        } catch (InterruptedException e) {
            e.printStackTrace();
        }
        threadB.interrupt();
    }
}

输出结果如下,线程A通过中断获取锁,线程B再通过中断获取锁时处于等待状态,直接中断抛出异常;

A得到了锁
线程B进行了中断

1.5使用Condition实现等待通知机制

codition 接口主要方法如下

 //造成当前线程在接到信号或被中断之前一直处于等待状态。 
 void await() 
 //造成当前线程在接到信号、被中断或到达指定等待时间之前一直处于等待状态。 
 boolean await(long time, TimeUnit unit) 
 //造成当前线程在接到信号、被中断或到达指定等待时间之前一直处于等待状态。 
 long awaitNanos(long nanosTimeout) 
 //造成当前线程在接到信号之前一直处于等待状态。 
 void awaitUninterruptibly() 
 //造成当前线程在接到信号、被中断或到达指定最后期限之前一直处于等待状态。 
 boolean awaitUntil(Date deadline) 
 //唤醒一个等待线程。
 void signal()  
 //唤醒所有等待线程。
 void signalAll()  

利用 Condition 的await() 方法和 signal() 方法 实现 等待通知机制;

public class ConditionTest {

    private Lock lock = new ReentrantLock();

    private Condition condition = lock.newCondition();

    // 线程等待
    private void await(){
        try {
            // 加锁
            lock.lock();
            System.out.println("await时间为:"+System.currentTimeMillis());
            // 等待
            condition.await();
        }catch (Exception e){
            e.printStackTrace();
        }finally {
            // 释放锁
            lock.unlock();
        }
    }

    // 线程唤醒
    public void signal() {
        lock.lock();
        try {
            System.out.println("signal时间为" + System.currentTimeMillis());
            condition.signal();
        } catch (Exception e) {
            e.printStackTrace();
        } finally {
            lock.unlock();
        }
    }

    public static void main(String[] args) throws InterruptedException {
        // 线程1
        ConditionTest rLook = new ConditionTest();
        Thread thread = new Thread(() -> {
            rLook.await();
        });
        thread.start();
        //
        thread.sleep(3000);
        //
        rLook.signal();
    }
}

输出输出结果如下

await时间为:1606707478993
signal时间为1606707481993

多个Condition 使用方式

多个Condition 使用时,互不干涉; 线程 A, B 使用 不同Condition 的都 进入等待状态, 当使用 对应的 Condition 的 singal 才唤醒对应的线程;

public class MultiCondition {

    private Lock lock = new ReentrantLock();

    private Condition conditionA = lock.newCondition();
    private Condition conditionB = lock.newCondition();

    // 线程等待
    private void awaitA(){
        try {
            // 加锁
            lock.lock();
            System.out.println("线程"+Thread.currentThread().getName()+"----await时间为:"+System.currentTimeMillis());
            // 等待
            conditionA.await();
            System.out.println("线程"+Thread.currentThread().getName()+"继续执行");
        }catch (Exception e){
            e.printStackTrace();
        }finally {
            // 释放锁
            lock.unlock();
        }
    }

    // 线程等待
    private void awaitB(){
        try {
            // 加锁
            lock.lock();
            System.out.println("线程"+Thread.currentThread().getName()+"----await时间为:"+System.currentTimeMillis());
            // 等待
            conditionB.await();
            System.out.println("线程"+Thread.currentThread().getName()+"继续执行");
        }catch (Exception e){
            e.printStackTrace();
        }finally {
            // 释放锁
            lock.unlock();
        }
    }

    // 线程唤醒
    public void signalAllA() {
        lock.lock();
        try {
            System.out.println("线程"+Thread.currentThread().getName()+"----signal时间为" + System.currentTimeMillis());
            conditionA.signal();
        } catch (Exception e) {
            e.printStackTrace();
        } finally {
            lock.unlock();
        }
    }
    // 线程唤醒
    public void signalAllB() {
        lock.lock();
        try {
            System.out.println("线程"+Thread.currentThread().getName()+"----signal时间为" + System.currentTimeMillis());
            conditionB.signal();
        } catch (Exception e) {
            e.printStackTrace();
        } finally {
            lock.unlock();
        }
    }

    public static void main(String[] args) throws InterruptedException {
        // 线程1
        MultiCondition rLook = new MultiCondition();
        Thread threadA = new Thread(() -> {
            rLook.awaitA();
        });
        threadA.setName("A");
        threadA.start();
        Thread threadB = new Thread(() -> {
            rLook.awaitB();
        });
        threadB.setName("B");
        threadB.start();
        //
        Thread.sleep(3000);
        //
        rLook.signalAllA();
    }
}

输出结果如下,线程Condition A 对应的线程会被唤醒,B 线程 还是处于等待状态;

线程A----await时间为:1606708895650
线程B----await时间为:1606708895650
线程main----signal时间为1606708898651
线程A继续执行

1.6使用Condition实现生产消费模式

public class ConsumerProductCondition {

    private Lock lock = new ReentrantLock();

    private Condition condition = lock.newCondition();

    private Boolean has = false;

    // 生产
    private void set(){
        try {
            // 加锁
            lock.lock();
            while (has == true){
                condition.await();
            }
            System.out.println("生产GG");
            has = true;
            // 唤醒一个线程
            condition.signal();
        }catch (Exception e){
            e.printStackTrace();
        }finally {
            // 释放锁
            lock.unlock();
        }
    }

    // 消费
    private void get(){
        try {
            // 加锁
            lock.lock();
            while (has == false){
                condition.await();
            }
            System.out.println("消费MM");
            has = false;
            // 唤醒一个线程
            condition.signal();
        }catch (Exception e){
            e.printStackTrace();
        }finally {
            // 释放锁
            lock.unlock();
        }
    }

    public static void main(String[] args) throws InterruptedException {
        // 线程1
        ConsumerProductCondition rLook = new ConsumerProductCondition();
        Thread threadA = new Thread(() -> {
            for (int i = 0; i < 200; i++) {
                rLook.set();
            }
        });
       // 线程2
        Thread threadB = new Thread(() -> {
            for (int i = 0; i < 200; i++) {
                rLook.get();
            }
        });
        threadA.start();
        threadB.start();
    }
}

输出交替打印

生产GG
消费MM
生产GG
消费MM
生产GG
消费MM
生产GG
消费MM
生产GG
消费MM
.....

1.7 公平锁与非公平锁

Lock 锁分为公平锁和非公平锁,公平锁表示通过线程加锁的顺序获取锁,非公平锁表示通过抢占机制获取锁;

公平锁获示例

public class FairLockTest {


    private Lock lock = new ReentrantLock(true);

    private void testLock(){
        // 加锁
        lock.lock();
        // 执行业务逻辑
        System.out.println("线程"+Thread.currentThread().getName()+"获取到锁");
        // 释放锁
        lock.unlock();
    }

    public static void main(String[] args) {
        // 线程1
        FairLockTest rLook = new FairLockTest();


        for (int i = 0; i <100 ; i++) {
            // 线程2
            Thread thread = new Thread(() -> {
                rLook.testLock();
            });
            thread.setName(""+i);
            thread.start();
        }

    }

}

输出结果基本有序

线程0获取到锁
线程1获取到锁
线程2获取到锁
线程3获取到锁
线程4获取到锁
线程5获取到锁
线程6获取到锁
线程8获取到锁
线程7获取到锁
线程9获取到锁
线程10获取到锁
....

非公平锁示例如下

public class FairLockTest {


    private Lock lock = new ReentrantLock(false);

    private void testLock(){
        // 加锁
        lock.lock();
        // 执行业务逻辑
        System.out.println("线程"+Thread.currentThread().getName()+"获取到锁");
        // 释放锁
        lock.unlock();
    }

    public static void main(String[] args) {
        // 线程1
        FairLockTest rLook = new FairLockTest();


        for (int i = 0; i <100 ; i++) {
            // 线程2
            Thread thread = new Thread(() -> {
                rLook.testLock();
            });
            thread.setName(""+i);
            thread.start();
        }

    }

}

输出结果基本无序

线程0获取到锁
线程1获取到锁
线程7获取到锁
线程2获取到锁
线程10获取到锁
线程3获取到锁
线程6获取到锁
线程11获取到锁
线程4获取到锁
线程5获取到锁
.........

二 ReentrantReadWriteLock 使用

ReentrantLock 与 synchronized 都是 独占锁, 安全性较高,但是相对来说效率低下;ReentrantReadWriteLock读写锁提供了 readLock()和writeLock()用来获取读锁和写锁; 读锁之间读取数据不互斥(故读锁也成为共享锁)。读锁写锁之间互斥,写锁写锁之间互斥;

2.1读锁

读锁示例

public class ReadLockTest {

    private ReentrantReadWriteLock lock = new ReentrantReadWriteLock();

    private void testLock(){
        // 加锁
        lock.readLock().lock();
        // 执行业务逻辑
        System.out.println("线程"+Thread.currentThread().getName()+"获取到锁"+System.currentTimeMillis());
        // 睡眠,保证B线程进来时A线程还是获取锁状态
        try {
            Thread.sleep(2000);
        } catch (InterruptedException e) {
            e.printStackTrace();
        }
        // 释放锁
        lock.readLock().unlock();
    }

    public static void main(String[] args) {
        // 线程1
        ReadLockTest rLook = new ReadLockTest();
        // 线程2
        Thread threadA = new Thread(() -> {
            rLook.testLock();
        });
        threadA.setName("A");
        Thread threadB = new Thread(() -> {
            rLook.testLock();
        });
        threadB.setName("B");
        threadA.start();
        threadB.start();


    }
}

线程A与线程B几乎时同时获取到锁

线程B获取到锁1606723001641
线程A获取到锁1606723001641

2.2 写锁

写锁示例

public class WriteLockTest {

    private ReentrantReadWriteLock lock = new ReentrantReadWriteLock();

    private void testLock(){
        // 加锁
        lock.writeLock().lock();
        // 执行业务逻辑
        System.out.println("线程"+Thread.currentThread().getName()+"获取到锁"+System.currentTimeMillis());
        // 睡眠,保证B线程进来时A线程还是获取锁状态
        try {
            Thread.sleep(2000);
        } catch (InterruptedException e) {
            e.printStackTrace();
        }
        // 释放锁
        lock.writeLock().unlock();
    }

    public static void main(String[] args) {
        // 线程1
        WriteLockTest rLook = new WriteLockTest();
        // 线程2
        Thread threadA = new Thread(() -> {
            rLook.testLock();
        });
        threadA.setName("A");
        Thread threadB = new Thread(() -> {
            rLook.testLock();
        });
        threadB.setName("B");
        threadA.start();
        threadB.start();


    }
}

输出结果基本就是相差 2 秒,即线程A获取锁后,线程B需要等线程A释放锁才能获取锁

线程A获取到锁1606723216664
线程B获取到锁1606723218664

2.3 读写锁

读写锁示例

public class ReadAndWriteTest {

    private ReentrantReadWriteLock lock = new ReentrantReadWriteLock();

    private void writeLock(){
        // 加锁
        lock.writeLock().lock();
        // 执行业务逻辑
        System.out.println("线程"+Thread.currentThread().getName()+"获取到写锁"+System.currentTimeMillis());
        // 睡眠,保证B线程进来时A线程还是获取锁状态
        try {
            Thread.sleep(2000);
        } catch (InterruptedException e) {
            e.printStackTrace();
        }
        // 释放锁
        lock.writeLock().unlock();
    }

    private void readLock(){
        // 加锁
        lock.readLock().lock();
        // 执行业务逻辑
        System.out.println("线程"+Thread.currentThread().getName()+"获取到读锁"+System.currentTimeMillis());
        // 释放锁
        lock.readLock().unlock();
    }

    public static void main(String[] args) {
        // 线程1
        ReadAndWriteTest rLook = new ReadAndWriteTest();
        // 线程2
        Thread threadA = new Thread(() -> {
            rLook.writeLock();
        });
        threadA.setName("A");
        Thread threadB = new Thread(() -> {
            rLook.readLock();
        });
        threadB.setName("B");
        threadA.start();
        threadB.start();


    }
}

输出结果如下,刚好相差2秒;当线程A获取到写锁,线程B必须等待线程A释放写锁后才能获取到读锁;

线程A获取到写锁1606724500217
线程B获取到读锁1606724502217

三 总结

通过本篇文章,我们大概知道常见的锁的使用方式;

  • ReentrantLock 和 synchronized 都是 独占锁,又是可重入锁;独占锁想必大家都知道,重点解释下可重入锁,

当线程A获取到锁进入 methodA 时,再次调用 methodB 就不需要再次获取锁,即代表可重入锁;

class Test{
    public synchronized void methodA() {
        methodB();
    }
     
    public synchronized void methodB() {
         
    }
}
  • synchronized不是可中断锁,而Lock是可中断锁。如果某一线程A正在执行锁中的代码,线程B由于等待时间过长去处理其它业务,就称线程B获得的锁为可中断锁;

  • 公平锁尽量以请求锁的顺序来获取锁。比如有多个线程在等待一个锁,当这个锁被释放时,等待时间最久的线程(最先请求的线程)会获得该锁,这就是公平锁;非公平锁获取方式就是完全随机,抢占模式;

  • 读写锁一般用于操作文件,只要涉及到写锁都是独占锁;读锁是共享锁;

有关 其它锁的知识,可以阅读知识追寻者以前发布的并发编程系列文章

欢迎关注我的公众号:知识追寻者,送原创PDF,面经,开源系统

posted @ 2020-12-02 10:23  知识追寻者  阅读(296)  评论(0编辑  收藏  举报