参考:
http://www.cnblogs.com/xingzc/p/5750152.html
http://www.cnblogs.com/tison/p/8283233.html
1.公平锁与非公平锁
[1].公平锁
是指多个线程按照申请锁的顺序来获取锁,类似排队
[2].非公平锁
是指多个线程获取锁的顺序并不是按照申请锁的顺序,有可能后申请的线程比先申请的线程优先获得锁,在高并发情况下,有可能会造成优先级反转或者饥饿现象
[3].两者的区别并发包中ReentrantLock的创建可以指定构造函数的boolean类型来得到公平锁或非公平锁,默认即非公平锁
/**
* Creates an instance of {@code ReentrantLock}.
* This is equivalent to using {@code ReentrantLock(false)}.
*/
public ReentrantLock() {
sync = new NonfairSync();
}
/**
* Creates an instance of {@code ReentrantLock} with the
* given fairness policy.
*
* @param fair {@code true} if this lock should use a fair ordering policy
*/
public ReentrantLock(boolean fair) {
sync = fair ? new FairSync() : new NonfairSync();
}
①.公平锁:
Threads acquire a fair lock in the order in which they requested it
公平锁,就是很公平,在并发环境中,每个线程在获取锁时会先查看此锁维护的等待队列,如果为空,或者当前线程是等待队列的第一个,就占有锁, 否则就会加入到等待队列中,以后会按照FIFO的规则从队列中取到自己
java.util.concurrent.locks.ReentrantLock$FairSync.java
protected final boolean tryAcquire( int acquires) {
final Thread current = Thread.currentThread();
int c = getState();
//状态为0,说明当前没有线程占有锁
if (c == 0 ) {
//如果当前线程是等待队列的第一个或者等待队列为空,则通过cas指令设置state为1,当前线程获得锁
if (isFirst(current) &&
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 ;
}
②.非公平锁:
a nonfair lock permits barging: threads requesting a lock can jump ahead of the queue of waiting threads if the lock happens to be available when it is requested.
非公平锁比较粗鲁,上来就直接尝试占有锁,如果尝试失败,就再采用类似公平锁那种方式
java.util.concurrent.locks.ReentrantLock$Sync.java
final boolean nonfairTryAcquire(int acquires) {
final Thread current = Thread.currentThread();
int c = getState();
if (c == 0) {
//如果当前没有线程占有锁,当前线程直接通过cas指令占有锁,管他等待队列,就算自己排在队尾也是这样
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;
}
[4].非公平锁比公平锁吞吐量大
[5].synchronized也是一种非公平锁
2.可重入锁
[1].重入锁(ReentrantLock)是一种递归无阻塞的同步机制。重入锁,也叫做递归锁,指的是同一线程 外层函数获得锁之后 ,内层递归函数仍然有获取该锁的代码,在同一个线程外层方法获取锁的时候,在进入内层方法会自动获得锁,也就是说,线程可以进入它任何一个它已经拥有的锁锁同步着的代码块。
[2].ReentrantLock 和synchronized 都是 可重入锁。
package com.w4xj.interview.thread;
import java.util.concurrent.TimeUnit;
import java.util.concurrent.locks.Lock;
import java.util.concurrent.locks.ReentrantLock;
/**
* @Author by w4xj
* @Classname ReentrantLockTest
* @Description TODO
* @Date 2019/5/6 20:53
* @Created by IDEA
*/
public class ReentrantLockTest {
public static void main(String[] args) {
CXK cxk = new CXK();
new Thread(cxk, "Thread1").start();
new Thread(cxk, "Thread2").start();
try { TimeUnit.SECONDS.sleep(1); } catch (InterruptedException e) { e.printStackTrace(); }
System.out.println("-----------------------------------------");
new Thread(() ->{ cxk.sing(); },"Thread3").start();
new Thread(() ->{ cxk.sing(); },"Thread3").start();
/*
打印:
Thread1 music~
Thread1 basketball~
Thread2 music~
Thread2 basketball~
-----------------------------------------
Thread3 sing~
Thread3 jump~
Thread3 rap~
Thread3 sing~
Thread3 jump~
Thread3 rap~
*/
}
}
class CXK implements Runnable{
Lock lock = new ReentrantLock();
@Override
public void run() {
music();
}
public void music(){
//注意这里双锁依然是可以,但是锁一定要配对,不然会真正的死锁
lock.lock();
lock.lock();
try {
System.out.println(Thread.currentThread().getName() + " music~");
basketball();
}finally {
lock.unlock();
lock.unlock();
}
}
public void basketball(){
lock.lock();
try {
System.out.println(Thread.currentThread().getName() + " basketball~");
}finally {
lock.unlock();
}
}
public synchronized void sing(){
System.out.println(Thread.currentThread().getName() + " sing~");
jump();
}
public synchronized void jump(){
System.out.println(Thread.currentThread().getName() + " jump~");
rap();
}
public synchronized void rap(){
System.out.println(Thread.currentThread().getName() + " rap~");
}
}
[3].可重入锁最大的作用就是避免死锁
3.自旋锁
[1].自旋锁,由于自旋锁使用者一般保持锁时间非常短,因此选择自旋而不是睡眠是非常必要的,自旋锁的效率远高于互斥锁。如何旋转呢?何为自旋锁,就是如果发现锁定了,不是睡眠等待,而是采用让当前线程不停地的在循环体内执行实现的,当循环的条件被其他线程改变时 才能进入临界区。这样的好处是减少上下文切换的消耗,缺点是会消耗CPU。Unsafe类的getAndAddxxx就是典型的自旋锁
public final int getAndAddInt(Object var1, long var2, int var4) {
int var5;
do {
var5 = this.getIntVolatile(var1, var2);
} while(!this.compareAndSwapInt(var1, var2, var5, var5 + var4));
return var5;
}
[2].手写自旋锁
package com.w4xj.interview.thread;
import java.util.concurrent.TimeUnit;
import java.util.concurrent.atomic.AtomicReference;
/**
* @Author by w4xj
* @Classname SpinLock
* @Description TODO
* @Date 2019/5/7 8:07
* @Created by IDEA
*/
public class SpinLock {
AtomicReference<Thread> atomicReference = new AtomicReference<>();
/**
* 获得锁
*/
public void lock(){
Thread thread = Thread.currentThread();
System.out.println(thread.getName() + " try to get the lock");
//自旋
while (!atomicReference.compareAndSet(null, thread)){
//获取锁失败
}
//已经获得锁
System.out.println(thread.getName() + " got the lock");
}
/**
* 解锁
*/
public void unlock() {
Thread thread = Thread.currentThread();
atomicReference.compareAndSet(thread,null);
System.out.println(thread.getName() + " release the lock");
}
public static void main(String[] args) {
SpinLock spinLock = new SpinLock();
new Thread(() ->{
spinLock.lock();
try { TimeUnit.SECONDS.sleep(3); } catch (InterruptedException e) { e.printStackTrace(); }
spinLock.unlock();
},"ThreadA").start();
try { TimeUnit.SECONDS.sleep(1); } catch (InterruptedException e) { e.printStackTrace(); }
new Thread(() ->{
spinLock.lock();
spinLock.unlock();
},"ThreadB").start();
/*
打印:
ThreadA try to get the lock
ThreadA got the lock
ThreadB try to get the lock
ThreadA release the lock
ThreadB got the lock
ThreadB release the lock
*/
}
}
4.独占锁(写锁)、共享锁(读锁)与互斥锁
[1].独占锁:指该锁一次只能被一个线程所持有。对ReentrantLock和Synchronized而言都是独占锁
[2].共享锁:指该锁可以被多个线程所持有。对ReentrantReadWriteLock其读锁是共享锁,其写锁是独占锁。
[3].读锁的共享锁可以保证并发是非常高效的,读写、写读、写写的过程都是互斥的
[4].ReentrantReadWriteLock代码案例,加锁和不加锁的对比
package com.w4xj.interview.thread;
import java.util.HashMap;
import java.util.Map;
import java.util.concurrent.TimeUnit;
import java.util.concurrent.locks.ReentrantReadWriteLock;
/**
* @Author by w4xj
* @Classname ReentrantReadWriteLockTest
* @Description TODO
* @Date 2019/5/7 8:41
* @Created by IDEA
*/
public class ReentrantReadWriteLockTest {
public static void main(String[] args) {
Cache cache1 = new Cache();
Cache cache2 = new Cache();
//cache1会产生写争抢,因为没有加锁
for (int i = 0 ; i < 4 ; i++){
final String key = String.valueOf(i);
final Integer value = i;
new Thread(() ->{
cache1.put1(key, value);
},"threadName" + i).start();
}
while (Thread.activeCount() > 2){
Thread.yield();
}
for (int i = 0 ; i < 4 ; i++){
final String key = String.valueOf(i);
new Thread(() ->{
cache1.get1(key);
},"threadName" + i).start();
}
try { TimeUnit.SECONDS.sleep(10); } catch (InterruptedException e) { e.printStackTrace(); }
System.out.println("--------------------------------------");
//cache2不会会产生写争抢,因为put方法加了write锁
for (int i = 10 ; i < 14 ; i++){
final String key = String.valueOf(i);
final Integer value = i;
new Thread(() ->{
cache2.put2(key, value);
},"threadName" + i).start();
}
while (Thread.activeCount() > 2){
Thread.yield();
}
for (int i = 10 ; i < 14 ; i++){
final String key = String.valueOf(i);
new Thread(() ->{
cache2.get2(key);
},"threadName" + i).start();
}
/*
打印:
threadName0 正在写入(0,0)
threadName1 正在写入(1,1)
threadName2 正在写入(2,2)
threadName3 正在写入(3,3)
threadName3 写入(3,3)完成
threadName1 写入(1,1)完成
threadName2 写入(2,2)完成
threadName0 写入(0,0)完成
threadName0 正在读取0
threadName1 正在读取1
threadName2 正在读取2
threadName3 正在读取3
threadName1 读取(1,1)完成
threadName0 读取(0,0)完成
threadName2 读取(2,2)完成
threadName3 读取(3,3)完成
--------------------------------------
threadName11 正在写入(11,11)
threadName11 写入(11,11)完成
threadName10 正在写入(10,10)
threadName10 写入(10,10)完成
threadName13 正在写入(13,13)
threadName13 写入(13,13)完成
threadName12 正在写入(12,12)
threadName12 写入(12,12)完成
threadName10 正在读取10
threadName11 正在读取11
threadName12 正在读取12
threadName13 正在读取13
threadName10 读取(10,10)完成
threadName13 读取(13,13)完成
threadName11 读取(11,11)完成
threadName12 读取(12,12)完成
*/
}
}
class Cache {
private volatile Map<String, Object> map = new HashMap<>();
private ReentrantReadWriteLock readWriteLock = new ReentrantReadWriteLock();
public void put1(String key, Object value) {
System.out.println(Thread.currentThread().getName() + "\t 正在写入(" + key + "," + value + ")");
//睡0.1秒
try { TimeUnit.MILLISECONDS.sleep(100); } catch (InterruptedException e) { e.printStackTrace(); }
map.put(key, value);
System.out.println(Thread.currentThread().getName() + "\t 写入(" + key + "," + value + ")完成");
}
public void get1(String key){
System.out.println(Thread.currentThread().getName() + "\t 正在读取" + key + "");
//睡0.1秒
try { TimeUnit.MILLISECONDS.sleep(100); } catch (InterruptedException e) { e.printStackTrace(); }
Object value = map.get(key);
System.out.println(Thread.currentThread().getName() + "\t 读取(" + key + "," + value + ")完成");
}
public void put2(String key, Object value) {
readWriteLock.writeLock().lock();
try {
System.out.println(Thread.currentThread().getName() + "\t 正在写入(" + key + "," + value + ")");
//睡0.1秒
try { TimeUnit.MILLISECONDS.sleep(100); } catch (InterruptedException e) { e.printStackTrace(); }
map.put(key, value);
System.out.println(Thread.currentThread().getName() + "\t 写入(" + key + "," + value + ")完成");
} catch (Exception e) {
e.printStackTrace();
} finally {
readWriteLock.writeLock().unlock();
}
}
public void get2(String key){
readWriteLock.readLock().lock();
try {
System.out.println(Thread.currentThread().getName() + "\t 正在读取" + key + "");
//睡0.1秒
try { TimeUnit.MILLISECONDS.sleep(100); } catch (InterruptedException e) { e.printStackTrace(); }
Object value = map.get(key);
System.out.println(Thread.currentThread().getName() + "\t 读取(" + key + "," + value + ")完成");
}catch (Exception e){
e.printStackTrace();
}finally {
readWriteLock.readLock().unlock();
}
}
}
5.补充:synchronized和Lock的区别
[1].原始构成
①.synchronized是关键字属于JVM层面,monitorenter/monitorexit(底层是通过monitor对象来完成,其实wait/notify等方法也依赖monitor对象只有在同步代码块和同步方法中才能调用wait/notify等方法)
②.Lock是具体的类,是api层面的锁;
[2].使用方法
synchronized不需要用户手动释放锁,synchronized代码执行完成以后系统会自动让线程释放对锁的占有
ReentrantLock则需要用户手动去释放锁,若没有主动释放锁,就有可能导致死锁现象。需要使用lock()和unlock()方法配合try finally语句块来完成。
[3].等待是否可以中断
①.synchronized不可中断,除非抛出异常或者正常运行完成。
②.ReetrantLock可中断,
a.设置超时方法tryLock(long timeout, TimeUnit unit);
b.lockInterruptibly()放入代码块中,调用interrupt()方法可中断;
[4].加锁是否公平
①.synchronized是非公平锁
②.ReentrantLock默认是非公平锁,可设置为公平锁。
[5].锁绑定多个条件condition
①.synchronized没有;
②.ReentrantLock用来实现分组唤醒需要唤醒的线程们,可以精确唤醒,而不是像synchronized要么随机唤醒一个,要么唤醒全部线程。
package com.w4xj.interview.thread;
import java.util.concurrent.locks.Condition;
import java.util.concurrent.locks.Lock;
import java.util.concurrent.locks.ReentrantLock;
/**
* @Author by w4xj
* @Classname MultipleCondition
* @Description 3个线程交替输出ABC
* @Date 2019/5/9 8:47
* @Created by IDEA
*/
public class MultipleCondition {
public static void main(String[] args) {
ShareResouce shareResouce = new ShareResouce();
new Thread(() ->{
for (int i = 0; i < 10; i++) {
shareResouce.printA();
}
},"ThreadA").start();
new Thread(() ->{
for (int i = 0; i < 10; i++) {
shareResouce.printB();
}
},"ThreadB").start();
new Thread(() ->{
for (int i = 0; i < 10; i++) {
shareResouce.printC();
}
},"ThreadC").start();
/*
打印:
ThreadA A
ThreadB B
ThreadC C
...
ThreadA A
ThreadB B
ThreadC C
*/
}
}
//资源类
class ShareResouce{
/**
* 标志位
*/
private int current = 1;
/**
* 锁
*/
private Lock lock = new ReentrantLock();
/**
* 多条件
*/
private Condition condition1 = lock.newCondition();
private Condition condition2 = lock.newCondition();
private Condition condition3 = lock.newCondition();
public void printA(){
lock.lock();
try {
while (current != 1){
condition1.await();
}
System.out.println(Thread.currentThread().getName() + " A");
//修改标志位
current = 2;
//唤醒
condition2.signal();
}catch (Exception e){
e.printStackTrace();
}finally {
lock.unlock();
}
}
public void printB(){
lock.lock();
try {
while (current != 2){
condition2.await();
}
System.out.println(Thread.currentThread().getName() + " B");
//修改标志位
current = 3;
//唤醒
condition3.signal();
}catch (Exception e){
e.printStackTrace();
}finally {
lock.unlock();
}
}
public void printC(){
lock.lock();
try {
while (current != 3){
condition3.await();
}
System.out.println(Thread.currentThread().getName() + " C");
//修改标志位
current = 1;
//唤醒
condition1.signal();
}catch (Exception e){
e.printStackTrace();
}finally {
lock.unlock();
}
}
}
如果考虑到上述特性,就用ReentrantLock
[6].性能:两者的性能已经相差无几
在JDK1.6之前,synchronized 的性能是比 ReenTrantLock 差很多。具体表示为:synchronized 关键字吞吐量岁线程数的增加,下降得非常严重。而ReenTrantLock 基本保持一个比较稳定的水平。我觉得这也侧面反映了, synchronized 关键字还有非常大的优化余地。后续的技术发展也证明了这一点,我们上面也讲了在 JDK1.6 之后 JVM 团队对 synchronized 关键字做了很多优化。JDK1.6 之后,synchronized 和 ReenTrantLock 的性能基本是持平了。所以网上那些说因为性能才选择 ReenTrantLock 的文章都是错的!JDK1.6之后,性能已经不是选择synchronized和ReenTrantLock的影响因素了!而且虚拟机在未来的性能改进中会更偏向于原生的synchronized,所以还是提倡在synchronized能满足你的需求的情况下,优先考虑使用synchronized关键字来进行同步!优化后的synchronized和ReenTrantLock一样,在很多地方都是用到了CAS操作。