java并发lock锁详解和使用

Java基础

浏览数:90

2019-10-3

AD:资源代下载服务

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

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

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

Lock接口简介和使用

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

public interface Lock {
    void lock();
    void lockInterruptibly() throws InterruptedException;
    boolean tryLock();
    boolean tryLock(long time, TimeUnit unit) throws InterruptedException;
    void unlock();
    Condition newCondition();
}

lock()、tryLock()、tryLock(long time, TimeUnit unit)和lockInterruptibly()是用来获取锁的,unLock()方法是用来释放锁的。Lock中声明了四个方法来获取锁,那么这四个方法有何区别呢?

1)lock()方法是平常使用得最多的一个方法,就是用来获取锁。如果锁已被其他线程获取,则进行等待。采用Lock,必须主动去释放锁,并且在发生异常时,不会自动释放锁。因此一般来说,使用Lock必须在try{}catch{}块中进行,并且将释放锁的操作放在finally块中进行,以保证锁一定被被释放,防止死锁的发生。通常使用Lock来进行同步的话,是以下面这种形式去使用的:

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

2)tryLock()方法是有返回值的,它表示用来尝试获取锁,如果获取成功,则返回true,如果获取失败(即锁已被其他线程获取),则返回false,也就说这个方法无论如何都会立即返回,在拿不到锁时也不会一直在那等待。
tryLock(long time, TimeUnit unit)方法和tryLock()方法是类似的,只不过区别在于这个方法在拿不到锁时会等待一定的时间,在时间期限之内如果还拿不到锁,就返回false。如果如果一开始拿到锁或者在等待期间内拿到了锁,则返回true。所以,一般情况下通过tryLock来获取锁时是这样使用的:

Lock lock=new ReentrantLock();
if(lock.tryLock()) {
     try{
         //处理任务
     }catch(Exception ex){
         
     }finally{
         lock.unlock();   //释放锁
     } 
}else {
    //如果不能获取锁,则直接做其他事情
}

3)lockInterruptibly()方法比较特殊,当通过这个方法去获取锁时,如果线程正在等待获取锁,则这个线程能够响应中断,即中断线程的等待状态。也就说,当两个线程同时通过lock.lockInterruptibly()想获取某个锁时,假若此时线程A获取到了锁,而线程B只有在等待,那么对线程B调用threadB.interrupt()方法能够中断线程B的等待过程。
由于lockInterruptibly()的声明中抛出了异常,所以lock.lockInterruptibly()必须放在try块中或者在调用lockInterruptibly()的方法外声明抛出InterruptedException。因此lockInterruptibly()一般的使用形式如下:

public void method() throws InterruptedException {
    Lock lock=new ReentrantLock();
    lock.lockInterruptibly();
    try {  
     //.....
    }
    finally {
        lock.unlock();
    }  
}

注意,当一个线程获取了锁之后,是不会被interrupt()方法中断的。因为单独调用interrupt()方法不能中断正在运行过程中的线程,只能中断阻塞过程中的线程。因此当通过lockInterruptibly()方法获取某个锁时,如果不能获取到,只有进行等待的情况下,是可以响应中断的。而用synchronized修饰的话,当一个线程处于等待某个锁的状态,是无法被中断的,只有一直等待下去。

4)newCondition()获取等待通知组件,该组件和当前的锁绑定,当前线程只有获得了锁,才能调用该组件的wait()方法,而调用后,当前线程将释放锁。

Lock接口的实现类:ReentrantLock

ReentrantLock意思是“可重入锁”,ReentrantLock是唯一实现了Lock接口的类,并且ReentrantLock提供了更多的方法。ReentrantLock和synchronized关键字一样可以用来实现线程之间的同步互斥,功能比synchronized关键字更强大而且更灵活。
通过查看Lock的源码可知,主要方法有:

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() //试图释放此锁

通过一些实例看具体看一下如何使用ReentrantLock

1)lock()的使用方法
public class LockTest2 {
    private Lock lock = new ReentrantLock();

    public static void main(String[] args) {
        final LockTest2 test = new LockTest2();

        new Thread(){
            public void run(){
                test.insert(Thread.currentThread());
            }
        }.start();

        new Thread(){
            public void run(){
                test.insert(Thread.currentThread());
            }
        }.start();
    }

    public void insert(Thread thread){
        lock.lock();
        try {
            System.out.println(thread.getName() + "得到了锁");
            for (int i = 0; i < 5; i++) {
                System.out.println("ThreadName=" + Thread.currentThread().getName() + (" " + (i + 1)));
            }
        } catch (Exception e) {
        } finally {
            System.out.println(thread.getName() + "释放了锁");
            lock.unlock();
        }
    }
}


从运行结果可以看出,当一个线程运行完毕后才把锁释放,其他线程才能执行,其他线程的执行顺序是不确定的。
2)tryLock()的使用方法

public class LockTest2 {
    private Lock lock = new ReentrantLock();

    public static void main(String[] args) {
        final LockTest2 test = new LockTest2();

        new Thread(){
            public void run(){
                test.insert(Thread.currentThread());
            }
        }.start();

        new Thread(){
            public void run(){
                test.insert(Thread.currentThread());
            }
        }.start();

        new Thread(){
            public void run(){
                test.insert(Thread.currentThread());
            }
        }.start();
    }

    public void insert(Thread thread){
        if(lock.tryLock()) {
            try {
                System.out.println(thread.getName()+"得到了锁");
                for (int i = 0; i < 3; i++) {
                    System.out.println("ThreadName=" + Thread.currentThread().getName() + (" " + (i + 1)));
                }
            } catch (Exception e) {
            }finally {
                System.out.println(thread.getName()+"释放了锁");
                lock.unlock();
            }
        } else {
            System.out.println(thread.getName()+"获取锁失败");
        }
    }
}


3)lockInterruptibly()响应中断的使用方法

public class LockTest3 {
    private Lock lock = new ReentrantLock();

    public static void main(String[] args) {
        LockTest3 test = new LockTest3();
        MyThread thread1 = new MyThread(test);
        MyThread thread2 = new MyThread(test);
        thread1.start();
        thread2.start();

        try {
            Thread.sleep(2000);
        } catch (InterruptedException e) {
            e.printStackTrace();
        }
        thread2.interrupt();
    }

    public void insert(Thread thread) throws InterruptedException {
        lock.lockInterruptibly(); //注意,如果需要正确中断等待锁的线程,必须将获取锁放在外面,然后将InterruptedException抛出
        try {
            System.out.println(thread.getName() + "得到了锁");
            long startTime = System.currentTimeMillis();

            for (; ; ) {
                if (System.currentTimeMillis() - startTime >= Integer.MAX_VALUE)
                    break;
                //插入数据
            }
        } finally {
            System.out.println(Thread.currentThread().getName() + "执行finally");
            lock.unlock();
            System.out.println(thread.getName() + "释放了锁");
        }
    }

    static class MyThread extends Thread {
        private LockTest3 test;

        public MyThread(LockTest3 test) {
            this.test = test;
        }

        @Override
        public void run() {
            try {
                test.insert(Thread.currentThread());
            } catch (InterruptedException e) {
                System.out.println(Thread.currentThread().getName() + "被中断");
            }
        }
    }
}

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() //唤醒所有等待线程。 

1)Condition实现等待/通知机制

public class UseSingleConditionWaitNotify {

    public static void main(String[] args) throws InterruptedException {
        MyService service = new MyService();

        ThreadA a = new ThreadA(service);
        a.start();

        Thread.sleep(3000);

        service.signal();
    }
    
    static public class MyService {
        private Lock lock = new ReentrantLock();
        public Condition condition = lock.newCondition();

        public void await() {
            lock.lock();
            try {
                System.out.println("await时间为:" + System.currentTimeMillis());
                condition.await();
                System.out.println("这是condition.await()方法之后的语句,condition.signal()方法之后我才被执行");
            } catch (InterruptedException e) {
                e.printStackTrace();
            } finally {
                lock.unlock();
            }
        }

        public void signal() {
            lock.lock();
            try {
                System.out.println("signal时间为" + System.currentTimeMillis());
                condition.signal();
                Thread.sleep(3000);
                System.out.println("这是condition.signal()方法之后的语句");
            } catch (InterruptedException e) {
                e.printStackTrace();
            } finally {
                lock.unlock();
            }
        }
    }

    static public class ThreadA extends Thread {
        private MyService service;

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

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

}


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

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

public 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();
        }
    }

}
public class UseMoreConditionWaitNotify {
    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();
        }
    }
}

3)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进行写入操作。

1)假如有多个线程要同时进行读操作的话,先看一下synchronized达到的效果:

public class ReentrantReadWriteLockTest2 {
    public static void main(String[] args) {
        final ReentrantReadWriteLockTest2 test = new ReentrantReadWriteLockTest2();
        
        new Thread() {
            public void run() {
                test.get(Thread.currentThread());
            }
        }.start();

        new Thread() {
            public void run() {
                test.get(Thread.currentThread());
            }
        }.start();

    }

    synchronized public void get(Thread thread) {
        long start = System.currentTimeMillis();
        while (System.currentTimeMillis() - start <= 1) {
            System.out.println(thread.getName() + "正在进行读操作");
        }
        System.out.println(thread.getName() + "读操作完毕");
    }
}

2)实现读写锁

public class ReentrantReadWriteLockTest2 {

    private ReentrantReadWriteLock rwl = new ReentrantReadWriteLock();

    public static void main(String[] args) {
        final ReentrantReadWriteLockTest2 test = new ReentrantReadWriteLockTest2();

        new Thread() {
            public void run() {
                test.get(Thread.currentThread());
            }
        }.start();

        new Thread() {
            public void run() {
                test.get(Thread.currentThread());
            }
        }.start();

    }

    public void get(Thread thread) {
        rwl.readLock().lock();
        try {
            long start = System.currentTimeMillis();
            while (System.currentTimeMillis() - start <= 1) {
                System.out.println(thread.getName() + "正在进行读操作");
            }
            System.out.println(thread.getName() + "读操作完毕");
        } finally {
            rwl.readLock().unlock();
        }
    }
}

thread1和thread2在同时进行读操作,这样就大大提升了读操作的效率。不过要注意的是,如果有一个线程已经占用了读锁,则此时其他线程如果要申请写锁,则申请写锁的线程会一直等待释放读锁。如果有一个线程已经占用了写锁,则此时其他线程如果申请写锁或者读锁,则申请的线程会一直等待释放写锁。

3)读读共享

public class ReentrantReadWriteLockTest {

    /**
     * ReentrantReadWriteLock的特性
     * 1.公平性选择:支持非公平(默认)和公平的锁获取方式,吞吐量上来看还是非公平优于公平
     * 2.重进入:该锁支持重进入,以读写线程为例:读线程在获取了读锁之后,能够再次获取读锁。而写线程在获取了写锁之后能够再次获取写锁也能够同时获取读锁
     * 3.锁降级:遵循获取写锁、获取读锁再释放写锁的次序,写锁能够降级称为读锁
     */
    public static void main(String[] args) {
        Service service = new Service();

        MyThread1 a1 = new MyThread1(service);
        MyThread1 a2 = new MyThread1(service);

        a1.start();
        a2.start();
    }


    static public class Service {
        private ReentrantReadWriteLock lock = new ReentrantReadWriteLock();

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

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

    static public class MyThread1 extends Thread {
        private Service service;

        public MyThread1(Service service) {
            this.service = service;
        }

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

    //只要出现写操作的过程就是互斥的
    static public class MyThread2 extends Thread {
        private Service service;

        public MyThread2(Service service) {
            this.service = service;
        }

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

两个线程可以同时或者说是几乎同时运行lock()方法后面的代码,输出的两句话显示的时间一样,这样提高了程序的运行效率。
4)写写互斥

public static void main(String[] args) {
    Service service = new Service();

    MyThread2 a1 = new MyThread2(service);
    MyThread2 a2 = new MyThread2(service);

    a1.start();
    a2.start();
}

同一时间只允许一个线程执行lock()方法后面的代码
5)读写互斥

public static void main(String[] args) {
    Service service = new Service();

    MyThread1 a1 = new MyThread1(service);
    MyThread2 a2 = new MyThread2(service);

    a1.start();
    a2.start();
}

运行两个使用同一个Service对象实例的线程a,b,线程a执行上面的read方法,线程b执行上面的write方法。你会发现同一时间只允许一个线程执行lock()方法后面的代码。

锁的相关概念简介

1)可重入锁
如果锁具备可重入性,则称作为可重入锁。像synchronized和ReentrantLock都是可重入锁,可重入性在我看来实际上表明了锁的分配机制:基于线程的分配,而不是基于方法调用的分配。举个简单的例子,当一个线程执行到某个synchronized方法时,比如说method1,而在method1中会调用另外一个synchronized方法method2,此时线程不必重新去申请锁,而是可以直接执行方法method2。

class Test{
    public synchronized void method1() {
        method2();
    }
     
    public synchronized void method2() {
         
    }
}

上述代码中的两个方法method1和method2都用synchronized修饰了,假如某一时刻,线程A执行到了method1,此时线程A获取了这个对象的锁,而由于method2也是synchronized方法,假如synchronized不具备可重入性,此时线程A需要重新申请锁。但是这就会造成一个问题,因为线程A已经持有了该对象的锁,而又在申请获取该对象的锁,这样就会线程A一直等待永远不会获取到的锁。而由于synchronized和Lock都具备可重入性,所以不会发生上述现象。

2)可中断锁
在Java中,synchronized就不是可中断锁,而Lock是可中断锁。如果某一线程A正在执行锁中的代码,另一线程B正在等待获取该锁,可能由于等待时间过长,线程B不想等待了,想先处理其他事情,我们可以让它中断自己或者在别的线程中中断它,这种就是可中断锁。

3)公平锁
公平锁即尽量以请求锁的顺序来获取锁。比如同是有多个线程在等待一个锁,当这个锁被释放时,等待时间最久的线程(最先请求的线程)会获得该所,这种就是公平锁。非公平锁即无法保证锁的获取是按照请求锁的顺序进行的。这样就可能导致某个或者一些线程永远获取不到锁。
在Java中,synchronized就是非公平锁,它无法保证等待的线程获取锁的顺序。而对于ReentrantLock和ReentrantReadWriteLock,它默认情况下是非公平锁,但是可以设置为公平锁。

  /**
     * Sync object for non-fair locks
     */
    static final class NonfairSync extends Sync {
        private static final long serialVersionUID = 7316153563782823691L;

        /**
         * Performs lock.  Try immediate barge, backing up to normal
         * acquire on failure.
         */
        final void lock() {
            if (compareAndSetState(0, 1))
                setExclusiveOwnerThread(Thread.currentThread());
            else
                acquire(1);
        }

        protected final boolean tryAcquire(int acquires) {
            return nonfairTryAcquire(acquires);
        }
    }

    /**
     * Sync object for fair locks
     */
    static final class FairSync extends Sync {
        private static final long serialVersionUID = -3000897897090466540L;

        final void lock() {
            acquire(1);
        }

        /**
         * Fair version of tryAcquire.  Don't grant access unless
         * recursive call or no waiters or is first.
         */
        protected final boolean tryAcquire(int acquires) {
            final Thread current = Thread.currentThread();
            int c = getState();
            if (c == 0) {
                if (!hasQueuedPredecessors() &&
                    compareAndSetState(0, acquires)) {
                    setExclusiveOwnerThread(current);
                    return true;
                }
            }
            else if (current == getExclusiveOwnerThread()) {
                int nextc = c + acquires;
                if (nextc < 0)
                    throw new Error("Maximum lock count exceeded");
                setState(nextc);
                return true;
            }
            return false;
        }
    }

在ReentrantLock中定义了2个静态内部类,一个是NotFairSync,一个是FairSync,分别用来实现非公平锁和公平锁。我们可以在创建ReentrantLock对象时,通过以下方式来设置锁的公平性:ReentrantLock lock = new ReentrantLock(true);如果参数为true表示为公平锁,为fasle为非公平锁。默认情况下,如果使用无参构造器,则是非公平锁。

4)读写锁
读写锁将对一个资源(比如文件)的访问分成了2个锁,一个读锁和一个写锁。正因为有了读写锁,才使得多个线程之间的读操作不会发生冲突。
ReadWriteLock就是读写锁,它是一个接口,ReentrantReadWriteLock实现了这个接口。可以通过readLock()获取读锁,通过writeLock()获取写锁。

参考文献:https://blog.csdn.net/qq_3433…
https://www.cnblogs.com/wuhan…

作者:Jello