Lock 显式锁基本特性
Lock⽅式来获取锁⽀持中断、超时不获取、是⾮阻塞的
提⾼了语义化 ,哪⾥加锁,哪⾥解锁都得写出来
Lock显式锁可以给我们带来很好的灵活性,但同时我们必须⼿动释放锁
⽀持Condition条件对象
允许多个读线程同时访问共享资源
ReentrantLock-可重入锁 ReentrantLock 的基本使用 调用 lock()方法获得锁, 调用unlock()释放锁
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 public class ReentrantLockTest { static Lock lock = new ReentrantLock(); public static void test () { lock.lock(); try { for (int i = 0 ; i < 100 ; i++) { System.out.println(Thread.currentThread().getName() + " -- " + i); } } finally { lock.unlock(); } } public static void main (String[] args) { Runnable r = () -> test(); new Thread(r).start(); new Thread(r).start(); new Thread(r).start(); } }
使用 Lock 锁同步不同方法中的同步代码块
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 public class ReentrantLockTest02 { static Lock lock = new ReentrantLock(); public static void test1 () { lock.lock(); try { System.out.println(Thread.currentThread().getName() + "-- test 1 -- " + System.currentTimeMillis()); Thread.sleep(new Random().nextInt(1000 )); System.out.println(Thread.currentThread().getName() + "-- test 1 -- " + System.currentTimeMillis()); } catch (InterruptedException e) { e.printStackTrace(); } finally { lock.unlock(); } } public static void test2 () { lock.lock(); try { System.out.println(Thread.currentThread().getName() + "-- test 2 -- " + System.currentTimeMillis()); Thread.sleep(new Random().nextInt(1000 )); System.out.println(Thread.currentThread().getName() + "-- test 2 -- " + System.currentTimeMillis()); } catch (InterruptedException e) { e.printStackTrace(); } finally { lock.unlock(); } } public static void main (String[] args) { Runnable r1 = () -> test1(); Runnable r2 = () -> test2(); new Thread(r1).start(); new Thread(r1).start(); new Thread(r1).start(); new Thread(r2).start(); new Thread(r2).start(); new Thread(r2).start(); } }
lockInterruptibly()方法 lockInterruptibly() 方法的作用:如果当前线程未被中断则获得锁,如果当前线程被中断则出现异常。
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 public class LockInterruptiblyTest { static class Service { private Lock lock = new ReentrantLock(); public void serviceMethod () { try { lock.lockInterruptibly(); System.out.println(Thread.currentThread().getName() + " ---> begin lock" ); for (int i = 0 ; i < Integer.MAX_VALUE; i++) { new StringBuilder(); } System.out.println(Thread.currentThread().getName() + " ---> end lock" ); } catch (Exception e) { e.printStackTrace(); } finally { lock.unlock(); System.out.println(Thread.currentThread().getName() + " ---> 释放锁" ); } } } public static void main (String[] args) throws InterruptedException { Service s = new Service(); Runnable r = () -> s.serviceMethod(); Thread t1 = new Thread(r); t1.start(); Thread.sleep(50 ); Thread t2 = new Thread(r); t2.start(); Thread.sleep(50 ); t2.interrupt(); } }
对于synchronized内部锁来说,如果一个线程在等待锁,只有两个结果:要么该线程获得锁继续执行;要么就保持等待。
对于 ReentrantLock可重入锁来说,提供另外一种可能,在等待锁的过程中,程序可以根据需要取消对锁的请求。
通过 ReentrantLock 锁的 lockInterruptibly()方法避免死锁的产生
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 public class LockInterruptiblyTest02 { static class IntLock implements Runnable { public static ReentrantLock lock1 = new ReentrantLock(); public static ReentrantLock lock2 = new ReentrantLock(); int lockNum; public IntLock (int lockNum) { this .lockNum = lockNum; } @Override public void run () { try { if (lockNum % 2 == 1 ) { lock1.lockInterruptibly(); System.out.println(Thread.currentThread().getName() + "获得锁1,还需 要获得锁 2" ); Thread.sleep(new Random().nextInt(500 )); lock2.lockInterruptibly(); System.out.println(Thread.currentThread().getName() + "同时获得了锁1与锁2...." ); } else { lock2.lockInterruptibly(); System.out.println(Thread.currentThread().getName() + "获得锁2,还需要获得锁1" ); Thread.sleep(new Random().nextInt(500 )); lock1.lockInterruptibly(); System.out.println(Thread.currentThread().getName() + "同时获得了锁1与锁2...." ); } } catch (InterruptedException e) { e.printStackTrace(); System.out.println(Thread.currentThread().getName() + " 中断了线程" ); } finally { if (lock1.isHeldByCurrentThread()) { lock1.unlock(); } if (lock2.isHeldByCurrentThread()) { lock2.unlock(); } System.out.println(Thread.currentThread().getName() + "线程退出" ); } } } public static void main (String[] args) throws InterruptedException { IntLock intLock1 = new IntLock(1 ); IntLock intLock2 = new IntLock(2 ); Thread t1 = new Thread(intLock1); Thread t2 = new Thread(intLock2); t1.start(); t2.start(); Thread.sleep(3000 ); if (t2.isAlive()) { t2.interrupt(); } } } Thread-0 获得锁1 ,还需 要获得锁2 Thread-1 获得锁2 ,还需要获得锁1 java.lang.InterruptedException...... Thread-1 中断了线程 Thread-1 线程退出 Thread-0 同时获得了锁1 与锁2. ... Thread-0 线程退出
tryLock() tryLock(long time, TimeUnit unit) 的作用在给定等待时长内锁没有被另外的线程持有,并且当前线程也没有被中断,则获得该锁 。通过该方法可以实现锁对象的限时等待 。
基本使用 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 public class TryLockTest { static class TimeLock implements Runnable { private static ReentrantLock lock = new ReentrantLock(); @Override public void run () { try { if (lock.tryLock(3 , TimeUnit.SECONDS)) { System.out.println(Thread.currentThread().getName() + "获得锁,执行耗时任务" ); Thread.sleep(2000 ); } else { System.out.println(Thread.currentThread().getName() + "没有获得锁" ); } } catch (InterruptedException e) { e.printStackTrace(); } finally { if (lock.isHeldByCurrentThread()) { lock.unlock(); } } } } public static void main (String[] args) { TimeLock timeLock = new TimeLock(); Thread t1 = new Thread(timeLock); Thread t2 = new Thread(timeLock); t1.start(); t2.start(); } }
tryLock()仅在调用时锁定未被其他线程持有的锁 ,如果调用方法时,锁对象对其他线程持有,则放弃。调用方法尝试获得没,如果该锁没有被其他线程占用则返回 true 表示锁定成功;如果锁被其他线程占用则返回false,不等待。
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 public class TryLockTest02 { static class Service { private ReentrantLock lock = new ReentrantLock(); public void serviceMethod () { try { if (lock.tryLock()) { System.out.println(Thread.currentThread().getName() + "获得锁定" ); Thread.sleep(3000 ); } else { System.out.println(Thread.currentThread().getName() + "没有获得锁定 " ); } } catch (InterruptedException e) { e.printStackTrace(); } finally { if (lock.isHeldByCurrentThread()) { lock.unlock(); } } } } public static void main (String[] args) throws InterruptedException { Service service = new Service(); Runnable r = () -> service.serviceMethod(); Thread t1 = new Thread(r); t1.start(); Thread.sleep(50 ); Thread t2 = new Thread(r); t2.start(); } } Thread-0 获得锁定 Thread-1 没有获得锁定
tryLock避免死锁 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 80 81 82 83 84 85 86 87 public class TryLockTest03 { static class IntLock implements Runnable { private static ReentrantLock lock1 = new ReentrantLock(); private static ReentrantLock lock2 = new ReentrantLock(); private int lockNum; public IntLock (int lockNum) { this .lockNum = lockNum; } @Override public void run () { if (lockNum % 2 == 0 ) { while (true ) { try { if (lock1.tryLock()) { System.out.println(Thread.currentThread().getName() + "获得 锁 1, 还想获得锁 2" ); Thread.sleep(new Random().nextInt(100 )); try { if (lock2.tryLock()) { System.out.println(Thread.currentThread().getName() + "同时获得锁 1 与锁 2 ----完成任务了" ); return ; } } finally { if (lock2.isHeldByCurrentThread()) { lock2.unlock(); } } } } catch (InterruptedException e) { e.printStackTrace(); } finally { if (lock1.isHeldByCurrentThread()) { lock1.unlock(); } } } } else { while (true ) { try { if (lock2.tryLock()) { System.out.println(Thread.currentThread().getName() + "获得 锁 2, 还想获得锁 1" ); Thread.sleep(new Random().nextInt(100 )); try { if (lock1.tryLock()) { System.out.println(Thread.currentThread().getName() + "同时获得锁 1 与锁 2 ----完成任务了" ); return ; } } finally { if (lock1.isHeldByCurrentThread()) { lock1.unlock(); } } } } catch (InterruptedException e) { e.printStackTrace(); } finally { if (lock2.isHeldByCurrentThread()) { lock2.unlock(); } } } } } } public static void main (String[] args) { IntLock intLock1 = new IntLock(1 ); IntLock intLock2 = new IntLock(2 ); Thread t1 = new Thread(intLock1); Thread t2 = new Thread(intLock2); t1.start(); t2.start(); } }
newCondition() 关键字 synchronized 与 wait()/notify()这两个方法一起使用可以实现等待/通知模式。Lock 锁newContition()方法返回Condition对象,Condition 类也可以实现等待/通知模式。使用 notify()通知 时, JVM 会随机唤醒 某个等待的线程。使用Condition类 可以进行选择性通知 。
Condition 比较常用的两个方法:
注意: 在调用Condition的await()/signal()方法前,也需要线程持有相关的Lock锁。调用await()后线程会释放这个锁,在singal()调用后会从当前Condition对象的等待队列中,唤醒 一个线程,唤醒的线程尝试获得锁, 一旦获得锁成功就继续执行。
Condition 等待与通知基本用法
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 public class ConditionTest { static Lock lock = new ReentrantLock(); static Condition condition = lock.newCondition(); static class SubThread extends Thread { @Override public void run () { lock.lock(); try { System.out.println("condition await begin" ); condition.await(); System.out.println("condition await end" ); } catch (InterruptedException e) { e.printStackTrace(); } finally { lock.unlock(); System.out.println("condition unlock" ); } } } public static void main (String[] args) throws InterruptedException { SubThread t1 = new SubThread(); t1.start(); Thread.sleep(3000 ); lock.lock(); try { System.out.println("condition signal begin" ); condition.signal(); System.out.println("condition signal end" ); } finally { lock.unlock(); } } } condition await begin condition signal begin condition signal end condition await end condition unlock
多个Condition实现通知部分线程, 使用更灵活
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 public class ConditionTest02 { static Lock lock = new ReentrantLock(); static Condition conditionA = lock.newCondition(); static Condition conditionB = lock.newCondition(); static class Service { public void awaitA () { lock.lock(); try { System.out.println("conditionA await begin" ); conditionA.await(); System.out.println("conditionA await end" ); } catch (InterruptedException e) { e.printStackTrace(); } finally { lock.unlock(); } } public void awaitB () { lock.lock(); try { System.out.println("conditionB await begin" ); conditionB.await(); System.out.println("conditionB await end" ); } catch (InterruptedException e) { e.printStackTrace(); } finally { lock.unlock(); } } public void signalA () { lock.lock(); try { conditionA.signal(); } finally { lock.unlock(); } } public void signalB () { lock.lock(); try { conditionB.signal(); } finally { lock.unlock(); } } } public static void main (String[] args) throws InterruptedException { Service service = new Service(); new Thread(() -> service.awaitA()).start(); new Thread(() -> service.awaitB()).start(); Thread.sleep(3000 ); service.signalA(); } } conditionA await begin conditionB await begin conditionA await end
condition实现交替打印
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 public class ConditionTest03 { static Lock lock = new ReentrantLock(); static Condition condition = lock.newCondition(); public static boolean flag = true ; static class Service { public void print1 () { lock.lock(); try { while (flag) { System.out.println("method1 await begin" ); condition.await(); System.out.println("method1 await begin" ); } flag = true ; System.out.println(Thread.currentThread().getName() + " -------------" ); condition.signal(); } catch (InterruptedException e) { e.printStackTrace(); } finally { lock.unlock(); } } public void print2 () { lock.lock(); try { while (!flag) { System.out.println("method2 await begin" ); condition.await(); System.out.println("method2 await begin" ); } flag = false ; System.out.println(Thread.currentThread().getName() + " **************" ); condition.signal(); } catch (InterruptedException e) { e.printStackTrace(); } finally { lock.unlock(); } } } public static void main (String[] args) { Service service = new Service(); new Thread(() -> { for (int i = 0 ; i < 100 ; i++) { service.print1(); } }).start(); new Thread(() -> { for (int i = 0 ; i < 100 ; i++) { service.print2(); } }).start(); } }
公平锁与非公平锁 大多数情况下,锁的申请都是非公平的。如果线程1与线程2都在请求锁 A, 当锁 A 可用时, 系统只是会从阻塞队列中随机的选择一个线程,不能保证其公平性.。
公平的锁会按照时间先后顺序,保证先到先得,公平锁的这一特点不会出现线程饥饿现象。
synchronized 内部锁就是非公平的。 ReentrantLock 重入锁提供了一个构造方法:ReentrantLock(boolean fair) ,当在创建锁对象时实参传递true 可以把该锁设置为公平锁。公平锁看起来很公平 ,但是要实现公平锁必须要求系统维护一个有序队列,公平锁的实现成本较高,性能也低 。因此默认情况下锁是非公平的, 不是特别的需求,一般不使用公平锁。
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 public class ReentrantLockTest03 { static Lock lock = new ReentrantLock(true ); static class SubThread extends Thread { @Override public void run () { while (true ) { lock.lock(); try { System.out.println(Thread.currentThread().getName() + " 获得了锁" ); } finally { lock.unlock(); } } } } public static void main (String[] args) { for (int i = 0 ; i < 3 ; i++) { new SubThread().start(); } } }
在上面的运行结果中可以发现:
如果是非公平锁,系统倾向于让一个线程再次获得已经持有的锁 ,这种分配策略是高效的,非公平的 。
如果是公平锁,多个线程不会发生同一个线程连续多次获得锁的可能 ,保证了公平性。
其他方法 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 int getHoldCount(): 返回当前线程调用 lock()方法的次数 int getQueueLength(): 返回正等待获得锁的线程预估数 int getWaitQueueLength(Condition condition): 返回与 Condition 条件相关的等待的线程预估数 boolean hasQueuedThread(Thread thread): 查询参数指定的线程是否在等待获得锁 boolean hasQueuedThreads(): 查询是否还有线程在等待获得该锁 boolean hasWaiters(Condition condition): 查询是否有线程正在等待指定的 Condition 条件 boolean isFair(): 判断是否为公平锁 boolean isHeldByCurrentThread(): 判断当前线程是否持有该锁 boolean isLocked(): 查询当前锁是否被线程持有
ReentrantReadWriteLock-读写锁 synchronized 内部锁与ReentrantLock 锁都是独占锁(排它锁)**, 同一时间只允许一个线程执行同步代码块, 可以保证线程的安全性,但是执行效率低**。
ReentrantReadWriteLock读写锁是一种改进的排他锁,也可以称作共享/排他锁。允许多个线程同时读取共享数据,但是一次只允许一个线程对共享数据进行更新。
读写锁通过读锁与写锁来完成读写操作。线程在读取共享数据前必须先持有读锁,该读锁可以同时被多个线程持有,即它是共享的。线程在修改共享数据前必须先持有写锁,写锁是排他的, 一个线程持有写锁时其他线程无法获得相应的锁。
读锁只是在读线程之间共享,任何一个线程持有读锁时,其他线程都无法获得写锁 , 保证线程在读取数据期间没有其他线程对数据进行更新,使得读线程能够读到数据的最新值,保证在读数据期间共享变量不被修改 。
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 ReadWriteLock rwLock = new ReentrantReadWriteLock(); Lock readLock = rwLock.readLock(); Lock writeLock = rwLock.writeLock(); readLock.lock(); try { 读取共享数据 }finally { readLock.unlock(); } writeLock.lock(); try { 更新修改共享数据 }finally { writeLock.unlock(); }
读写锁允许读读共享, 读写互斥,写写互斥,在JUC包中定义了ReadWriteLock接口,该接口中定义了 readLock()返回读锁,定义 writeLock()方法返回写锁。该接口的实现类是 ReentrantReadWriteLock。
注: readLock()与writeLock()**方法 返回的锁对象是 同一个锁的两个不同的角色,不是分别获得两个不同的锁。**
读读共享 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 public class ReadWriteLockTest { static ReadWriteLock readWriteLock = new ReentrantReadWriteLock(); public void read () { Lock lock = readWriteLock.readLock(); lock.lock(); try { System.out.println(Thread.currentThread().getName() + "--->获得读锁,正在读数据" ); Thread.sleep(2000 ); System.out.println(Thread.currentThread().getName() + "--->读取完毕" ); } catch (InterruptedException e) { e.printStackTrace(); } finally { lock.unlock(); } } public static void main (String[] args) { ReadWriteLockTest test = new ReadWriteLockTest(); for (int i = 0 ; i < 5 ; i++) { new Thread(() -> { test.read(); }).start(); } } }
读写互斥 读写锁中写锁是排他的,同一时间不允许多个线程持有同一个写锁。
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 public class ReadWriteLockTest02 { static ReadWriteLock readWriteLock = new ReentrantReadWriteLock(); public void write () { Lock lock = readWriteLock.writeLock(); lock.lock(); try { System.out.println(Thread.currentThread().getName() + "--->获得写锁,正在写数据" ); Thread.sleep(2000 ); System.out.println(Thread.currentThread().getName() + "--->写数据完毕" ); } catch (InterruptedException e) { e.printStackTrace(); } finally { lock.unlock(); } } public static void main (String[] args) { ReadWriteLockTest02 test = new ReadWriteLockTest02(); for (int i = 0 ; i < 5 ; i++) { new Thread(() -> { test.write(); }).start(); } } }
从运行结果可以发现,同一时间只有一个线程获得写锁。
写写互斥 写锁是独占锁,是排他锁,读线程与写线程也是互斥的。
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 public class ReadWriteLockTest03 { ReadWriteLock readWriteLock = new ReentrantReadWriteLock(); Lock readLock = readWriteLock.readLock(); Lock writeLock = readWriteLock.writeLock(); public void read () { readLock.lock(); try { System.out.println(Thread.currentThread().getName() + "--->获得读锁,正在读数据" ); Thread.sleep(2000 ); System.out.println(Thread.currentThread().getName() + "--->读取完毕" ); } catch (InterruptedException e) { e.printStackTrace(); } finally { readLock.unlock(); } } public void write () { writeLock.lock(); try { System.out.println(Thread.currentThread().getName() + "--->获得写锁,正在写数据" ); Thread.sleep(2000 ); System.out.println(Thread.currentThread().getName() + "--->写数据完毕" ); } catch (InterruptedException e) { e.printStackTrace(); } finally { writeLock.unlock(); } } public static void main (String[] args) { ReadWriteLockTest03 test = new ReadWriteLockTest03(); for (int i = 0 ; i < 3 ; i++) { new Thread(() -> { test.read(); }).start(); } for (int i = 0 ; i < 3 ; i++) { new Thread(() -> { test.write(); }).start(); } } } Thread-0 --->获得读锁,正在读数据 Thread-2 --->获得读锁,正在读数据 Thread-1 --->获得读锁,正在读数据 Thread-0 --->读取完毕 Thread-1 --->读取完毕 Thread-2 --->读取完毕 Thread-3 --->获得写锁,正在写数据 Thread-3 --->写数据完毕 Thread-4 --->获得写锁,正在写数据 Thread-4 --->写数据完毕 Thread-5 --->获得写锁,正在写数据 Thread-5 --->写数据完毕
synchronized 锁和Lock 锁使⽤哪个Lock显式锁给我们的程序带来了很多的灵活性,很多特性都是Synchronized锁没有的。Lock锁在刚出来的时候很多性能⽅⾯都⽐Synchronized锁要好,但是从JDK1.6开始Synchronized锁就做了各种的优化。
优化操作:适应⾃旋锁,锁消除,锁粗化,轻量级锁,偏向锁。
所以,到现在Lock锁和Synchronized锁的性能其实差别不是很⼤ !⽽Synchronized锁⽤起来⼜特别简单。Lock锁还得顾忌到它的特性,要⼿动释放锁才⾏(如果忘了释放,这就是⼀个隐患)
所以说,我们绝⼤部分时候还是会使⽤Synchronized锁 ,⽤到了Lock锁提及的特性,带来的灵活性才会考虑使⽤Lock显式锁。