synchronized关键字
synchronized关键字可以作用于代码块,也可以作用于方法声明. 若作用于代码块, 必须显式指明使用那个对象或类的monitor锁. 若作用于方法: 方法是static, 则使用类的monitor, 否则使用对象实例的monitor.
若已获得某对象或类的monitor, 可再次进入该对象或类monitor 作用的块或方法.
被synchronized的语句要尽量少, 以提高性能;
若某线程已经通过synchronized获得A的monitor, 又获得B的monitor,
synchronized与 wait(), notify(), notifyAll()
wait(), notify(), notifyAll()必须在获得同一对象实例或类的的monitor的 synchronized的语句中, 否则JVM会抛出IllegalMonitorStateException.
当wait() 方法被调用之后, 当前线程释放该对象或实例的monitor, 并且挂起, 其它等待该monitor的线程获得该monitor, 进入被保护的synchronized语句运行, 直到有获得同样monitor的线程调用notify() 或notifyAll() 方法, 并且释放该monitor, 挂起的线程才有可能恢复运行.
另外 wait(), notify(), notifyAll() 方法都会抛出 InterruptedException, 要注意处理;wait() 一般处在一个while循环中, 每次醒来都去check一下当前的条件是不是已经被满足.
Intrinsic Locks and Synchronization 内置锁和synchronized
Java的synchronized是建立在所有对象内置的一个内置锁. 在一般的教程或API中一般称之为 intrinsic lock or monitor lock, 有时候也简单的称之为 monitor.
这里有更详细的说明: https://docs.oracle.com/javase/tutorial/essential/concurrency/locksync.html
多个synchronized 的锁获得与释放顺序
在JDK Lock的文档看到这么一句讲synchronized锁的: when multiple locks are acquired they must be released in the opposite order. 其实锁并不需要这个顺序, 只不过这是synchronized的语句结构造成的, 每个synchronized的语句都有一对大括号用来显示指明起锁的代码范围, 词法结构造成了这个获得与release的顺序. 所以也就有了接下来的这句: all locks must be released in the same lexical scope in which they were acquired.
这也是synchronized与Lock的重要区别. 当然这种结构也使这种锁更简单, 不容易出错.
Lock 接口 的 non-blocking 特性
Lock 相对于synchronized的最大优点是, 可以尝试去获得Lock, 而不是一直等待:
boolean tryLock() //试图获得, 当lock free 的时候便可以获得;
boolean tryLock(long time, TimeUnit unit) // 在给定时间内尝试获得, 并且线程没被interrupted.
void lockInterruptibly() // 尝试获得, 除非当前线程被interrupted
同时Lock 也可以通过与之关联的Condition 类实现了 wait/notify 机制;
Lock 相对于synchronized的其它特性
获得锁的顺序: 公平锁, 不公平锁;
non-reentrant : synchronized一定是reentrant的, Lock 可以做到不允许reentrant (参考具体实现);
死锁检测:
Lock Condition
Lock取代了synchronized关键字, Condition取代了synchronized需要的内置锁.
Object 的wait()/notify()/notifyAll() 方法依赖对象或类的内置锁实现等待和唤醒, Lock的子类则通过Condition实现线程的等待和唤醒, Condition有对应的await(), signal() 方法.
Object的wait()/notify()/notifyAll() 方法只能依赖该对象的唯一内置锁, 而Lock可以依赖多个Condition, 同一把锁, 有多个开始等待和唤醒的条件, 那么就可以实现根据不同的条件, 唤醒正在等待这把锁的多个线程中特定的线程, 从而可以实现唤醒的顺序, 也提高了系统的效率.
尽管Condition对象也有内置锁, 但它的现实与之内置锁, 没有任何关系.
ReentrantLock
ReentrantLock 可以设置公平性(fair) 参数. 同时也提供了一些有用的方法去监控当前Lock的状态, 如: isLocked(), getLockQueueLength(),isHeldByCurrentThread(), getHoldCount(),getWaitingThreads(Condition condition) 等.
ReadWriteLock
读写锁允许多线程对共享资源的并发访问, 它有读锁和写锁两把锁, 读锁允许同时有多个线程读, 不能有写锁被Lock, 写锁只允许同时有且仅有一个写操作获得锁.
数据库表的操作是一个典型的读写锁的例子.
读写锁的性能取决于读操作和写操作的频率对比, 以及读操作写操作的单词操作时长.