Java中常见的5synchronized的缺陷.locks.util

synchronized的5个缺陷

synchronized是java中的一个关键字,也就是说它是Java语言的一个内置特性。那么为什么会出现Lock呢?如果一个代码块被synchronized修改,当一个线程获取到对应的锁并执行该代码块时,其他线程只能等到获取锁的线程释放锁,而这里获取锁的线程释放锁才有两种情况:

那么如果获取锁的线程因为等待IO或者其他原因(比如调用sleep方法)被阻塞,但是锁没有被释放,其他线程只能干等待。想象一下这如何影响程序执行。效率。因此,需要有一种机制来防止等待线程无限期等待(例如,只等待一定时间或能够响应中断),这可以通过Lock来完成。再比如:当有多个线程读写一个文件时,读操作和写操作会冲突,写操作和写操作也会冲突,但是读操作和读操作不会冲突。但是,使用synchronized关键字来实现同步会带来一个问题:如果多个线程只进行读操作,那么当一个线程在进行读操作时,其他线程只能等待而不能进行读操作。因此,当多个线程只进行读操作时,需要一种机制来防止线程之间的冲突,这可以通过Lock来完成。另外,通过Lock,可以知道线程是否成功获取了锁。这是同步无法做到的。综上所述,就是说Lock提供了比同步更多的功能。但请记住以下几点:

6.java.util.concurrent.locks 封装常用类6.1个锁

首先要解释的是Lock。通过查看源码,我们可以看到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,则必须主动释放锁,发生异常时不会自动释放锁。因此,一般情况下,Lock的使用必须在try{}catch{}块中进行,释放锁的操作必须在finally块中进行,以保证锁必须被释放,防止发生的僵局。通常使用Lock进行同步,使用形式如下:

class C {
        //锁对象
        private final ReentrantLock lock = new ReentrantLock();
        ......
        //保证线程安全方法
        public void method() {
            //上锁
            lock.lock();
            try {
                //保证线程安全操作代码
            } catch() {
            
            } finally {
                lock.unlock();//释放锁
            }
        }
    }

tryLock() 方法有一个返回值,表示它是用来尝试获取锁的。如果获取成功,则返回true,如果获取失败(即锁已被其他线程获取),则返回false,表示该方法无论如何都会立即返回。得不到锁的时候不会一直等待。

tryLock(long time, TimeUnit unit) 方法与tryLock() 方法类似,但不同的是,该方法在获取不到锁时会等待一定的时间,如果无法获取锁在时限内获取,返回false。如果锁是最初获得的或在等待期内获得的,则返回 true。

所以,一般来说,tryLock 是用来获取锁的:

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

lockInterruptibly() 方法很特殊。通过该方法获取锁时,如果线程正在等待获取锁,则线程可以响应中断,即中断线程的等待状态。也就是说,当两个线程要同时通过lock.lockInterruptibly()获取锁时,如果线程A获取了锁而线程B只是在等待,那么在线程B上调用threadB.interrupt()方法就可以中断线程B的等待进程。

因为lockInterruptibly()语句会抛出异常,所以lock.lockInterruptibly()必须放在try块中或者声明在调用lockInterruptibly()的方法之外抛出InterruptedException。

因此lockInterruptibly()的一般用法如下:

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

注意当线程获取锁时,不会被interrupt()方法打断。因为我在上一篇文章中说过,单独调用interrupt()方法不能中断正在运行的进程中的线程,只能中断阻塞进程中的线程。

因此,当通过lockInterruptibly()方法获取锁时,如果无法获取,则只能通过等待来响应中断。同步修改,当线程处于等待锁状态时,不能被中断,只能永远等待。

6.2 重入锁

ReentrantLock,意思是“可重入锁”。 ReentrantLock 是唯一实现 Lock 接口的类,ReentrantLock 提供了更多的方法。下面通过一些例子来看看如何使用 ReentrantLock。

示例1、lock()的正确使用

public class TestLock {
    private ArrayList arrayList = new ArrayList<>();
    public static void main(String[] args){
        final TestLock testLock = new TestLock();
        new Thread(){
            public void run(){
                testLock.insert(Thread.currentThread());
            }
        }.start();
        for (int i = 0; i < 5; i++) {
            new Thread(){
                public void run(){
                    testLock.insert(Thread.currentThread());
                }
            }.start();
        }
    }
    private void insert(Thread thread) {
        Lock lock = new ReentrantLock();//注意这个地方
        lock.lock();
        try {
            System.out.println(thread.getName()+"得到了锁");
            for (int i = 0; i <5 ; i++) {
                arrayList.add(i);
            }
        }catch (Exception e){
            //ToDo:hanle exception
        }finally {
            System.out.println(thread.getName()+"释放了锁");
            lock.unlock();
        }
    }
}

结果不是预期的同步:

Thread-0得到了锁
Thread-1得到了锁
Thread-0释放了锁
Thread-1释放了锁
Thread-2得到了锁
Thread-3得到了锁
Thread-2释放了锁
Thread-3释放了锁
Thread-4得到了锁
Thread-4释放了锁
Thread-5得到了锁
Thread-5释放了锁

为什么结果不是同步锁?因为这里的锁是放在方法里面的,所以是局部变量。如开篇所述,方法中的局部变量存放在线程的工作区中,每个线程在执行该方法的时候都会保存一份副本,所以当然每个线程都执行lock.lock()来获取不同的锁,所以没有冲突。

因此,锁应该被声明为类的属性。分享:

public class TestLock {
    private ArrayList arrayList = new ArrayList<>();
    private Lock lock = new ReentrantLock();//注意这个地方
    public static void main(String[] args){
        final TestLock testLock = new TestLock();
        new Thread(){
            public void run(){
                testLock.insert(Thread.currentThread());
            }
        }.start();
        for (int i = 0; i < 5; i++) {
            new Thread(){
                public void run(){
                    testLock.insert(Thread.currentThread());
                }
            }.start();
        }
    }
    private void insert(Thread thread) {
        lock.lock();
        try {
            System.out.println(thread.getName()+"得到了锁");
            for (int i = 0; i <5 ; i++) {
                arrayList.add(i);
            }
        }catch (Exception e){
            //ToDo:hanle exception
        }finally {
            System.out.println(thread.getName()+"释放了锁");
            lock.unlock();
        }
    }
}

这是使用锁的正确方法。

示例2、tryLock()的使用

class TestTryLock{
    private ArrayList arrayList = new ArrayList<>();
    private Lock lock = new ReentrantLock();//注意这个地方
    public static void main(String[] args){
        final TestTryLock testLock = new TestTryLock();
        for (int i = 0; i < 5; i++) {
            new Thread(){
                public void run(){
                    testLock.insert(Thread.currentThread());
                }
            }.start();
        }
    }
    private void insert(Thread thread) {
        if (lock.tryLock()){
            try {
                System.out.println(thread.getName()+" :得到了锁");
                for (int i = 0; i <5 ; i++) {
                    arrayList.add(i);
                }
//                Thread.sleep(10);
            }catch (Exception e){
                //ToDo:handle exception
            }finally {
                System.out.println(thread.getName()+" :释放了锁");
                lock.unlock();
            }
        }else{
            System.out.println(thread.getName()+"获取锁失败");
        }
    }
}

由于多线程操作不确定,结果不确定,通过休眠可以更明显。

Thread-1 :得到了锁
Thread-2获取锁失败
Thread-0获取锁失败
Thread-3获取锁失败
Thread-1 :释放了锁
Thread-4 :得到了锁
Thread-4 :释放了锁

示例3、使用lockInterrptibly()响应中断:

class TestLockInterruptibly{
    private Lock lock = new ReentrantLock();
    public static void main(String[] args) {
        TestLockInterruptibly testLockInterruptibly = new TestLockInterruptibly();
        MyThread thread1 = new MyThread(testLockInterruptibly);
        MyThread thread2 = new MyThread(testLockInterruptibly);
        thread1.start();
        thread2.start();
        try {
            Thread.sleep(2000);
        } catch (InterruptedException e) {
            e.printStackTrace();
        }
        thread2.interrupt();
    }
    private void insert(Thread thread) throws InterruptedException {
            lock.lockInterruptibly();//注意:如果需要正确中断等待锁的线程,必须将获取锁放在外面,然后将InterruptedException抛出
            try {
                System.out.println(thread.getName()+" :得到了锁");
                long start = System.currentTimeMillis();
                for (;;){
                    if (System.currentTimeMillis()-start >=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 TestLockInterruptibly testLockInterruptibly = null;
        public MyThread(TestLockInterruptibly testLockInterruptibly){
            this.testLockInterruptibly = testLockInterruptibly;
        }
        @Override
        public void run() {
            try{
                testLockInterruptibly.insert(Thread.currentThread());
            } catch (InterruptedException e) {
                System.out.println(Thread.currentThread().getName()+"被中断");
                e.printStackTrace();
            }
        }
    }
}

运行后发现thread2可以中断,但程序并没有结束。

Thread-0 :得到了锁
Thread-1被中断
java.lang.InterruptedException
	at java.util.concurrent.locks.AbstractQueuedSynchronizer.doAcquireInterruptibly(AbstractQueuedSynchronizer.java:896)
	at java.util.concurrent.locks.AbstractQueuedSynchronizer.acquireInterruptibly(AbstractQueuedSynchronizer.java:1221)
	at java.util.concurrent.locks.ReentrantLock.lockInterruptibly(ReentrantLock.java:340)
	at com.test.java.thread.TestLockInterruptibly.insert(TestLock.java:102)
	at com.test.java.thread.TestLockInterruptibly.access$000(TestLock.java:84)
	at com.test.java.thread.TestLockInterruptibly$MyThread.run(TestLock.java:128)

6.3 读写锁

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

一个用于获取读锁,一个用于获取写锁。也就是说,文件的读写操作是分开的,分成两把锁分配给线程,这样多个线程可以同时进行读操作。下面的 ReentrantReadWriteLock 实现了 ReadWriteLock 接口。

6.4 ReentrantReadWriteLock

ReentrantReadWriteLock提供了很多丰富的方法,但主要有两个:readLock()和writeLock()用于获取读锁和写锁。

我们通过几个例子来看看ReentrantReadWriteLock的具体用法。

如果有多个线程同时读取,先看一下synchronized实现的效果:

/**
 * 读写锁
 * 若使用synchronized
 */
class TestReadWriteLock{
    private ReentrantReadWriteLock rwl = new ReentrantReadWriteLock();
    public static void main(String[] args) {
        final TestReadWriteLock test = new TestReadWriteLock();
        new Thread(){
            public void run(){
                test.get(Thread.currentThread());
            }
        }.start();
        new Thread(){
            public void run(){
                test.get(Thread.currentThread());
            }
        }.start();
    }
    public synchronized void get(Thread thread){
        long start = System.currentTimeMillis();
        while (System.currentTimeMillis()-start<=1){
            System.out.println(thread.getName()+"正在进行读操作");
        }
        System.out.println(thread.getName()+"读操作完毕");
    }
}

这个程序的输出将是线程1执行的读取操作的信息在线程0完成读取操作之前不会被打印出来。

Thread-0正在进行读操作
Thread-0正在进行读操作
Thread-0正在进行读操作
Thread-0正在进行读操作
Thread-0正在进行读操作
Thread-0正在进行读操作
Thread-0正在进行读操作
Thread-0正在进行读操作
Thread-0正在进行读操作
Thread-0正在进行读操作
Thread-0正在进行读操作
Thread-0正在进行读操作
Thread-0正在进行读操作
Thread-0正在进行读操作
Thread-0正在进行读操作
Thread-0正在进行读操作
Thread-0正在进行读操作
Thread-0正在进行读操作
Thread-0正在进行读操作
Thread-0正在进行读操作
Thread-0正在进行读操作
Thread-0正在进行读操作
Thread-0正在进行读操作
Thread-0正在进行读操作
Thread-0正在进行读操作
Thread-0正在进行读操作
Thread-0正在进行读操作
Thread-0正在进行读操作
Thread-0正在进行读操作
Thread-0正在进行读操作
Thread-0正在进行读操作
Thread-0正在进行读操作
Thread-0正在进行读操作
Thread-0正在进行读操作
Thread-0正在进行读操作
Thread-0正在进行读操作
Thread-0正在进行读操作
Thread-0正在进行读操作
Thread-0正在进行读操作
Thread-0正在进行读操作
Thread-0正在进行读操作
Thread-0正在进行读操作
Thread-0正在进行读操作
Thread-0正在进行读操作
Thread-0正在进行读操作
Thread-0正在进行读操作
Thread-0正在进行读操作
Thread-0正在进行读操作
Thread-0正在进行读操作
Thread-0正在进行读操作
Thread-0正在进行读操作
Thread-0正在进行读操作
Thread-0正在进行读操作
Thread-0正在进行读操作
Thread-0正在进行读操作
Thread-0正在进行读操作
Thread-0正在进行读操作
Thread-0正在进行读操作
Thread-0正在进行读操作
Thread-0正在进行读操作
Thread-0正在进行读操作
Thread-0正在进行读操作
Thread-0正在进行读操作
Thread-0正在进行读操作
Thread-0正在进行读操作
Thread-0正在进行读操作
Thread-0正在进行读操作
Thread-0正在进行读操作
Thread-0正在进行读操作
Thread-0读操作完毕
Thread-1正在进行读操作
Thread-1正在进行读操作
Thread-1正在进行读操作
Thread-1正在进行读操作
Thread-1正在进行读操作
Thread-1正在进行读操作
Thread-1正在进行读操作
Thread-1正在进行读操作
Thread-1正在进行读操作
Thread-1正在进行读操作
Thread-1正在进行读操作
Thread-1正在进行读操作
Thread-1正在进行读操作
Thread-1正在进行读操作
Thread-1正在进行读操作
Thread-1正在进行读操作
Thread-1正在进行读操作
Thread-1正在进行读操作
Thread-1正在进行读操作
Thread-1正在进行读操作
Thread-1正在进行读操作
Thread-1正在进行读操作
Thread-1正在进行读操作
Thread-1正在进行读操作
Thread-1正在进行读操作
Thread-1正在进行读操作
Thread-1正在进行读操作
Thread-1正在进行读操作
Thread-1正在进行读操作
Thread-1正在进行读操作
Thread-1正在进行读操作
Thread-1正在进行读操作
Thread-1正在进行读操作
Thread-1正在进行读操作
Thread-1正在进行读操作
Thread-1正在进行读操作
Thread-1正在进行读操作
Thread-1正在进行读操作
Thread-1正在进行读操作
Thread-1正在进行读操作
Thread-1正在进行读操作
Thread-1正在进行读操作
Thread-1正在进行读操作
Thread-1正在进行读操作
Thread-1正在进行读操作
Thread-1正在进行读操作
Thread-1正在进行读操作
Thread-1正在进行读操作
Thread-1正在进行读操作
Thread-1正在进行读操作
Thread-1正在进行读操作
Thread-1正在进行读操作
Thread-1正在进行读操作
Thread-1正在进行读操作
Thread-1正在进行读操作
Thread-1正在进行读操作
Thread-1正在进行读操作
Thread-1正在进行读操作
Thread-1正在进行读操作
Thread-1正在进行读操作
Thread-1正在进行读操作
Thread-1正在进行读操作
Thread-1正在进行读操作
Thread-1正在进行读操作
Thread-1正在进行读操作
Thread-1正在进行读操作
Thread-1正在进行读操作
Thread-1正在进行读操作
Thread-1正在进行读操作
Thread-1正在进行读操作
Thread-1正在进行读操作
Thread-1正在进行读操作
Thread-1正在进行读操作
Thread-1正在进行读操作
Thread-1正在进行读操作
Thread-1正在进行读操作
Thread-1正在进行读操作
Thread-1正在进行读操作
Thread-1正在进行读操作
Thread-1正在进行读操作
Thread-1正在进行读操作
Thread-1正在进行读操作
Thread-1正在进行读操作
Thread-1正在进行读操作
Thread-1正在进行读操作
Thread-1正在进行读操作
Thread-1读操作完毕

如果改为读写锁:

class TestReadWriteLock{
    private ReentrantReadWriteLock rwl = new ReentrantReadWriteLock();
    public static void main(String[] args) {
        final TestReadWriteLock test = new TestReadWriteLock();
        new Thread(){
            public void run(){
                test.get2(Thread.currentThread());
            }
        }.start();
        new Thread(){
            public void run(){
                test.get2(Thread.currentThread());
            }
        }.start();
    }
    public synchronized void get(Thread thread){
        long start = System.currentTimeMillis();
        while (System.currentTimeMillis()-start<=1){
            System.out.println(thread.getName()+"正在进行读操作");
        }
        System.out.println(thread.getName()+"读操作完毕");
    }
    public void get2(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();
        }
    }
}

此时打印的结果是:

Thread-0正在进行读操作
Thread-0正在进行读操作
Thread-0正在进行读操作
Thread-0正在进行读操作
Thread-0正在进行读操作
Thread-0正在进行读操作
Thread-0正在进行读操作
Thread-0正在进行读操作
Thread-0正在进行读操作
Thread-0正在进行读操作
Thread-0正在进行读操作
Thread-0正在进行读操作
Thread-0正在进行读操作
Thread-0正在进行读操作
Thread-0正在进行读操作
Thread-0正在进行读操作
Thread-0正在进行读操作
Thread-0正在进行读操作
Thread-0正在进行读操作
Thread-0正在进行读操作
Thread-0正在进行读操作
Thread-0正在进行读操作
Thread-0正在进行读操作
Thread-0正在进行读操作
Thread-0正在进行读操作
Thread-0正在进行读操作
Thread-0正在进行读操作
Thread-0正在进行读操作
Thread-1正在进行读操作
Thread-0正在进行读操作
Thread-1正在进行读操作
Thread-0正在进行读操作
Thread-1正在进行读操作
Thread-0正在进行读操作
Thread-1正在进行读操作
Thread-0正在进行读操作
Thread-1正在进行读操作
Thread-0正在进行读操作
Thread-1正在进行读操作
Thread-0正在进行读操作
Thread-1正在进行读操作
Thread-0正在进行读操作
Thread-1正在进行读操作
Thread-0正在进行读操作
Thread-1正在进行读操作
Thread-0正在进行读操作
Thread-1正在进行读操作
Thread-0正在进行读操作
Thread-1正在进行读操作
Thread-0正在进行读操作
Thread-1正在进行读操作
Thread-0正在进行读操作
Thread-1正在进行读操作
Thread-0正在进行读操作
Thread-1正在进行读操作
Thread-0正在进行读操作
Thread-1正在进行读操作
Thread-0正在进行读操作
Thread-1正在进行读操作
Thread-0正在进行读操作
Thread-1正在进行读操作
Thread-0正在进行读操作
Thread-1正在进行读操作
Thread-0正在进行读操作
Thread-1正在进行读操作
Thread-1正在进行读操作
Thread-0正在进行读操作
Thread-1正在进行读操作
Thread-0读操作完毕
Thread-1正在进行读操作
Thread-1正在进行读操作
Thread-1正在进行读操作
Thread-1正在进行读操作
Thread-1正在进行读操作
Thread-1正在进行读操作
Thread-1正在进行读操作
Thread-1正在进行读操作
Thread-1正在进行读操作
Thread-1正在进行读操作
Thread-1正在进行读操作
Thread-1正在进行读操作
Thread-1正在进行读操作
Thread-1正在进行读操作
Thread-1正在进行读操作
Thread-1正在进行读操作
Thread-1正在进行读操作
Thread-1正在进行读操作
Thread-1正在进行读操作
Thread-1正在进行读操作
Thread-1正在进行读操作
Thread-1正在进行读操作
Thread-1正在进行读操作
Thread-1正在进行读操作
Thread-1正在进行读操作
Thread-1正在进行读操作
Thread-1正在进行读操作
Thread-1正在进行读操作
Thread-1正在进行读操作
Thread-1正在进行读操作
Thread-1正在进行读操作
Thread-1正在进行读操作
Thread-1正在进行读操作
Thread-1正在进行读操作
Thread-1正在进行读操作
Thread-1正在进行读操作
Thread-1正在进行读操作
Thread-1正在进行读操作
Thread-1正在进行读操作
Thread-1正在进行读操作
Thread-1读操作完毕

表示thread0和thread1同时读取。

这大大提高了读取操作的效率。

但是需要注意的是,如果一个线程已经占用了读锁,此时如果其他线程要申请写锁,申请写锁的线程会等待读锁被释放.

如果一个线程已经占用了写锁,此时如果其他线程申请写锁或读锁,则申请的线程会等待写锁被释放。

对ReentrantReadWriteLock类中其他方法感兴趣的朋友可以自行查阅API文档。

6.5 锁定和同步选项

总的来说,Lock 和 synchronized 有以下区别: 1)Lock 是一个接口,而synchronized 是Java 中的关键字,synchronized 是一种内置的语言实现; 2)synchronized发生当异常发生时,线程占用的锁会自动释放,不会造成死锁现象;当发生异常时,如果Lock没有通过unLock()主动释放锁,很可能会造成死锁现象,所以在使用Lock时需要在finally块中释放锁; 3)Lock可以让等待锁的线程响应中断,但synchronized不能。使用synchronized时,等待线程会永远等待,无法响应中断; 4) 通过Lock可以知道锁是否被成功获取,但是synchronized不能。 5)锁可以提高多线程读操作的效率。在性能方面,如果竞争资源不激烈,两者的性能差不多,但是当竞争资源非常激烈(即同时有大量线程竞争)时,性能锁远比同步好。因此,具体用途应根据情况选择。

7.锁的相关概念介绍

Lock 的基本用法前面已经介绍过了。本节介绍与锁相关的几个概念。

7.1.可重入锁

如果锁是可重入的,则称它是可重入的。就像 synchronized 和 ReentrantLock 都是可重入锁一样,在我看来,重入实际上是指锁分配的机制:基于线程的分配,而不是基于方法调用的分配。举个简单的例子,当一个线程执行一个同步方法时,比如method1,在method1中又调用了另一个同步方法method2。此时线程不需要再次申请锁同步代码块的锁是什么,直接执行方法method2即可。

看下面的代码就明白了:

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

上面代码中的method1和method2这两个方法都是用synchronized修改的。如果在某个时刻,线程A执行了method1,线程A获取到了这个对象的锁,并且由于method2也是一个同步方法,如果同步没有重入,此时线程A需要重新申请锁但是这样会带来一个问题,因为线程A已经持有对象的锁,并且正在申请获取对象的锁,所以线程A会一直等待永远不会被获取的锁。

由于synchronized和Lock都是可重入的,所以不会出现上述现象。

7.2.可中断锁

可中断锁:顾名思义就是可以相应中断的锁。

在Java中,synchronized不是可中断锁,而Lock是可中断锁。

如果一个线程A正在执行锁中的代码,而另一个线程B正在等待获取锁,可能是因为等待时间过长同步代码块的锁是什么,线程B不想等待,想先处理其他事情,我们可以让它自己中断或者在另一个线程中中断,这就是可中断锁。

Lock的可中断性在之前的lockInterruptibly()的使用演示中已经演示过了。

7.3.公平锁定

公平锁尝试按照请求的顺序获取锁。例如,有多个线程同时在等待一个锁。当锁被释放时,等待时间最长的线程(最先请求的线程)将获得锁。这是一个公平的锁。

不公平锁定意味着不能保证按照请求锁的顺序获取锁。这可能会导致一个或一些线程永远无法获得锁。

在Java中,synchronized是一个不公平的锁,它不能保证等待线程获取锁的顺序。

对于ReentrantLock和ReentrantReadWriteLock,默认为非公平锁,但可以设置为公平锁。

看这两个类的源码就清楚了:

  /**
     * 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();
    }
 /**
     * 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中定义了两个静态内部类,一个是NotFairSync,一个是FairSync,分别用于实现不公平锁和公平锁。

我们在创建ReentrantLock对象时,可以通过以下方式设置锁的公平性: ReentrantLock lock =new ReentrantLock(true);如果参数为true,则表示公平锁,如果为fasle,则表示不公平锁。默认情况下,如果使用无参数构造函数,则为不公平锁。

另外,ReentrantLock类中定义了很多方法,比如:

isFair() //判断锁是否为公平锁

isLocked() //判断锁是否已经被任何线程获取

isHeldByCurrentThread() //判断锁是否被当前线程获取

hasQueuedThreads() //判断是否有线程等待锁

ReentrantReadWriteLock 里面也有类似的方法,也可以设置公平锁和不公平锁。但是请记住,ReentrantReadWriteLock 没有实现 Lock 接口,它实现了 ReadWriteLock 接口。

7.4.读写锁

读写锁将对资源(如文件)的访问分为两种锁,一个读锁和一个写锁。

由于读写锁,多线程之间的读操作不会冲突。

ReadWriteLock是读写锁,是一个接口,ReentrantReadWriteLock实现了这个接口。

读锁可以通过readLock()获取,写锁可以通过writeLock()获取。

读写锁的使用上面已经演示过了,这里不再赘述。

© 版权声明
THE END
喜欢就支持一下吧
点赞0
分享
评论 抢沙发

请登录后发表评论