首页
学习
活动
专区
工具
TVP
发布
社区首页 >专栏 >Lock介绍

Lock介绍

原创
作者头像
HLee
修改2021-10-18 09:07:38
7460
修改2021-10-18 09:07:38
举报
文章被收录于专栏:房东的猫房东的猫

简介

java.util.concurrent.locks包下常用的类与接口(lock是jdk 1.5后新增的)。

锁是用于通过多个线程控制对共享资源的访问的工具,通常锁提供对共享资源的独占访问,一次只能有一个线程可以获取锁,并且对共享资源的所有访问都要求首先获取锁。 但是,一些锁可能允许并发访问共享资源,如ReadWriteLock的读写锁。在Lock接口出现之前,Java程序是靠synchronized关键字实现锁功能的。JDK1.5之后并发包中新增了Lock接口以及相关实现类来实现锁功能。synchronized方法和语句的范围机制使得使用监视器锁更容易编程,并且有助于避免涉及锁的许多常见编程错误,但是有时您需要以更灵活的方式处理锁。

  1. 如果一个代码块被synchronized修饰了,当一个线程获取了对应的锁,并执行该代码块时,其他线程便只能一直等待,等待获取锁的线程释放锁,而这里获取锁的线程释放锁只会有两种情况: 获取锁的线程执行完了该代码块,然后线程释放对锁的占有
  2. 线程执行发生异常,此时JVM会让线程自动释放锁

那么如果这个获取锁的线程由于要等待IO或者其他原因(比如调用sleep方法)被阻塞了,但是又没有释放锁,其他线程便只能地等待,这多么影响程序执行效率。因此就需要有一种机制可以不让等待的线程一直无期限地等待下去(比如只等待一定的时间或者能够响应中断),通过Lock就可以办到。

Lock接口

通过查看Lock的源码可知,Lock是一个接口,接口的实现类ReentrantLock, ReentrantReadWriteLock.ReadLock, ReentrantReadWriteLock.WriteLock。

Lock接口有6个方法:

// 获取锁  
void lock()   

// 如果当前线程未被中断,则获取锁,可以响应中断  
void lockInterruptibly()   

// 返回绑定到此 Lock 实例的新 Condition 实例  
Condition newCondition()   

// 仅在调用时锁为空闲状态才获取该锁,可以响应中断  
boolean tryLock()   

// 如果锁在给定的等待时间内空闲,并且当前线程未被中断,则获取锁  
boolean tryLock(long time, TimeUnit unit)   

// 释放锁  
void unlock()  

ReentrantLock类

ReentrantLock意思是“可重入锁”,ReentrantLock是唯一实现了Lock接口的类,并且ReentrantLock提供了更多的方法。

ReentrantLock() //创建一个 ReentrantLock 的实例
ReentrantLock(boolean fair) //创建一个具有给定公平策略的 ReentrantLock 

int getHoldCount() //查询当前线程保持此锁的次数
protected Thread getOwner() //返回目前拥有此锁的线程,如果此锁不被任何线程拥有,则返回 null 
protected Collection<Thread> getQueuedThreads() //返回一个collection,它包含可能正等待获取此锁的线程 
int getQueueLength() //返回正等待获取此锁的线程估计数 
protected Collection<Thread> getWaitingThreads(Condition condition) //返回一个 collection,它包含可能正在等待与此锁相关给定条件的那些线程 
int getWaitQueueLength(Condition condition) //返回等待与此锁相关的给定条件的线程估计数
boolean hasQueuedThread(Thread thread) //查询给定线程是否正在等待获取此锁
boolean hasQueuedThreads() //查询是否有些线程正在等待获取此锁
boolean hasWaiters(Condition condition) //查询是否有些线程正在等待与此锁有关的给定条件
boolean isFair() //如果此锁的公平设置为 true,则返回true 
boolean isHeldByCurrentThread() //查询当前线程是否保持此锁
boolean isLocked() //查询此锁是否由任意线程保持
void lock() //获取锁
void lockInterruptibly() //如果当前线程未被中断,则获取锁。
Condition newCondition() //返回用来与此 Lock 实例一起使用的 Condition 实例 
boolean tryLock() //仅在调用时锁未被另一个线程保持的情况下,才获取该锁
boolean tryLock(long timeout, TimeUnit unit) //如果锁在给定等待时间内没有被另一个线程保持,且当前线程未被中断,则获取该锁 
void unlock() //试图释放此锁
  • 构造函数:不带参数
  • 构造函数:带参数 
    • true:公平锁;
    • false:非公平锁
/**
 * Creates an instance of {@code ReentrantLock}.
 * This is equivalent to using {@code ReentrantLock(false)}.
 */
public ReentrantLock() {
    sync = new NonfairSync();
}

/**
 * Creates an instance of {@code ReentrantLock} with the
 * given fairness policy.
 *
 * @param fair {@code true} if this lock should use a fair ordering policy
 */
public ReentrantLock(boolean fair) {
    sync = fair ? new FairSync() : new NonfairSync();
}

ReentrantLock和synchronized关键字一样可以用来实现线程之间的同步互斥,功能比synchronized关键字更强大而且更灵活。

public class LockTest {

    public static void main(String[] args) {
        Lock lock = new ReentrantLock();

        Thread thread = new ThreadUser(lock);
        Thread thread1 = new ThreadUser(lock);
        Thread thread2 = new ThreadUser(lock);

        thread.start();
        thread1.start();
        thread2.start();
    }
}

class ThreadUser extends Thread{

    private Lock lock;

    public ThreadUser(Lock lock) {
        this.lock = lock;
    }

    @Override
    public void run() {
        lock.lock();
        for (int i = 0; i < 3; i++) {
            System.out.println(Thread.currentThread().getName() + ": " + (i + 1));
        }
        lock.unlock();
    }
}

执行结果:
Thread-0: 1
Thread-0: 2
Thread-0: 3
Thread-1: 1
Thread-1: 2
Thread-1: 3
Thread-2: 1
Thread-2: 2
Thread-2: 3

总结一下:

  • synchronized是Java语言的关键字,因此是内置特性,Lock不是Java语言内置的,Lock是一个接口,通过实现类可以实现同步访问。
  • synchronized是在JVM层面上实现的,不但可以通过一些监控工具监控synchronized的锁定,而且在代码执行时出现异常,JVM会自动释放锁定,但是使用Lock则不行,lock是通过代码实现的,要保证锁定一定会被释放,就必须将unLock()放到finally{}中
  • 在资源竞争不是很激烈的情况下,Synchronized的性能要优于ReetrantLock,但是在资源竞争很激烈的情况下,Synchronized的性能会下降几十倍,但是ReetrantLock的性能能维持常态。

Condition接口

synchronized关键字与wait()和notify/notifyAll()方法相结合可以实现等待/通知机制,ReentrantLock类当然也可以实现,但是需要借助于Condition接口与newCondition() 方法。Condition是JDK1.5之后才有的,它具有很好的灵活性,比如可以实现多路通知功能也就是在一个Lock对象中可以创建多个Condition实例(即对象监视器),线程对象可以注册在指定的Condition中,从而可以有选择性的进行线程通知,在调度线程上更加灵活。

在使用notify/notifyAll()方法进行通知时,被通知的线程是有JVM选择的,使用ReentrantLock类结合Condition实例可以实现“选择性通知”,这个功能非常重要,而且是Condition接口默认提供的。

而synchronized关键字就相当于整个Lock对象中只有一个Condition实例,所有的线程都注册在它一个身上。如果执行notifyAll()方法的话就会通知所有处于等待状态的线程这样会造成很大的效率问题,而Condition实例的signalAll()方法 只会唤醒注册在该Condition实例中的所有等待线程

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

Condition实现等待/通知机制

public class LockTest2 {

    public static void main(String[] args) throws Exception{
        LockService2 lockService2 = new LockService2();

        Thread thread = new ThreadUser2(lockService2);
        thread.start();

        Thread.sleep(2000);

        lockService2.singal();
    }
}

class ThreadUser2 extends Thread {

    private LockService2 lockService2;

    public ThreadUser2(LockService2 lockService2) {
        this.lockService2 = lockService2;
    }

    @Override
    public void run() {
        lockService2.await();
    }
}

class LockService2{

    private Lock lock = new ReentrantLock();
    private Condition condition = lock.newCondition();

    public void await() {
        try {
            lock.lock();
            System.out.println("await方法开始于: " + System.currentTimeMillis());
            condition.await();
            System.out.println("await方法结束于: " + System.currentTimeMillis());
        } catch (Exception e) {
            e.printStackTrace();
        } finally {
            lock.unlock();
        }
    }

    public void singal() {
        lock.lock();
        System.out.println("singal方法开始于: " + System.currentTimeMillis());
        condition.signal();
        lock.unlock();
    }
}

执行结果:
await方法开始于: 1633782223657
singal方法开始于: 1633782225660
await方法结束于: 1633782225660

在使用wait/notify实现等待通知机制的时候我们知道必须执行完notify()方法所在的synchronized代码块后才释放锁。在这里也差不多,必须执行完signal所在的try语句块之后才释放锁,condition.await()后的语句才能被执行。

多个Condition实例实现等待/通知机制

package com.java.master.Lock;

import java.util.concurrent.locks.Condition;
import java.util.concurrent.locks.Lock;
import java.util.concurrent.locks.ReentrantLock;

public class LockTest3 {

    public static void main(String[] args) throws InterruptedException {

        MyserviceMoreCondition service = new MyserviceMoreCondition();

        ThreadA a = new ThreadA(service);
        a.setName("A");
        a.start();

        ThreadB b = new ThreadB(service);
        b.setName("B");
        b.start();

        Thread.sleep(3000);

        service.signalAll_A();
    }

    static public class ThreadA extends Thread {

        private MyserviceMoreCondition service;

        public ThreadA(MyserviceMoreCondition service) {
            this.service = service;
        }

        @Override
        public void run() {
            service.awaitA();
        }
    }

    static public class ThreadB extends Thread {

        private MyserviceMoreCondition service;

        public ThreadB(MyserviceMoreCondition service) {
            this.service = service;
        }

        @Override
        public void run() {
            service.awaitB();
        }
    }
}

class MyserviceMoreCondition {

    private Lock lock = new ReentrantLock();

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

    public void awaitA() {
        lock.lock();
        try {
            System.out.println("begin awaitA时间为" + System.currentTimeMillis()
                    + " ThreadName=" + Thread.currentThread().getName());
            conditionA.await();
            System.out.println("end awaitA时间为" + System.currentTimeMillis()
                    + " ThreadName=" + Thread.currentThread().getName());
        } catch (Exception e) {
            e.printStackTrace();
        } finally {
            lock.unlock();
        }
    }

    public void awaitB() {
        lock.lock();
        try {
            System.out.println("begin awaitB时间为" + System.currentTimeMillis()
                    + " ThreadName=" + Thread.currentThread().getName());
            conditionB.await();
            System.out.println("end awaitB时间为" + System.currentTimeMillis()
                    + " ThreadName=" + Thread.currentThread().getName());
        } catch (Exception e) {
            e.printStackTrace();
        } finally {
            lock.unlock();
        }
    }

    public void signalAll_A() {
        lock.lock();
        try {
            System.out.println("signalAll_A时间为" + System.currentTimeMillis()
                    + " ThreadName=" + Thread.currentThread().getName());
            conditionA.signalAll();
        } catch (Exception e) {
            e.printStackTrace();
        } finally {
            lock.unlock();
        }
    }

    public void signalAll_B() {
        lock.lock();
        try {
            System.out.println("signalAll_B时间为" + System.currentTimeMillis()
                    + " ThreadName=" + Thread.currentThread().getName());
            conditionB.signalAll();
        } catch (Exception e) {
            e.printStackTrace();
        } finally {
            lock.unlock();
        }
    }
}

执行结果:
begin awaitA时间为1633950645627 ThreadName=A
begin awaitB时间为1633950645628 ThreadName=B
signalAll_A时间为1633950648629 ThreadName=main
end awaitA时间为1633950648630 ThreadName=A

Condition实例生产者&消费者

package com.java.master.Lock;

import java.util.concurrent.locks.Condition;
import java.util.concurrent.locks.Lock;
import java.util.concurrent.locks.ReentrantLock;

/**
 * @author Huan Lee
 * @version 1.0
 * @date 10/11/21 7:14 PM
 * @describtion 业精于勤,荒于嬉;行成于思,毁于随。
 */
public class LockTest4 {

    public static void main(String[] args) {

        LockService4 lockService4 = new LockService4();

        // 一生产者 一消费者
//        Thread producer = new LockProducer4(lockService4);
//        producer.start();
//
//        Thread consumer = new LockConsumer4(lockService4);
//        consumer.start();

        // 多生产者 多消费者
        int size = 2;
        Thread[] producers = new Thread[size];
        Thread[] consumers = new Thread[size];

        for (int i = 0; i < size; i++) {
            char c = (char)('A' + i);

            producers[i] = new LockProducer4(lockService4);
            producers[i].setName("生产者" + c);
            producers[i].start();

            consumers[i] = new LockConsumer4(lockService4);
            consumers[i].setName("消费者" + c);
            consumers[i].start();
        }
    }
}

class LockProducer4 extends Thread{
    private LockService4 lockService4;

    public LockProducer4(LockService4 lockService4) {
        this.lockService4 = lockService4;
    }

    @Override
    public void run() {
        while (true) {
            lockService4.set();
        }
    }
}

class LockConsumer4 extends Thread{
    private LockService4 lockService4;

    public LockConsumer4(LockService4 lockService4) {
        this.lockService4 = lockService4;
    }

    @Override
    public void run() {
        while (true) {
            lockService4.get();
        }
    }
}

class LockService4{

    private Lock lock = new ReentrantLock();
    private Condition condition1 = lock.newCondition();
    private String val = "";

    public void set() {
        try {
            lock.lock();
            while (!"".equals(val)) {
                condition1.await();
            }
            val = System.currentTimeMillis() + "-" + System.nanoTime();
            System.out.println(Thread.currentThread().getName() + " 生产总值: " + val);
//            condition1.signal(); // 一生产者 一消费者
            condition1.signalAll();
        } catch (Exception e) {
            e.printStackTrace();
        } finally {
            lock.unlock();
        }
    }

    public void get() {
        try {
            lock.lock();
            while ("".equals(val)) {
                condition1.await();
            }
            System.out.println(Thread.currentThread().getName() + " 消费总值: " + val);
            val = "";
//            condition1.signal(); // 一生产者 一消费者
            condition1.signalAll();
        } catch (Exception e) {
            e.printStackTrace();
        } finally {
            lock.unlock();
        }
    }
}

公平锁与非公平锁

Lock分为公平锁和非公平锁两种,公平锁表示线程获取锁的顺序是按照加锁的顺序来分配的,也就是先到先得;而非公平锁就是一种抢占机制,是随机获取锁,先来的不一定先获取锁。

public class LockTest5 {

    public static void main(String[] args) {
//        LockService5 lockService5 = new LockService5(true); // 公平锁
        LockService5 lockService5 = new LockService5(false); // 非公平锁

        Thread[] threads = new Thread[5];
        for (int i = 0; i < threads.length; i++) {
            threads[i] = new LockUser5(lockService5);
        }
        for (int i = 0; i < threads.length; i++) {
            threads[i].start();
        }
    }
}

class LockUser5 extends Thread{
    private LockService5 lockService5;

    public LockUser5(LockService5 lockService5) {
        this.lockService5 = lockService5;
    }

    @Override
    public void run() {
        lockService5.fool();
    }
}

class LockService5{

    private Lock lock;
    public LockService5(boolean isFair) {
        // 无参是非公平锁  参数 是true是公平锁
        lock = new ReentrantLock(isFair);
    }

    public void fool() {
        try {
            lock.lock();
            System.out.println(Thread.currentThread().getName() + " 获得锁定");
        } catch (Exception e) {
            e.printStackTrace();
        } finally {
            lock.unlock();
        }
    }
}

非公平:
Thread-0 获得锁定
Thread-3 获得锁定
Thread-4 获得锁定
Thread-1 获得锁定
Thread-2 获得锁定

公平:
Thread-0 获得锁定
Thread-1 获得锁定
Thread-2 获得锁定
Thread-3 获得锁定
Thread-4 获得锁定

Condition实现顺序执行

public class ConditionSeqExec {

    volatile private static int nextPrintWho = 1;

    // 默认情况下ReentranLock类使用的是非公平锁
    final private static ReentrantLock lock = new ReentrantLock();

    final private static Condition conditionA = lock.newCondition();
    final private static Condition conditionB = lock.newCondition();
    final private static Condition conditionC = lock.newCondition();

    public static void main(String[] args) {


        Thread threadA = new Thread() {
            public void run() {
                lock.lock();
                try {
                    while (nextPrintWho != 1) {
                        conditionA.await();
                    }
                    for (int i = 0; i < 3; i++) {
                        System.out.println("ThreadA" + (i + 1));
                    }

                    nextPrintWho = 2;
                    //通知conditionB实例的线程运行
                    conditionB.signalAll();
                } catch (InterruptedException e) {
                    e.printStackTrace();
                } finally {
                    lock.unlock();
                }
            }
        };

        Thread threadB = new Thread() {
            public void run() {
                lock.lock();
                try {
                    while (nextPrintWho != 2) {
                        conditionB.await();
                    }
                    for (int i = 0; i < 3; i++) {
                        System.out.println("ThreadB" + (i + 1));
                    }

                    nextPrintWho = 3;
                    //通知conditionB实例的线程运行
                    conditionC.signalAll();
                } catch (InterruptedException e) {
                    e.printStackTrace();
                } finally {
                    lock.unlock();
                }
            }
        };

        Thread threadC = new Thread() {
            public void run() {
                lock.lock();
                try {
                    while (nextPrintWho != 3) {
                        conditionC.await();
                    }
                    for (int i = 0; i < 3; i++) {
                        System.out.println("ThreadC" + (i + 1));
                    }

                    nextPrintWho = 1;
                    //通知conditionB实例的线程运行
                    conditionA.signalAll();
                } catch (InterruptedException e) {
                    e.printStackTrace();
                } finally {
                    lock.unlock();
                }
            }
        };

        Thread[] array1 = new Thread[5];
        Thread[] array2 = new Thread[5];
        Thread[] array3 = new Thread[5];

        for (int i = 0; i < 5; i++) {
            array1[i] = new Thread(threadA);
            array2[i] = new Thread(threadB);
            array3[i] = new Thread(threadC);

            array1[i].start();
            array2[i].start();
            array3[i].start();
        }
    }
}

在一个线程运行完之后通过condition.signal()/condition.signalAll()方法通知下一个特定的运行运行,就这样循环往复即可。

ReadWriteLock接口

public interface ReadWriteLock {
    /**
     * Returns the lock used for reading.
     *
     * @return the lock used for reading
     */
    Lock readLock();

    /**
     * Returns the lock used for writing.
     *
     * @return the lock used for writing
     */
    Lock writeLock();
}

ReadWriteLock里面只定义了两个方法:一个用来获取读锁,一个用来获取写锁。也就是说将文件的读写操作分开,分成2个锁来分配给线程,从而使得多个线程可以同时进行读操作,ReentrantReadWriteLock实现了ReadWriteLock接口。

ReentrantReadWriteLock接口

ReentrantLock(排他锁)具有完全互斥排他的效果,即同一时刻只允许一个线程访问,这样做虽然保证了实例变量的线程安全性,但效率非常低下。ReadWriteLock接口的实现类ReentrantReadWriteLock读写锁就是为了解决这个问题。ReentrantReadWriteLock里面提供了很多丰富的方法,不过最主要的有两个方法:readLock()和writeLock()用来获取读锁和写锁。

读写锁维护了两个锁,一个是读操作相关的锁也成为共享锁,一个是写操作相关的锁也称为排他锁。通过分离读锁和写锁,其并发性比一般排他锁有了很大提升。

多个读锁之间不互斥,读锁与写锁互斥,写锁与写锁互斥(只要出现写操作的过程就是互斥的)。在没有线程Thread进行写入操作时,进行读取操作的多个Thread都可以获取读锁,而进行写入操作的Thread只有在获取写锁后才能进行写入操作。即多个Thread可以同时进行读取操作,但是同一时刻只允许一个Thread进行写入操作。

public class LockTest6 {

    public static void main(String[] args) {
        LockService6 lockService6 = new LockService6();
        Thread t1 = new LockUser6A(lockService6);
        t1.setName("读线程A");
        t1.start();

        Thread t2 = new LockUser6A(lockService6);
        t2.setName("读线程B");
        t2.start();

        Thread t3 = new LockUser6B(lockService6);
        t3.setName("写线程A");
        t3.start();

        Thread t4 = new LockUser6B(lockService6);
        t4.setName("写线程B");
        t4.start();
    }
}

class LockUser6A extends Thread{

    private LockService6 lockService6;

    public LockUser6A(LockService6 lockService6) {
        this.lockService6 = lockService6;
    }

    @Override
    public void run() {
        lockService6.read();
    }
}

class LockUser6B extends Thread{

    private LockService6 lockService6;

    public LockUser6B(LockService6 lockService6) {
        this.lockService6 = lockService6;
    }

    @Override
    public void run() {
        lockService6.write();
    }
}

class LockService6{

    private ReentrantReadWriteLock lock = new ReentrantReadWriteLock();

    public void read() {
        try {
            lock.readLock().lock();
            System.out.println(Thread.currentThread().getName() + "获得读锁于" + System.currentTimeMillis());
            Thread.sleep(1000);
            System.out.println(Thread.currentThread().getName() + "接触读锁于" + System.currentTimeMillis());
        } catch (Exception e) {
            e.printStackTrace();
        } finally {
            lock.readLock().unlock();
        }
    }

    public void write() {
        try {
            lock.writeLock().lock();
            System.out.println(Thread.currentThread().getName() + "获得写锁于" + System.currentTimeMillis());
            Thread.sleep(1000);
            System.out.println(Thread.currentThread().getName() + "接触写锁于" + System.currentTimeMillis());
        } catch (Exception e) {
            e.printStackTrace();
        } finally {
            lock.writeLock().unlock();
        }
    }
}

执行结果:
读线程B获得读锁于1633955419136
读线程A获得读锁于1633955419136
读线程B接触读锁于1633955420140
读线程A接触读锁于1633955420140
写线程A获得写锁于1633955420140
写线程A接触写锁于1633955421144
写线程B获得写锁于1633955421144
写线程B接触写锁于1633955422147

https://segmentfault.com/a/1190000020541622

https://www.cnblogs.com/myseries/p/10784076.html

原创声明:本文系作者授权腾讯云开发者社区发表,未经许可,不得转载。

如有侵权,请联系 cloudcommunity@tencent.com 删除。

原创声明:本文系作者授权腾讯云开发者社区发表,未经许可,不得转载。

如有侵权,请联系 cloudcommunity@tencent.com 删除。

评论
登录后参与评论
0 条评论
热度
最新
推荐阅读
目录
  • 简介
  • Lock接口
  • ReentrantLock类
  • Condition接口
    • Condition实现等待/通知机制
      • 多个Condition实例实现等待/通知机制
        • Condition实例生产者&消费者
          • 公平锁与非公平锁
            • Condition实现顺序执行
            • ReadWriteLock接口
              • ReentrantReadWriteLock接口
              领券
              问题归档专栏文章快讯文章归档关键词归档开发者手册归档开发者手册 Section 归档