ReentrantLock和synchronized两种锁定机制的对比
synchronized:
synchronized (lockObject) {
// update object state
}
这是一个独享非公平可重入锁。
把代码块声明为 synchronized,有两个重要后果,通常是指该代码具有 原子性(atomicity)和 可见性(visibility)。原子性意味着一个线程一次只能执行由一个指定监控对象(lock)保护的代码,从而防止多个线程在更新共享状态时相互冲突。
可见性则更为微妙;它要对付内存缓存和编译器优化的各种反常行为。
缺点:
无法中断一个正在等候获得锁的线程。
无法通过投票得到锁,如果不想等下去,也就没法得到锁。
synchronized是一个重量级的锁。
ReentrantLock 类:
这是一个独享非公平可重入锁。
java.util.concurrent.lock 中的 Lock 框架是锁定的一个抽象,它允许把锁定的实现作为 Java 类,而不是作为语言的特性来实现。这就为 Lock 的多种实现留下了空间,各种实现可能有不同的调度算法、性能特性或者锁定语义。
ReentrantLock 类实现了 Lock ,它拥有与 synchronized 相同的并发性和内存语义,但是添加了类似锁投票、定时锁等候和可中断锁等候的一些特性。
ReentrantLock 默认是非公平锁。
Lock lock = new ReentrantLock();
lock.lock();
try {
// update object state
}
finally {
lock.unlock();
}
什么时候选择用 ReentrantLock 代替 synchronized
既然如此,我们什么时候才应该使用 ReentrantLock 呢?答案非常简单 —— 在确实需要一些 synchronized 所没有的特性的时候,比如时间锁等候、可中断锁等候、无块结构锁、多个条件变量或者锁投票。 ReentrantLock 还具有可伸缩性的好处,应当在高度争用的情况下使用它,但是请记住,大多数 synchronized 块几乎从来没有出现过争用,所以可以把高度争用放在一边。我建议用 synchronized 开发,直到确实证明 synchronized 不合适,而不要仅仅是假设如果使用 ReentrantLock “性能会更好”。请记住,这些是供高级用户使用的高级工具。(而且,真正的高级用户喜欢选择能够找到的最简单工具,直到他们认为简单的工具不适用为止。)。一如既往,首先要把事情做好,然后再考虑是不是有必要做得更快。
锁的各种分类
悲观锁/乐观锁
悲观锁:我们假设在多线程使用同一资源时会互相抢占资源,这种态度引起的措施叫悲观锁。悲观锁一般用synchronized或者Lock来加锁。
乐观锁:在使用资源时认为其他资源不会抢占资源,就不用使用加锁的方式,这就是乐观锁,一般使用CAS算法处理。
公平锁/非公平锁
公平锁是指多个线程按照申请锁的顺序来获取锁。
非公平锁是指多个线程获取锁的顺序并不是按照申请锁的顺序,有可能后申请的线程比先申请的线程优先获取锁。有可能,会造成优先级反转或者饥饿现象。
对于Java ReentrantLock而言,通过构造函数指定该锁是否是公平锁,默认是非公平锁。非公平锁的优点在于吞吐量比公平锁大。
对于Synchronized而言,也是一种非公平锁。由于其并不像ReentrantLock是通过AQS的来实现线程调度,所以并没有任何办法使其变成公平锁。
可重入锁
可重入锁又名递归锁,是指在同一个线程在外层方法获取锁的时候,在进入内层方法会自动获取锁。
synchronized void setA() throws Exception{
Thread.sleep(1000);
setB();
}
synchronized void setB() throws Exception{
Thread.sleep(1000);
}
上面的代码就是一个可重入锁的一个特点,如果不是可重入锁的话,setB可能不会被当前线程执行,可能造成死锁。
独享锁/共享锁
独享锁是指该锁一次只能被一个线程所持有。
共享锁是指该锁可被多个线程所持有。
对于Java ReentrantLock而言,其是独享锁。但是对于Lock的另一个实现类ReadWriteLock,其读锁是共享锁,其写锁是独享锁。
读锁的共享锁可保证并发读是非常高效的,读写,写读 ,写写的过程是互斥的。
独享锁与共享锁也是通过AQS来实现的,通过实现不同的方法,来实现独享或者共享。
对于Synchronized而言,当然是独享锁。
偏向锁/轻量级锁/重量级锁
这三种锁是指锁的状态,并且是针对Synchronized。在Java 5通过引入锁升级的机制来实现高效Synchronized。这三种锁的状态是通过对象监视器在对象头中的字段来表明的。
偏向锁是指一段同步代码一直被一个线程所访问,那么该线程会自动获取锁。降低获取锁的代价。
轻量级锁是指当锁是偏向锁的时候,被另一个线程所访问,偏向锁就会升级为轻量级锁,其他线程会通过自旋的形式尝试获取锁,不会阻塞,提高性能。
重量级锁是指当锁为轻量级锁的时候,另一个线程虽然是自旋,但自旋不会一直持续下去,当自旋一定次数的时候,还没有获取到锁,就会进入阻塞,该锁膨胀为重量级锁。重量级锁会让其他申请的线程进入阻塞,性能降低。
CAS算法
全名:Compare And Swap(比较与交换)
无锁算法:基于硬件原语实现,在不使用锁(没有线程被阻塞)的清况下实现多线程之间的变量同步。
Jdk中实现:java.util.concurrent包中的原子类(Atomiclnteger)就是通过CAS来实现了乐观锁。
算法涉及到三个操作数:
需要读写的内存值V
进行比较的值A
要写入的新值B
CAS算法执行过程:两个线程同时操作一个变量他们都会在内存中生成一个副本,首先将原始数据先把参数V赋值给本地参数A,然后比较两个参数A和B,B为要修改后的参数,如果不同则需要修改,此时第一个进程去修改了V值,修改为1,当第二个线程修改时,先进行比较,发现V和A不相同,则成为脏值,程序无法更新值,只能自旋,等待或报错。
自旋锁:在进行完上面的行为后,线程二如果进入自旋,就相当于再把V值读给A,A和B进行比较后,再A和V进行比较,如果A和V相同了,就可以进行交换了。
自旋锁的概念可以描述为当一个线程在获取锁的时候,如果锁已经被其他线程获取,那么该线程就会循环等待,不断判断锁是否被成功获取,自旋直到锁被成功获取才推出循环。需要操作系统切换cpu状态,会耗费一定的时间。
AQS同步器—AbstractQueuedSynchronizer—抽象队列同步
此同步器是一个可重入锁