并发编程-锁

it2025-10-28  5

ReentrantLockReentrantLock实现wait和notiftyCondition接口来实现多路通知tryLock和TryLock(time, uint)公平锁和非公平锁ReadWriteLock读写锁(写加锁,读不需要)LockSupport读写锁(写加锁,读不需要)

ReentrantLock

在Java多线程中,可以使用synchronized关键字来实现线程之间的同步互斥,但在JDK1.5中新增加ReentranLock类也可以达到同样的效果,并且在扩展功能上更加强大,比如具有嗅探锁定、多路分支通知等,而且在使用上也比synchronized更加灵活。从运行结果来看,当前线程打印完毕之后将锁进行释放,其它线程才可以继续 打印。线程打印的数组是分级打印,因为当前线程已经持有锁,但线程之间打印的顺序是随机的。调用lock方法的线程就会持有对象锁,其它的线程只能等待锁被释放(调用unlock方法)才可以再次争抢锁。效果和synchronized关键字一样,线程之间还是按照顺序执行

public class TestReentrantLock { private Lock lock; public TestReentrantLock(Lock lock) { this.lock = lock; } public void printNumber() { try { lock.lock(); for (int i = 0; i <= 6; i++) { System.out.println(Thread.currentThread().getName() + ":" + i); } } catch (Exception e) { e.printStackTrace(); } finally { lock.unlock(); } } public static void main(String[] args) { Lock lock = new ReentrantLock(); TestReentrantLock reentrantLock = new TestReentrantLock(lock); for (int i = 0; i < 5; i++) { new Thread(() -> { reentrantLock.printNumber(); }).start(); } } }

ReentrantLock实现wait和notifty

ReentrantLock 实现了 Lock 接口,并提供了与synchronized 相同的互斥性和内存可见性,ReentrantLock也可以实现等待/通知模式的功能,但需要借助Condition对象。Condition类是在JDK5中出现的技术。synchronized就相当于整个Lock对象中只有一个单一Condition对象,所有的线程都是注册在它一个对象的身上。线程开始notifyAll时需要通知所有正在等待的线程,没有选择权,会出现相当大效率问题。

public class TestReentrantLock { public static void main(String[] args) throws InterruptedException { ReentrantLockService service = new ReentrantLockService(); Thread thread = new AwaitThread(service); thread.start(); TimeUnit.SECONDS.sleep(3); service.signal(); } } class ReentrantLockService { private Lock lock = new ReentrantLock(); private Condition condition = lock.newCondition(); public void await() { lock.lock(); System.out.println(Thread.currentThread().getName() + "begin await:" + System.currentTimeMillis()); try { condition.await(); } catch (InterruptedException e) { e.printStackTrace(); } lock.unlock(); } public void signal() { lock.lock(); System.out.println(Thread.currentThread().getName() + "begin signal:" + System.currentTimeMillis()); condition.signal(); lock.unlock(); } } class AwaitThread extends Thread { private ReentrantLockService service; public AwaitThread(ReentrantLockService service) { this.service = service; } @Override public void run() { service.await(); } }

Condition接口来实现多路通知

在一个Lock对象里面可以创建多个Condition实例(Condition 实例实质上被绑定到一个锁上。要为特定 Lock 实例获得Condition 实例,使用其 newCondition() 方法),线程对象可以注册在指定的Condition中,从而可以有选择性地进行线程通知,在调试线程上更加灵活。在使用notify/notifyAll方法进行通知时,被通知线程是由JVM随机选择的,但使用ReentrantLock结合Condition类可以实现选择性通知,这个功能是非常重要的,而且在Condition在中是默认提供的,单个Lock可能与多个Condition对象关联

public class TestMultiReentrantLock { public static void main(String[] args) throws InterruptedException { MultiReentrantLockService service = new MultiReentrantLockService(); new Thread((service::await1)).start(); new Thread((service::await2)).start(); TimeUnit.SECONDS.sleep(5); service.signal(); } } class MultiReentrantLockService { private ReentrantLock lock = new ReentrantLock(); private Condition condition1 = lock.newCondition();//单个Lock可能与多个Condition对象关联 private Condition condition2 = lock.newCondition();//单个Lock可能与多个Condition对象关联 public void await1() { try { lock.lock(); System.out.println(Thread.currentThread().getName() + " begin await1..."); condition1.await(); System.out.println(Thread.currentThread().getName() + " signal await1..."); } catch (InterruptedException e) { e.printStackTrace(); } finally { lock.unlock(); } } public void await2() { try { lock.lock(); System.out.println(Thread.currentThread().getName() + " begin await2..."); condition2.await(); System.out.println(Thread.currentThread().getName() + " signal await2..."); } catch (InterruptedException e) { e.printStackTrace(); } finally { lock.unlock(); } } public void signal() { try { lock.lock(); condition1.signalAll(); System.out.println(Thread.currentThread().getName() + " begin signal..."); } catch (Exception e) { e.printStackTrace(); } finally { lock.unlock(); } } }

PS: 在 Condition 对象中,与 wait、notify 和 notifyAll 方法对应的分别是await、signal 和 signalAll

面试题:开启3个线程,这三个线程的Name分别为A、B、C,每个线程将自己的Name在屏幕上打印5遍

public class TestReentrantLock { public static void main(String[] args) { Print print = new Print(); new Thread(()->{ for (int i = 0; i < 5; i++) { print.printA(); } }, "A").start(); new Thread(()->{ for (int i = 0; i < 5; i++) { print.printB(); } }, "B").start(); new Thread(()->{ for (int i = 0; i < 5; i++) { print.printC(); } }, "C").start(); } } class Print { private String flag = "A"; final ReentrantLock lock = new ReentrantLock(); final Condition conditionA = lock.newCondition(); final Condition conditionB = lock.newCondition(); final Condition conditionC = lock.newCondition(); public void printA() { try { lock.lock(); if (!"A".equals(flag)) { conditionA.await(); } System.out.print(Thread.currentThread().getName()); flag = "B"; conditionB.signal(); } catch (InterruptedException e) { e.printStackTrace(); } finally { lock.unlock(); } } public void printB() { try { lock.lock(); if (!"B".equals(flag)) { conditionB.await(); } System.out.print(Thread.currentThread().getName()); flag = "C"; conditionC.signal(); } catch (InterruptedException e) { e.printStackTrace(); } finally { lock.unlock(); } } public void printC() { try { lock.lock(); if (!"C".equals(flag)) { conditionC.await(); } System.out.print(Thread.currentThread().getName()); flag = "A"; conditionA.signal(); } catch (InterruptedException e) { e.printStackTrace(); } finally { lock.unlock(); } } }

tryLock和TryLock(time, uint)

public class TestTryLock { public static void main(String[] args) { TryLockService tryLockService = new TryLockService(); Thread A = new Thread(tryLockService::tryLock); A.setName("A"); A.start(); Thread B = new Thread(() -> tryLockService.tryLockTime(4L)); B.setName("B"); B.start(); } } class TryLockService { final ReentrantLock lock = new ReentrantLock(); public void tryLock() { System.out.println(Thread.currentThread().getName() + " start....."); if (lock.tryLock()) { System.out.println(Thread.currentThread().getName() + " 获取到锁!"); } else { System.out.println(Thread.currentThread().getName() + " 未获取到锁!"); } try { TimeUnit.SECONDS.sleep(3); } catch (InterruptedException e) { e.printStackTrace(); } finally { System.out.println(Thread.currentThread().getName() + " 释放锁!!"); lock.unlock(); } } public void tryLockTime(Long time) { try { System.out.println(Thread.currentThread().getName() + " start....."); if (lock.tryLock(time, TimeUnit.SECONDS)) { System.out.println(Thread.currentThread().getName() + " 获取到锁!"); } else { System.out.println(Thread.currentThread().getName() + " 未获取到锁!"); } } catch (InterruptedException e) { e.printStackTrace(); } finally { System.out.println(Thread.currentThread().getName() + " 释放锁!!"); lock.unlock(); } } }

公平锁和非公平锁

在java的锁机制中,公平和非公平的参考物是什么,简单的来说,如果一个线程组里,能保证每个线程都能拿到锁,那么这个锁就是公平锁。相反,如果保证不了每个线程都能拿到锁,也就是存在有线程饿死,那么这个锁就是非公平锁。 那如何能保证每个线程都能拿到锁呢,队列FIFO是一个完美的解决方案,也就是先进先出,java的ReenTrantLock也就是用队列实现的公平锁和非公平锁。在公平的锁中,如果有另一个线程持有锁或者有其他线程在等待队列中等待这个锁,那么新发出的请求的线程将被放入到队列中。而非公平锁上,只有当锁被某个线程持有时,新发出请求的线程才会被放入队列中(此时和公平锁是一样的)。所以,它们的差别在于非公平锁会有更多的机会去抢占锁。

public class TestReentrantLock { public static void main(String[] args) { TestReentrantLockService t1 = new TestReentrantLockService(); for (int i = 0; i < 50; i++) { new Thread(t1).start(); } } } class TestReentrantLockService extends Thread { final ReentrantLock lock = new ReentrantLock(true); //true公平锁,false非公平锁 @Override public void run() { for (int i = 0; i < 50; i++) { try { lock.lock(); System.out.println(Thread.currentThread().getName() + "获得锁:" + i); } catch (Exception e) { e.printStackTrace(); } finally { lock.unlock(); } } } }

ReadWriteLock读写锁(写加锁,读不需要)

并发读写操作保证安全的常规解决办法是在读写操作上加入互斥锁,这种情况下并发读写效率会打折扣,因为大多数情况下我们对同一数据读操作的频率会高于写操作,而线程与线程间的并发读操作是不涉及并发安全问题的,所以没有必要给读操作加互斥锁,只要保证读写、写写并发操作上锁是互斥的就行。 ReadWriteLock 维护了一对相关的锁,一个用于只读操作,另一个用于写入操作。只要没有 writer,读取锁可以由多个 reader 线程同时保持。写入锁是独占的,ReadWriteLock 读取操作通常不会改变共享资源,但执行写入操作时,必须独占方式来获取锁。对于读取操作占多数的数据结构。 ReadWriteLock 能提供比独占锁更高的并发性。而对于只读的数据结构,其中包含的不变性可以完全不需要考虑加锁操作。 上面基本都废话,主要核心: “读-读” 不互斥 “读-写” 互斥 “写-写” 互斥

public class TestReadWriteLock { public static void main(String[] args) { ReadWriteLockService service = new ReadWriteLockService(); for (int i = 0; i < 20; i++) { char c = (char) ('A' + i); new Thread(() -> { service.read(); }, "A").start(); new Thread(() -> { service.write(); }, "B").start(); } } } class ReadWriteLockService { private String value = ""; final ReadWriteLock lock = new ReentrantReadWriteLock(); final Lock readLock = lock.readLock(); final Lock writeLock = lock.writeLock(); public void read() { try { readLock.lock(); TimeUnit.SECONDS.sleep(1); System.out.println(Thread.currentThread().getName() + " read..." + value); } catch (InterruptedException e) { e.printStackTrace(); }finally { readLock.unlock(); } } public void write() { try { writeLock.lock(); TimeUnit.SECONDS.sleep(1); value = System.currentTimeMillis() + ""; System.out.println(Thread.currentThread().getName() + " write..." + value); } catch (InterruptedException e) { e.printStackTrace(); }finally { writeLock.unlock(); } } }

LockSupport读写锁(写加锁,读不需要)

LockSupport 不需要加锁就可以实现阻塞和唤醒如果一个线程处理等待状态,连续调用了两次park方法。线程永远无法被唤醒unpark 和 park 方法不需要考虑调用先后顺序 public class TestLockSupport { public static void main(String[] args) { Thread thread = new Thread(() -> { for (int i = 0; i < 20; i++) { try { if (i == 3) { LockSupport.park(); } System.out.println(i); TimeUnit.MILLISECONDS.sleep(200); } catch (InterruptedException e) { e.printStackTrace(); } } }, "A"); thread.start(); LockSupport.unpark(thread); } }
最新回复(0)