1. ReentrantLock 类实现了Lock,它拥有与Sychronized相同的并发性和内存语义,但是添加了类似锁投票、定时锁等候和可中断锁等候的一些特性。此外,它还提供了在与激烈争用情况下更佳的性能。(换句话说,当许多线程都想访问共享资源时,JVM可以花更少的时间来调度线程,把更多的时间用在执行线程上)。
2. ReentrantLock 和 ReentrantReadWriteLock是两种不同的实现,在类继承结构上两者并无关联。
3. ReentrantReadWriteLock为2个锁提供了可重进入的加锁语义。与ReentrantLock相同,ReentrantReadWriteLock 能够被构造成非公平或公平的。在公平锁中,选择权交给等待时间最长的线程;在非公平锁中,线程允许访问的顺序是不定的。
4. 若一个程序或子程序可以“安全的被并行执行(Parallelcomputing)”,则称其为可重入(reentrant或re-entrant)的。即当该子程序正在运行时,可以再次进入并执行它(并行执行时,个别的执行结果,都符合设计时的预期)。可重入概念是在单线程操作系统的时代提出的。一个子程序的重入,可能由于自身原因,如执行了jmp或者call,类似于子程序的递归调用;或者由于硬件中断,UNIX系统的signal的处理,即子程序被中断处理程序或者signal处理程序调用。重入的子程序,按照后进先出线性序依次执行。
5. 重入锁(ReentrantLock)是一种递归无阻塞的同步机制。
6. 可重入锁的概念是自己可以再次获取自己的内部锁。
7. 如果当前线程已经获得了某个监视器对象所持有的锁,那么该线程在该方法中调用另外一个同步方法也同样持有该锁。
8. 什么叫可重入:就是同一个线程可以重复加锁,可以对同一个锁加多次,每次释放的时候释放一次,直到该线程加锁次数为0,这个线程才释放锁。
9. 什么叫读写锁:也就是说读锁可以共享,多个线程可以同时拥有读锁,但是写锁却只能只有一个线程拥有,而且获取写锁的时候其他线程都已经释放了读锁,而且该线程读取写锁之后,其他线程不能再获取读锁。
10. ReentrantReadWriteLock中fair这个参数表示是否创建一个公平的读写锁,还是非公平的读写锁。也就是抢占式还是非抢占式。
11. 公平和非公平:公平表示读取的锁的顺序是按照线程加锁的顺序来分配获取到锁的线程时最先加锁的过程,是按照FIFO的顺序来分配锁的;非公平表示获取锁的顺序是无需的,后来加锁的线程可能先获得锁,这种情况下就导致某些线程可能一直没有获取到锁。
12. Java.util.concurrent包中有很多同步类,比如互斥锁,读写锁,信号量等,这些同步类几乎都可以用不同的方式来实现,但是如果这样做,那么这样的项目充其量只能算一个二流工程。JSR166并没有生搬硬套,而是建立了一个同步中心类AbstractQueuedSynchronized(简称:AQS)的框架,其中提供了大量的同步操作,而且用户还可以在此类的基础上自定义自己的同步类。其设计目标主要有两点:
a) 提高扩展性,用户可以自定义自己的同步类
b) 最大限度的提高吞吐量,提供自定义公平策略
13. AQS用的是一个32位的整形类表示同步状态的,可以通过以下几个方法来设置和修改这个字段:getState(),setState(),compareAndSetState(),这些方法都需要java.util.concurrent.atomic包的支持,采用CAS操作。将state设置为32为整型是一个务实的决定,虽然JSR166提供64位版本的原子操作,但它还是使用对象内部锁来实现的,如果采用64为的state
14. JSR是javaSpecification Requests的缩写,意思是java规范请求。是指向JCP提出新增一个标准化技术规范的正式请求。任何人都可以提交JSR,以向java平台增添新的API和服务。JSR已经成为java界的一个重要标准。
15. 同步框架最重要的是要有一个同步队列,在这里被严格限制为FIFO队列,因此这个同步框架不支持基于优先级的同步策略。
16. AbstractQueuedSynchronized是采用一个模板方法模式实现的同步器基类,子类只需要实现获取和释放方法,子类一般不直接用于同步控制,而是采用代理模式。因为获取和释放方法一般是私有的,实现细节不必暴露出来,所以常用委派的方法来使用同步器类:在一个类的内部申请一个私有的AQS的子类,委派它的所有同步方法。
17. Java.util.concurrent包中的所有同步工具都依赖于AQS.
18. ReentrantLock 和 ReentrantReadWriteLock里面的实现都依赖于Sync,但是这两个类都定义了自己的sync,并且sync都继承了AbstractQueuedSynchronizer(AQS).
19. AQS全称为:AbstractQueuedSynchronized,他是juc的synchronizer的基础。
20. Juc即java.util.concurrent
21. 在JUC中,不管是FutureTask、CountDownLatch、Lock、还是信号量,CyclicBarrier,从某种角度说,他们都依赖某种状态,或者条件,虽然这些条件的值不同:
(1) FutureTask 等待任务结束
(2) CountDownLatch等待计数器到0
(3) Lock在等待其他线程释放锁
(4) 信号量在等待获得允许
(5) CyclicBarrier在等待计数器到0
22. 依赖状态在等待的时候可以理解为进入了一个FIFO队列,前面提到的一些类,就是在状态未满足的情况下阻塞,并且加入队列,在合适的时机,唤醒。Java提供了这样的内部队列,但是他是和锁相关的,每个对象都有一把锁,没把锁都有对应的队列,Object中的wait、notify、notifyAll就是操作这个队列的API。从这个角度来看,要操作锁的队列,需要先有锁,因此wait/notify需要在锁中调用,另外,这也确保了,在观察/操作状态的时候,不会有其他线程修改状态!
23. 前面提到了状态以后,以及wait/notify的队列的支持,但是内部锁很不灵活,wait/nofity用起来也有很多麻烦的地方,特别是多个线程等待因为不同的原因等待一个对象上的消息的时候,非常不直观,因此FutureTask/CountDownLatch等是用另一种方式实现的,ReentrantLock的源码,会发现lock,tryLock等方法都是调用内部类Sync实现,Sync又是继承自AQS,其他类也是这样的,AQS是这些synchronized类的基础。
24. 状态字段主要由字段state、getState、setState、compareAndSetState来表达、操作、状态的原子操作非常重要,可以保证实现synchronizer的语义,否则就会有并发问题;队列中保存的不仅仅是线程信息,它保存的是AQS的一个内部类,Node,它的结构后面介绍。
25. Signal状态表示该节点的后继节点需要被唤醒。这个状态的作用主要是告诉前置节点:“你结束以后,你后面还有兄弟需要被唤醒”,如果没有这个状态,那么unlock以后,仅仅修改锁的状态,不会有什么操作!如果因为中断/超时,该请求已经取消,会修改状态为Cancled,遇到取消任务,那么需要剔出这些节点,并把后面的节点接上,Pre这个引用在一般的CLH锁中是没有的,这里主要是为了在取消的时候,保证取消节点的next可以指向取消节点的pre。
26. 另外必须了解一个工具类,LockSupport
27. wait/notify是和对象、锁、等待队列、线程强关联的,在调用wait的时候,必须有锁,这时候释放对象上的锁,当前线程加入该对象锁的等待队列。
28. java中同步是通过监视器模型来实现的,java中的监视器实际是一个代码块,这段代码块同一时刻只允许被一个线程执行。线程要想执行这段代码的唯一方法就是获得监视器。
29. 一个线程只有在持有监视器时才能执行wait操作,处于等待的线程只有再次获得监视器才能退出等待状态。
30. 对象锁是可重入的,也就是说对一个对象或者类上的锁是可以累加的。
31. 在java中有两种监视区域:同步方法和同步块,这两种监视区都和一个引入对象相关联,当到达这个监视区域时,JVM就会锁住这个引用对象,不论他是怎么离开的,都会释放这个引用对象上的锁。Java程序员不能自己家对象锁,对象锁是JVM内部机制,只需要编写同步方法或者同步块即可,操作监视区域时JVM会帮你自动上锁或者释放锁。
32. 要建立一个同步语句,只需要在相关语句上加上synchronized关键字就可以,例如下面的incr方法,如果没有获得当前对象(this)的锁,在同步块内的语句是不会执行的,如果不是this引用,而是用另一个对象的引用,需要获得对应对象的锁同步块才会执行,如果表达式获得对class对象实例的引用,就需要锁住那个类。
33. AQS采用的是CLH队列,CLH队列是有一个一个的节点构成的,前面提到结点中有一个状态位,这个状态位与线程状态密切相关,这个状态位是一个32位的整形常量:
a) Static final int CANCELLED = 1;
b) Static final int SIGNAL = -1;
c) Static final int CONDITION =-2;
d) Static final int PROPAGATE =-3;
CANCELLED: 因为超时或者中断,结点会被设置为取消状态,被取消状态的结点不应该去竞争锁,只能保持取消状态不变,不能转换为其他状态。处于这种状态的结点会被踢出队列,被GC回收。
SIGNAL:表示这个结点的继任结点被阻塞了,因为等待某个条件而被阻塞。
CONDITION:表示这个结点在队列中,因为等待某个条件而被阻塞。
PROPAGATE:使用在共享模式头结点有可能处于这种状态,表示锁的下一次获取可以无条件传播。
0: 新结点都会处于这种状态。
34. 如果一个对象有多个synchronized方法,只要一个线程访问了其中的一个synchronized方法,其他线程不能同时访问这个对象中的任何一个synchronized方法。
35. Java中的每个对象都有一个锁,当访问某个对象的synchronized方法时,表示对该对象上锁,此时其他任何线程都无法去访问该synchronized方法了,直到之前的那个线程执行方法完毕后,其他线程才有可能去访问该synchronized方法。
36. 如果一个对象有多个synchronized方法,某一时刻某个线程已经进入到某个synchronized方法,那么在该方法没有执行完毕前,其他线程无法访问该对象的任何synchronized方法的,但可以访问非synchronized方法。
37. 如果synchronized方法是static的,那么当前线程访问该方法时,它锁的不是synchronized方法所在的对象,而是synchronized方法所在的对象对应的class对象。因为java无论一个类有多少个对象,这些对象会对应唯一一个class对象,因此当线程分别访问同一个类的两个对象的static,synchronized方法时,他们执行的也是按顺序来的,也就是说一个线程先执行,一个线程后执行。
38. Java提供了强制原子性的内部锁机制:synchronized块。但是内部锁是可重入的,当线程试图获得它自己占有的锁时,请求会成功。简单的说在一个synchronized方法内部调用本类的其他synchronized方法时,永远可以拿到锁。
39. 重进入的实现是通过为每个锁关联一个请求技术和一个占有它的线程。