java并发lock锁详解和使用

43次阅读

共计 17885 个字符,预计需要花费 45 分钟才能阅读完成。

锁是用于通过多个线程控制对共享资源的访问的工具,通常锁提供对共享资源的独占访问,一次只能有一个线程可以获取锁,并且对共享资源的所有访问都要求首先获取锁。但是,一些锁可能允许并发访问共享资源,如 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…

正文完
 0