在Java中,除了使用synchronized关键字实现线程同步,还可以使用java.util.concurrent.locks包下的重入锁(ReentrantLock)来实现同步。今天我们就来学习ReentrantLock同步。
以下是本文包含的知识点:
1.Lock接口介绍
2.ReentrantLock的使用
3.ReentrantLock与synchronized实现同步的区别
4.ReadWriteLock介绍
5.Condition使用
一、Lock接口介绍
ReentrantLock实现自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(),lockInterruptibly(),tryLock(),tryLock(long time, TimeUnit unit)都是用来获取锁的,区别如下:
lock():获取锁。如果锁可用,则获取锁,否则进行等待。
tryLock():如果锁可用,则获取锁,并立即返回值 true,否则返回 false,不会等待。
tryLock(long time, TimeUnit unit):如果在给定的等待时间内,锁可用,则获取锁,并立即返回值 true,否则返回false。
lockInterruptibly():获取锁,等待可中断。如果锁可用,并且当前线程未被中断,则获取锁,否则进行等待。如果被其它线程中断,则抛出InterruptException异常。
unlock()用来释放锁,newCondition()后面再讲。
因为采用Lock获取锁,必须手动释放锁,并且在发生异常时也不会自动释放锁,所以lock()/unlock()一般配合try/finally语句块来完成,通常使用形式如下:
Lock lock = ...; lock.lock();//获取锁 try{ //处理任务 }catch(Exception e){ }finally{ lock.unlock();//释放锁 }
二、ReentrantLock的使用
ReentrantLock除了实现Serializable接口外,唯一实现Lock接口,下面来看看它的用法:
public class ReentrantLockTest { private Lock lock = new ReentrantLock(); //lock要定义为成员变量,第个线程都共享这个锁 private List<Integer> arrList = new ArrayList<Integer>(); public static void main(String[] args) { final ReentrantLockTest test = new ReentrantLockTest(); new Thread(){ public void run(){ test.testLock(Thread. currentThread()); } }.start(); new Thread(){ public void run(){ test.testLock(Thread. currentThread()); } }.start(); } /** * lock方法 * @param thread */ public void testLock(Thread thread){ //Lock lock = new ReentrantLock();//注意这里,并不会达到互斥的效果 //因为在testLock 方法中的lock变量是局部变量,每个线程执行该方法时都会保存一个副本,那么理所当然每个线程执行到lock.lock()处获取的是不同的锁,所以就不会发生冲突。解决办法:变成全局的,所有线程共享 lock.lock();//lock()方法是平常使用得最多的一个方法,就是用来获取锁。如果锁已被其他线程获取,则进行等待。 try { System. out.println(thread.getName()+"得到了锁" ); for(int i=0; i<5; i++){ arrList.add(i); } } catch (Exception e) { e.printStackTrace(); } finally{ System. out.println(thread.getName()+"释放了锁" ); lock.unlock();//unlock总是与lock成对出现,且一般采用try,catch,finally的方式 } } /** * tryLock方法 * tryLock(long time, TimeUnit unit)方法 * @param thread */ public void testTryLock(Thread thread){ if(lock .tryLock()){//tryLock()方法是有返回值的,它表示用来尝试获取锁,如果获取成功,则返回true,如果获取失败(即锁已被其他线程获取),则返回false,也就说这个方法无论如何都会立即返回。在拿不到锁时不会一直在那等待。 //tryLock(long time, TimeUnit unit)方法和tryLock()方法是类似的,只不过区别在于这个方法在拿不到锁时会等待一定的时间,在时间期限之内如果还拿不到锁,就返回false。如果如果一开始拿到锁或者在等待期间内拿到了锁,则返回true。 try { System. out.println(thread.getName()+"得到了锁" ); for(int i=0; i<5; i++){ arrList.add(i); } } catch (Exception e) { e.printStackTrace(); } finally{ System. out.println(thread.getName()+"释放了锁" ); lock.unlock(); } } else{ System. out.println(thread.getName()+"获取锁失败" ); } } }执行结果:
Thread-0得到了锁 Thread-0释放了锁 Thread-1得到了锁 Thread-1释放了锁可以看到实现了同步
lockInterruptibly():获取锁,等待可中断实例:
public class TestLockInterruptibly { private Lock lock = new ReentrantLock(); public static void main(String[] args) { TestLockInterruptibly test = new TestLockInterruptibly(); MyThread thread1 = new MyThread(test); MyThread thread2 = new MyThread(test); thread1.start(); thread2.start(); try { Thread. sleep(3000); } catch (InterruptedException e) { e.printStackTrace(); } thread2.interrupt(); } public void testLockInterruptibly(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()+"释放了锁" ); } } } class MyThread extends Thread { private TestLockInterruptibly test = null ; public MyThread(TestLockInterruptibly test) { this.test = test; } @Override public void run() { try { test.testLockInterruptibly(Thread.currentThread()); } catch (InterruptedException e) { System. out.println(Thread.currentThread().getName()+ "被中断"); } } }执行结果:
Thread-0得到了锁 Thread-1被中断
可以看到thread2 被中断了
三、ReentrantLock与synchronized实现同步的区别
相同点:
1.实现同步功能
2.都具有线程重入性
不同点:
1.代码写法上,ReentrantLock为API层面互斥锁,synchronized为原生语法层面互斥锁。
2.功能上,ReentrantLock比synchronized提高了更高级的功能:等待可中断,可实现公平锁,锁可以绑定多个条件。
等待可中断是指当持有锁的线程长期不释放锁的时候,正在等待的线程可以选择放弃等待。可中断特性对执行时间非常长的同步块很有帮助。Lock的lockInterruptibly()方法就是等待可中断实现。
公平锁是指多个线程在等待同一个锁时,必须按申请锁的时间顺序依次获取锁;而非公平锁则不能保证这一点,在锁被释放的时候,任何一个线程都有机会获得锁。synchronized是非公平锁,ReentrantLock默认是非公平锁,但可以通过带布尔值的构造方法要求使用公平锁。
ReentrantLock构造器源码:
//默认为非公平锁 public ReentrantLock() { sync = new NonfairSync(); } //可以通过带布尔值的构造方法要求使用公平锁 public ReentrantLock(boolean fair) { sync = fair ? new FairSync() : new NonfairSync(); }锁可以绑定多个条件是指一个ReentrantLock对象可以绑定多个Condition对象,而在synchronized中,锁对象的wait()和notify()或notifyAll()可以实现一个隐含的条件,如果要和多于一个的条件关联,就不得不额外的添加一个锁,而ReentrantLock则无须这样做,只需多次调用newCondition()方法而已。
3.性能上,Jdk6加入了很多针对锁的优化,synchronized与ReentrantLock的性能基本上完全持平了。性能因素不再是选择ReentrantLock的理由,虚拟机未来在性能改进中肯定也会更加偏向于原生的synchronized,所以还是优先考虑使用synchrozied进行同步。
四、ReadWriteLock介绍
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 维护了一对相关的锁,一个用于只读操作,另一个用于写入操作。只要没有 writer,读取锁可以由多个 reader 线程同时保持。写入锁是独占的。
ReentrantReadWriteLock类实现了ReadWriteLock接口。我们来看下多个线程同时获取读取锁的情况:
public class ReadWriteLockTest { private ReadWriteLock readWriteLock = new ReentrantReadWriteLock(); public static void main(String[] args) { final ReadWriteLockTest test = new ReadWriteLockTest(); new Thread(){ public void run(){ test.get2(Thread. currentThread()); } }.start(); new Thread(){ public void run(){ test.get2(Thread. currentThread()); } }.start(); } /** * readLock方法:使用多个线程同时执行 * @param thread */ public void get2(Thread thread) { readWriteLock.readLock().lock(); try { long start = System.currentTimeMillis(); while(System.currentTimeMillis() - start <= 1) { System. out.println(thread.getName()+"正在进行读操作" ); } System. out.println(thread.getName()+"读操作完毕" ); } finally { readWriteLock.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-1正在进行读操作 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读操作完毕根据结果可以看到,两个线程在并发执行,说明读取锁(ReadLock)可以被多个线程同时获得,这也符合实际应用场景。提高读操作的效率。
不过要注意的是,如果有一个线程已经占用了读锁,则此时其他线程如果要申请写锁,则申请写锁的线程会一直等待释放读锁。
如果有一个线程已经占用了写锁,则此时其他线程如果申请写锁或者读锁,则申请的线程会一直等待释放写锁。
四、Condition使用
synchronized与wait()和nitofy()/notifyAll()方法相结合可以实现等待/通知模型,ReentrantLock同样可以,但是需要借助Condition,且Condition有更好的灵活性,具体体现在:
1、一个Lock里面可以创建多个Condition实例,实现多路通知
2、notify()方法进行通知时,被通知的线程时Java虚拟机随机选择的,但是ReentrantLock结合Condition可以实现有选择性地通知,这是非常重要的
看一下利用Condition实现等待/通知模型的最简单用法,下面的代码注意一下,await()和signal()之前,必须要先lock()获得锁,使用完毕在finally中unlock()释放锁,这和wait()/notify()/notifyAll()使用前必须先获得对象锁是一样的:
import java.util.PriorityQueue; import java.util.concurrent.locks.Condition; import java.util.concurrent.locks.Lock; import java.util.concurrent.locks.ReentrantLock; public class ConditionTest { private int queueSize = 10; private PriorityQueue<Integer> queue = new PriorityQueue<Integer>(queueSize); private Lock lock = new ReentrantLock(); private Condition notFull = lock.newCondition(); private Condition notEmpty = lock.newCondition(); public static void main(String[] args) { ConditionTest test = new ConditionTest(); Producer producer = test.new Producer(); Consumer consumer = test.new Consumer(); producer.start(); consumer.start(); } class Consumer extends Thread { @Override public void run() { consume(); } private void consume() { while (true) { lock.lock(); try { while (queue.size() == 0) { try { System.out.println("队列空,等待数据"); notEmpty.await(); } catch (InterruptedException e) { e.printStackTrace(); } } queue.poll(); // 每次移走队首元素 notFull.signal(); System.out.println("从队列取走一个元素,队列剩余" + queue.size() + "个元素"); } finally { lock.unlock(); } } } } class Producer extends Thread { @Override public void run() { produce(); } private void produce() { while (true) { lock.lock(); try { while (queue.size() == queueSize) { try { System.out.println("队列满,等待有空余空间"); notFull.await(); } catch (InterruptedException e) { e.printStackTrace(); } } queue.offer(1); // 每次插入一个元素 notEmpty.signal(); System.out.println("向队列取中插入一个元素,队列剩余空间:" + (queueSize - queue.size())); } finally { lock.unlock(); } } } } }
Condition的await()方法是释放锁的,原因也很简单,要是await()方法不释放锁,那么signal()方法又怎么能调用到Condition的signal()方法呢?
注意要是用一个Condition的话,那么多个线程被该Condition给await()后,调用Condition的signalAll()方法唤醒的是所有的线程。如果想单独唤醒部分线程该怎么办呢?new出多个Condition就可以了,这样也有助于提升程序运行的效率。使用多个Condition的场景是很常见的,像ArrayBlockingQueue里就有。参考
《深入Java 虚拟机》
相关推荐
2. **同步机制**:详细解析了Java中的同步工具,如`synchronized`关键字、volatile变量、java.util.concurrent包中的Lock接口及其实现,如ReentrantLock,以及Condition等。同时,还会探讨如何正确地使用这些机制...
在Java编程领域,并发编程是一项核心技能,尤其是在大型系统或分布式应用中,高效地处理多线程和并发操作是至关重要的。"Java并发编程与实践"文档深入剖析了这一主题,旨在帮助开发者理解和掌握如何在Java环境中有效...
Java并发编程是Java语言中最为复杂且重要的部分之一,它涉及了多线程编程、内存模型、同步机制等多个领域。为了深入理解Java并发编程,有必要了解其核心技术点和相关实现原理,以下将详细介绍文件中提及的关键知识点...
在Java并发编程中,理解和熟练使用同步机制是至关重要的,这包括了`ReentrantLock`和`synchronized`关键字。这两个工具都是为了确保多线程环境中的数据一致性与安全性,防止出现竞态条件和死锁等问题。 `...
3. **并发集合**:Java提供了一系列线程安全的集合,如`ConcurrentHashMap`、`CopyOnWriteArrayList`等,它们在多线程环境下提供了高效的操作。 4. **原子类**:`java.util.concurrent.atomic`包中的原子类,如`...
4. **并发集合**:Java并发集合库(java.util.concurrent包)提供了一系列线程安全的数据结构,如ConcurrentHashMap、CopyOnWriteArrayList、BlockingQueue等,它们在并发环境下具有高性能和高安全性。 5. **原子...
2. **同步机制**:Java提供了多种同步工具,如synchronized关键字、wait()、notify()和notifyAll()方法,以及Lock接口(如ReentrantLock)和Condition接口。这些机制用于控制对共享资源的访问,防止数据不一致性。 ...
在Java编程中,并发是指程序中存在多个执行线程,这些线程可以同时进行工作,以提高系统的整体处理能力。Java平台提供了丰富的并发工具和API,如线程(Thread)、守护线程(Daemon Thread)、线程池...
2. **同步机制**:Java提供了多种同步机制来确保线程安全,包括synchronized关键字、volatile变量、Lock接口(如ReentrantLock)以及Condition等。synchronized提供了方法和块级别的锁,而volatile则保证了变量的...
总之,`ReentrantLock`是Java并发编程中的强大工具,它提供了丰富的功能和灵活性,使得开发者能够根据具体需求调整锁的行为,提高并发代码的性能和安全性。在设计和实现多线程程序时,了解和正确使用`ReentrantLock`...
Java并发编程中的显示锁,即ReentrantLock,是Java 5.0引入的重要特性,它为多线程环境提供了更为灵活的控制。ReentrantLock是一个可重入的锁,这意味着一个线程可以多次获取同一锁,这与synchronized内置锁的行为...
- **Lock接口**:介绍ReentrantLock、Condition、读写锁等高级同步工具,及其与synchronized的区别。 3. **并发集合** - **线程安全的集合**:如Vector、Collections.synchronizedXXX方法生成的同步集合,以及...
Java并发编程是一个重要的主题,特别是在多线程应用中,它能极大地提高程序的效率和响应性。本资源主要探讨了Java并发的新特性,特别是Lock锁和条件变量的使用。下面将详细阐述这两个概念以及如何在实际编程中应用...
高并发编程第三阶段04讲 利用CAS构造一个TryLock自定义显式锁-增强并发情况下.mp4 高并发编程第三阶段05讲 AtomicBoolean源码分析.mp4 高并发编程第三阶段06讲 AtomicLong源码分析.mp4 高并发编程第三阶段07...
详细讲解了Java中多种同步机制,包括wait()、notify()和notifyAll()方法,Lock接口以及相关的ReentrantLock、Condition等高级同步工具。 第七章:并发工具 介绍其他并发工具,如Semaphore(信号量)、CyclicBarrier...
3. **同步机制**:Java提供了多种同步机制,包括`synchronized`关键字、`Lock`接口及其实现(如`ReentrantLock`)、`Semaphore`信号量、`CyclicBarrier`和`CountDownLatch`等。这些机制用于控制并发访问资源,防止...
《Java并发编程实践-电子书-07章》深入探讨了显示锁的概念,特别是`Lock`和`ReentrantLock`接口。通过理解和应用这些高级锁机制,开发人员可以构建更高效、更健壮的并发应用程序。这些锁提供了更细粒度的控制,允许...
Java并发编程中的JUC(Java Util Concurrent)库是开发者处理多线程问题的关键工具,尤其在多核处理器的环境中,其重要性不言而喻。JUC库包含了一系列的类和接口,帮助开发者构建高效、稳定的并发程序。下面将详细...
2. **并发基础**:Java并发编程的基础包括`Thread.start()`启动线程,`Thread.join()`等待线程结束,以及`Thread.sleep()`让当前线程暂停一段时间。 3. **同步机制**:Java提供了多种同步机制,如`synchronized`...
2. **同步机制**:Java提供了多种同步机制,包括`synchronized`关键字、`wait()`、`notify()`和`notifyAll()`方法,以及`Lock`接口(如`ReentrantLock`)和`Condition`接口。这些机制用于控制对共享资源的访问,避免...