可重入锁:基于线程的分配。
读写锁:对一个资源的访问分成了2个锁,比如文件,分为读锁和写锁。例如ReadWriteLock()。
可中断锁:可以中断的锁机制。例如Lock是可中断锁。
公平锁: 以请求锁的顺序来获取锁。有多个线程在等待一个锁,当锁被释放时,等待时间最久的线程会获取该锁,公平锁。
- 双亲委派
-
数据库优化
-
volatile可见性
当修改volatile变量时,会给cpu发送一个信号告诉其他cpu这个变量已修改,当其他cpu调用这个变量时,就会先检查是否有收到修改该变量的信号,有则重新从内存中读取。
乐观锁与悲观锁
参考链接:blog.csdn.net/qq_34337272…
悲观锁:
总是假设最坏的情况,每次去拿数据的时候都认为别人会修改,所以每次在拿数据的时候都会上锁,别的线程想拿这个数据就会阻塞直到它拿到锁(共享资源每次只给一个线程使用,其它线程阻塞,用完后再把资源转让给其它线程)。例如Java中synchronized和ReentrantLock等独占锁
乐观锁:总是假设最好的情况,每次去拿数据的时候都认为别人不会修改,所以不会上锁,但是在更新时会判断一下在此期间别的线程是否更新该数据。 例如版本号机制和CAS算法实现。
版本号机制
在数据表中加上一个数据版本号version字段,表示数据被修改的次数,当数据被修改时,version值会加一。当线程A要更新数据值时,在读取数据的同时也会读取version值,在提交更新时,若刚才读取到的version值为当前数据库中的version值相等时才更新,否则重试更新操作,直到更新成功
CAS算法
CAS算法涉及到三个操作数
- 需要读写的内存值 V
- 进行比较的值 A
- 拟写入的新值 B
当且仅当 V 的值等于 A时,CAS通过原子方式用新值B来更新V的值,否则不会执行任何操作(比较和替换是一个原子操作)。
公平锁与非公平锁
参考链接:www.jianshu.com/p/f584799f1…
公平锁保障了多线程下各线程获取锁的顺序,先到的线程优先获取锁,而非公平锁则无法保障。
在公平锁 if (!hasQueuedPredecessors() && compareAndSetState(0, acquires))
中,获取锁前会判断等待队列是否为空或者该线程是否在队列头部。
protected final boolean tryAcquire(int acquires) {
final Thread current = Thread.currentThread();
int c = getState();
if (c == 0) {
//hasQueuedPredecessors这个方法就是最大区别所在
if (!hasQueuedPredecessors() &&
compareAndSetState(0, acquires)) {
setExclusiveOwnerThread(current);
return true;
}
}
else if (current == getExclusiveOwnerThread()) {
int nextc = c + acquires;
if (nextc < 0)
throw new Error("Maximum lock count exceeded");
setState(nextc);
return true;
}
return false;
}
在非公平锁,如果在释放锁时没有新的线程刚好进入,则非公平锁等于公平锁。如果释放锁时有新的线程刚好进入,并且位于队列头部的线程没有被唤醒,新线程将优先获取锁。
final boolean nonfairTryAcquire(int acquires) {
final Thread current = Thread.currentThread();
int c = getState();
if (c == 0) {
//区别重点看这里
if (compareAndSetState(0, acquires)) {
setExclusiveOwnerThread(current);
return true;
}
}
else if (current == getExclusiveOwnerThread()) {
int nextc = c + acquires;
if (nextc < 0) // overflow
throw new Error("Maximum lock count exceeded");
setState(nextc);
return true;
}
return false;
}
对于非公平锁if (compareAndSetState(0, acquires))
,只要线程进入了等待队列,队列里面依然是FIFO的原则。
release()部分代码
private void unparkSuccessor(Node node) {
int ws = node.waitStatus;
if (ws < 0)
compareAndSetWaitStatus(node, ws, 0);
Node s = node.next;
if (s == null || s.waitStatus > 0) {
s = null;
for (Node t = tail; t != null && t != node; t = t.prev)
if (t.waitStatus <= 0)
s = t;
}
if (s != null)
//唤醒队列头的线程
LockSupport.unpark(s.thread);
}
非公平锁效率高于公平锁的原因:因为非公平锁减少了线程挂起的几率。
FIFO原则:先进先出
对比
- Lock是一个接口,通过代码实现的
synchronized是Java中的关键字,synchronized是内置的语言实现,synchronized是在JVM层面上实现的。
- synchronized在发生异常时,会自动释放线程占有的锁,因此不会导致死锁现象发生
Lock在发生异常时,如果没有主动通过unLock()去释放锁,则很可能造成死锁现象,因此使用Lock时需要在finally块中释放锁;
-
通过Lock可以知道有没有成功获取锁,而synchronized却无法办到
-
如果竞争资源不激烈,两者的性能是差不多的,而当竞争资源非常激烈时(即有大量线程同时竞争),此时Lock的性能要远远优于synchronized。
-
Lock可以让等待锁的线程响应中断,线程可以中断去干别的事务,而synchronized却不行
例子
当有多个线程读写文件时,读操作和写操作会发生冲突现象,写操作和写操作会发生冲突现象,但是读操作和读操作不会发生冲突现象。
但是采用synchronized关键字来实现同步的话,就会导致一个问题:
如果多个线程都只是进行读操作,所以当一个线程在进行读操作时,其他线程只能等待无法进行读操作。
因此就需要一种机制来使得多个线程都只是进行读操作时,线程之间不会发生冲突,通过Lock就可以办到。