前言
任何一个新引入的知识都是为了解决以往系统中出现的问题,否则新引入的将变得毫无价值
如果一个代码块被synchronized修饰了,当一个线程获取了对应的锁,并执行该代码块时,其他线程便只能一直等待,等待获取锁的线程释放锁。
但当有多个线程读写文件时,读操作和写操作会发生冲突现象,写操作和写操作会发生冲突现象,但是读操作和读操作不会发生冲突现象,通过Lock就可以实现。
Lock接口, 提供了与synchronized一样的锁功能。虽然它失去了像synchronize关键字隐式加锁解锁的便捷性,但是却拥有了锁获取和释放的可操作性,可中断的获取锁以及超时获取锁等多种synchronized关键字所不具备的同步特性
Lock必须要用户去手动释放锁,如果没有主动释放锁,就有可能导致出现死锁现象

一个线程获取多少次锁,就必须释放多少次锁。这对于内置锁也是适用的,每一次进入和离开synchronized方法(代码块),就是一次完整的锁获取和释放。
锁的分类
- 悲观锁:
 悲观锁,每次去请求数据的时候,都认为数据会被抢占更新(悲观的想法);所以每次操作数据时都要先加上锁,其他线程修改数据时就要等待获取锁。适用于写多读少的场景,synchronized就是一种悲观锁
- 乐观锁:
 在请求数据时,觉得无人抢占修改。等真正更新数据时,才判断此期间别人有没有修改过(预先读出一个版本号或者更新时间戳,更新时判断是否变化,没变则期间无人修改);和悲观锁不同的是,期间数据允许其他线程修改
 
- 自旋锁:
 一句话,魔力转转圈。当尝试给资源加锁却被其他线程先锁定时,不是阻塞等待而是循环再次加锁
 在锁常被短暂持有的场景下,线程阻塞挂起导致CPU上下文频繁切换,这可用自旋锁解决;但自旋期间它占用CPU空转,因此不适用长时间持有锁的场景
 
lock
Lock
| 12
 3
 4
 5
 6
 7
 8
 9
 10
 11
 12
 13
 14
 15
 16
 17
 18
 19
 20
 21
 
 | public interface Lock {
 
 void lock();
 
 
 
 void lockInterruptibly() throws InterruptedException;
 
 
 boolean tryLock();
 
 
 boolean tryLock(long time, TimeUnit unit) throws InterruptedException;
 
 
 void unlock();
 
 
 Condition newCondition();
 }
 
 | 
一般来说,使用Lock必须在try{}catch{}块中进行,并且将释放锁的操作放在finally块中进行,以保证锁一定被被释放,防止死锁的发生():
锁【lock.lock】必须紧跟try代码块,且unlock要放到finally第一行。
ReentrantLock
可重入锁, 支持重入性,表示能够对共享资源重复加锁,即当前线程获取该锁再次获取不会被阻塞。ReentrantLock实现了Lock接口的,并且ReentrantLock提供了更多的方法
JVM允许同一个线程重复获取同一个锁,这种能被同一个线程反复获取的锁,就叫做可重入锁
| 12
 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
 
 | public class ReentrantLockTest1 {private ArrayList<Integer> arrayList = new ArrayList<Integer>();
 private Lock lock = new ReentrantLock();
 
 public static void main(String[] args) {
 final LocksTest test = new LocksTest();
 
 new Thread(() -> test.insert(Thread.currentThread())).start();
 
 new Thread(() -> 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++) {
 arrayList.add(i);
 }
 } catch (Exception e) {
 e.printStackTrace();
 } finally {
 System.out.println(thread.getName() + "释放了锁");
 lock.unlock();
 }
 }
 
 }
 
 
 | 
一般情况下通过tryLock来获取锁时是这样使用的
| 12
 3
 4
 5
 6
 7
 8
 9
 10
 11
 12
 13
 14
 15
 16
 17
 18
 19
 20
 21
 22
 23
 24
 25
 26
 
 | public class ReentrantLockTest1 {
 public void insert(Thread thread) {
 if(lock.tryLock()) {
 try {
 System.out.println(thread.getName()+"得到了锁");
 for(int i=0;i<5;i++) {
 arrayList.add(i);
 }
 } catch (Exception e) {
 e.printStackTrace();
 }finally {
 System.out.println(thread.getName()+"释放了锁");
 lock.unlock();
 }
 } else {
 System.out.println(thread.getName()+"获取锁失败");
 }
 }
 }
 
 
 
 
 
 
 
 | 
由于lockInterruptibly()的声明中抛出了异常,所以lock.lockInterruptibly()必须放在try块中或者在调用lockInterruptibly()的方法外声明抛出InterruptedException
| 12
 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
 
 | public class InterruptTest {
 private Lock lock = new ReentrantLock();
 public static void main(String[] args)  {
 InterruptTest test = new InterruptTest();
 MyThread thread1 = new MyThread(test);
 MyThread thread2 = new MyThread(test);
 thread1.start();
 thread2.start();
 
 try {
 TimeUnit.SECONDS.sleep(2);
 } catch (InterruptedException e) {
 e.printStackTrace();
 }
 thread2.interrupt();
 }
 
 public void insert(Thread thread) throws InterruptedException{
 lock.lockInterruptibly();
 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 InterruptTest test;
 public MyThread(InterruptTest test) {
 this.test = test;
 }
 @Override
 public void run() {
 
 try {
 test.insert(Thread.currentThread());
 } catch (InterruptedException e) {
 System.out.println(Thread.currentThread().getName()+"被中断");
 }
 }
 }
 
 
 
 | 
ReadWriteLock
ReadWriteLock也是一个接口,只有两个方法:
| 12
 3
 4
 5
 6
 7
 8
 9
 10
 11
 12
 13
 14
 15
 16
 
 | public interface ReadWriteLock {
 
 
 
 
 Lock readLock();
 
 
 
 
 
 
 Lock writeLock();
 }
 
 
 | 
一个用来获取读锁,一个用来获取写锁。也就是说将文件的读写操作分开,分成2个锁来分配给线程,从而使得多个线程可以同时进行读操作
使用ReadWriteLock时,适用条件是同一个数据,有大量线程读取,但仅有少数线程修改, 适合读多写少的场景
ReentrantReadWriteLock
ReentrantReadWriteLock实现了ReadWriteLock接口,并添加了可重入的特性
如果在系统中,读操作次数远远大于写操作,则读写锁就可以发挥最大的功效,提升系统的性能
Lock和synchronized的选择
  总结来说,Lock和synchronized有以下几点不同:
  1)Lock是一个接口,而synchronized是Java中的关键字,synchronized是内置的语言实现;
  2)synchronized在发生异常时,会自动释放线程占有的锁,因此不会导致死锁现象发生;而Lock在发生异常时,如果没有主动通过unLock()去释放锁,则很可能造成死锁现象,因此使用Lock时需要在finally块中释放锁;
  3)Lock可以让等待锁的线程响应中断,而synchronized却不行,使用synchronized时,等待的线程会一直等待下去,不能够响应中断;
  4)通过Lock可以知道有没有成功获取锁,而synchronized却无法办到。
  5)Lock可以提高多个线程进行读操作的效率。
  在性能上来说,如果竞争资源不激烈,两者的性能是差不多的,而当竞争资源非常激烈时(即有大量线程同时竞争),此时Lock的性能要远远优于synchronized。所以说,在具体使用时要根据适当情况选择
Condition
它用来替代传统的Object的wait()、notify()实现线程间的协作,相比使用Object的wait()、notify(),使用Condition的await()、signal()这种方式实现线程间协作更加安全和高效。使用Condition可以实现等待/唤醒,并且能够唤醒制定线程
Condition可以替代wait和notify;Condition对象必须从Lock对象获取
LockSupport
LockSupport是一个工具类,可以让线程在任意位置阻塞,也可以在任意位置唤醒,它的内部其实两类主要的方法:park(停车阻塞线程)和unpark(启动唤醒线程):
| 12
 3
 4
 5
 6
 7
 8
 9
 10
 11
 12
 13
 14
 15
 16
 17
 18
 19
 20
 21
 22
 23
 
 | // 暂停当前线程public static void park(Object blocker);
 
 // 暂停当前线程,不过有超时时间的限制
 public static void parkNanos(Object blocker, long nanos);
 public static void parkNanos(long nanos);
 
 // 暂停当前线程,直到某个时间
 public static void parkUntil(Object blocker, long deadline);
 public static void parkUntil(long deadline);
 
 // 无期限暂停当前线程
 public static void park();
 
 
 // 恢复当前线程
 public static void unpark(Thread thread);
 
 //blocker的作用是在dump线程的时候看到阻塞对象的信息
 public static Object getBlocker(Thread t);
 
 //java14新增了设置blocker的方法
 public static void setCurrentBlocker(Object blocker);
 
 | 
| 12
 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
 
 | public class LockSupportTest {public static Object u = new Object();
 static ChangeObjectThread t1 = new ChangeObjectThread("t1");
 static ChangeObjectThread t2 = new ChangeObjectThread("t2");
 
 public static class ChangeObjectThread extends Thread {
 public ChangeObjectThread(String name) {
 super(name);
 }
 @Override public void run() {
 synchronized (u) {
 System.out.println(Thread.currentThread() +"in " + getName());
 LockSupport.park();
 if (Thread.currentThread().isInterrupted()) {
 System.out.println(Thread.currentThread() +"被中断了");
 }
 System.out.println(Thread.currentThread() + "继续执行");
 }
 }
 }
 
 public static void main(String[] args) throws InterruptedException {
 t1.start();
 Thread.sleep(1000L);
 t2.start();
 Thread.sleep(3000L);
 t1.interrupt();
 LockSupport.unpark(t2);
 t1.join();
 t2.join();
 }
 }
 
 
 | 
LockSuport主要是针对Thread进进行阻塞处理,可以指定阻塞队列的目标对象,每次可以指定具体的线程唤醒。Object.wait()是以对象为纬度,阻塞当前的线程和唤醒单个(随机)或者所有线程
park和unpark可以实现类似wait和notify的功能,但是并不和wait和notify交叉,也就是说unpark不会对wait起作用,notify也不会对park起作用
StampedLock
之前的锁或多或少都存在一些缺点,比如synchronized不可中断等,ReentrantLock 未能读写分离实现,虽然ReentrantReadWriteLock能够读写分离了,但是对于其写锁想要获取的话,就必须没有任何其他读写锁存在才可以,这实现了悲观读取。而且如果读操作很多,写很少的情况下,线程有可能遭遇饥饿问题。
饥饿问题:ReentrantReadWriteLock实现了读写分离,想要获取读锁就必须确保当前没有其他任何读写锁了,但是一旦读操作比较多的时候,想要获取写锁就变得比较困难了,因为当前有可能会一直存在读锁。而无法获得写锁
所以java8引入了新的锁StampedLock,这个类没有直接实现Lock或者ReadWriteLock方法,源码中是把他当作一个单独的类来实现的。相比于普通的ReentranReadWriteLock主要多了一种乐观读的功能。当然,一个StampedLock可以通过asReadLock,asWriteLock,asReadWriteLock方法来得到全部功能的子集
AbstractQwnableSynchronizer
抽象拥有同步器,简称AOS
AbstractQueuedSynchronizer
提供了一个基于FIFO队列,可以用于构建锁或者其他相关同步装置的基础框架, 简称AQS
AbstractQueuedLongSynchronizer
扩展自AbstractQueuedSynchronizer
—todo 未完待续—