1、java锁之公平锁和非公平锁
公平锁 是指多个线程按照申请的顺序来获取,类似排队打饭,先来后到。
非公平锁 是指多个线程获取锁的顺序并不是按照申请锁的顺序,有可能后申请的线程比先申请的线程优先获取锁,在高并发的情况下,有可能会造成优先级反转或者饥饿现象。
关于两者的区别:
- 公平锁:Thread acquire a fair lock in the order in which they requested it
- 公平锁,就是很公平,在并发环境中每个线程在获取锁时会查看此锁维护的等待队列,如果为空,或者当前线程是等待队列的第一个,就是占有锁,否则就会加入到等待队列中,以后会按照FIFO的规则从队列中取到自己。
- 非公平锁: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 ReentrantLock而言
通过构造函数指定该锁是否公平锁,默认是非公平锁。非公平锁的有点在于吞吐量比公平锁大。
Lock lock = new ReentrantLock();//非公平锁
Lock lock = new ReentrantLock(true);//公平锁
对了Synchronized 而言,也是一种非公平锁
2、可重入锁(又名递归锁)
指对是同一线程外层函数获得锁之后,内层递归函数仍然能获取该锁对代码
在同一个线程在外层方法获取锁对时候,在进入内层方法会自动获取锁
也即是说,线程可以进入任何一个它已经拥有的锁所同步着的代码块
ReentrantLock/Synchronized 就是一个典型的可重入锁
作用:可重入锁最大的作用是避免死锁
1、Synchronized是可重入锁,如下:
public class RentrantLockTest2 {public static void main(String[] args) {Phone phone = new Phone();new Thread(()->{phone.sendSMS();},"线程1") .start();new Thread(()->{phone.sendSMS();},"线程2") .start();}
}class Phone{public synchronized void sendSMS(){System.out.println(Thread.currentThread().getName()+"\t sendSMS()");sendEmail();}public synchronized void sendEmail(){System.out.println(Thread.currentThread().getName()+"\t sendEmail()");}
}
结果:
线程1 sendSMS() ------线程1在外层方法获取锁
线程1 sendEmail() ------线程1在进入内层方法会自动获取锁
线程2 sendSMS()
线程2 sendEmail()
2、ReentrantLock实现可重入锁,如下:
public class ReentrantLockTest3 {public static void main(String[] args) {Car car = new Car();new Thread(car,"线程1").start();new Thread(car,"线程2").start();}
}class Car implements Runnable{@Overridepublic void run() {test();}Lock lock = new ReentrantLock();private void test() {lock.lock();try {System.out.println(Thread.currentThread().getName()+"\t test()");method();}finally {lock.unlock();}}private void method() {lock.lock();try {System.out.println(Thread.currentThread().getName()+"\t method()");}finally {lock.unlock();}}
}
结果:
线程1 test()
线程1 method()
线程2 test()
线程2 method()
lock和unlock配对就可以了,不管加多少对都不会出现错误。
3、独占锁/共享锁
独占锁:指该锁一次只能被一个线程锁所持有。(原子+独占,整个过程必须是一个完整的)
对ReentrantLock和Synchronized而言都是独占锁
共享锁:指该锁可以被多个线程所持有。
对ReentrantReadWriteLock其读锁是共享锁,其写锁是独占锁。
多个线程同时读一个资源类没有任何问题,所以为了满足并发量,读取共享资源应该可以同时进行。
但是如果一个线程想去写共享资源,就不应该再有线程可以对该资源进行读或写
/*** ReentrantReadWriteLock 读写锁*/public class ReadWriteLockTest {public static void main(String[] args) {MyCache myCache = new MyCache();for (int i=0;i<5;i++){final int temp=i;new Thread(()->{myCache.write(temp+"",temp+"");},"write线程"+i).start();}for (int i=0;i<5;i++){final int temp=i;new Thread(()->{myCache.read(temp+"");},"read线程"+i).start();}}
}class MyCache{private volatile Map<String,Object> map = new HashMap<>(16);private ReentrantReadWriteLock reentrantReadWriteLock = new ReentrantReadWriteLock();public void read(String key){reentrantReadWriteLock.readLock().lock();try{System.out.println(Thread.currentThread().getName()+"\t 正在读++++");try {Thread.sleep(300);} catch (InterruptedException e) {e.printStackTrace();}Object result = map.get(key);System.out.println(Thread.currentThread().getName()+"\t 读完了++++"+result);}catch (Exception e){}finally {reentrantReadWriteLock.readLock().unlock();}}public void write(String key,Object object){reentrantReadWriteLock.writeLock().lock();try{System.out.println(Thread.currentThread().getName()+"\t 正在写---"+key);try {Thread.sleep(300);} catch (InterruptedException e) {e.printStackTrace();}map.put(key,object);System.out.println(Thread.currentThread().getName()+"\t 写完了----");}catch (Exception e){}finally {reentrantReadWriteLock.writeLock().unlock();}}}
没有使用ReentrantReadWriteLock的结果:
write线程0 正在写---0
write线程1 正在写---1
write线程2 正在写---2
write线程3 正在写---3
write线程4 正在写---4
read线程0 正在读++++
read线程1 正在读++++
read线程2 正在读++++
read线程3 正在读++++
read线程4 正在读++++
write线程0 写完了----
write线程1 写完了----
write线程2 写完了----
write线程4 写完了----
read线程2 读完了++++2
write线程3 写完了----
read线程3 读完了++++3
read线程1 读完了++++1
read线程0 读完了++++0
read线程4 读完了++++4
使用ReentrantReadWriteLock的结果:
write线程0 正在写---0
write线程0 写完了----
write线程2 正在写---2
write线程2 写完了----
write线程1 正在写---1
write线程1 写完了----
write线程3 正在写---3
write线程3 写完了----
write线程4 正在写---4
write线程4 写完了----
read线程0 正在读++++
read线程1 正在读++++
read线程2 正在读++++
read线程3 正在读++++
read线程4 正在读++++
read线程2 读完了++++2
read线程4 读完了++++4
read线程3 读完了++++3
read线程0 读完了++++0
read线程1 读完了++++1
4、自旋锁
自旋锁(spinLock)
是指尝试获取锁的线程不会立即阻塞,而是采用循环的方式去尝试获取锁,这样的好处是减少线程上下文切换的消耗,缺点是循环会消耗CPU
下面是AtomicInteger的compareAndSet调用的底层的UnSafe的getAndAddInt就是采用了自旋锁的机制,
public class SpinLock {public static void main(String[] args) {SpinLockDome spinLockDome = new SpinLockDome();new Thread(()->{spinLockDome.myLock();try {Thread.sleep(5);} catch (InterruptedException e) {e.printStackTrace();}spinLockDome.myUnLock();},"线程1").start();try {Thread.sleep(1);} catch (InterruptedException e) {e.printStackTrace();}new Thread(()->{spinLockDome.myLock();spinLockDome.myUnLock();},"线程2").start();}
}class SpinLockDome{AtomicReference<Thread> atomicReference = new AtomicReference<>();//加锁public void myLock(){Thread thread = Thread.currentThread();System.out.println(thread.getName()+"\t come lock");while(!atomicReference.compareAndSet(null,thread)){}}//解锁public void myUnLock(){Thread thread = Thread.currentThread();System.out.println(thread.getName()+"\t come in unlock");atomicReference.compareAndSet(thread,null);}
}
结果:
线程1 come lock
线程2 come lock
线程1 come in unlock
线程2 come in unlock
5、CountDownLatch
public static void main(String[] args) {CountDownLatch countDownLatch = new CountDownLatch(5);System.out.println("我要锁门了");for (int i=0;i<5;i++){new Thread(()->{System.out.println(Thread.currentThread().getName()+"\t班长等我出去在锁门...");countDownLatch.countDown();//每次减1},"同学"+i).start();}try {countDownLatch.await();//只有当countDownLatch.getCount()等于0当时候才会释放,} catch (InterruptedException e) {e.printStackTrace();}System.out.println("我锁门了");}
结果:
我要锁门了
同学0 班长等我出去在锁门...
同学1 班长等我出去在锁门...
同学2 班长等我出去在锁门...
同学3 班长等我出去在锁门...
同学4 班长等我出去在锁门...
我锁门了
6、CyclicBarrier
和CountDownLock有点相反:CyclicBarrier的字面意思是可循环(Cyclic)使用的屏障(Barrier)。他要做的事情是,让一组线程到达一个屏障(也可以叫同步点)时被阻塞,直到最后一个线程到达屏障时,屏障才会开门,所有被屏障拦截的线程才会继续干活,线程进入屏障通过Cyclicbarrier的await()方法
public class CyclibarrierTest {public static void main(String[] args) {CyclicBarrier cyclicBarrier = new CyclicBarrier(5,()->{System.out.println("既然人都到齐了,我们就开始开会吧...");});for (int i=0;i<5;i++) {new Thread(() -> {System.out.println(Thread.currentThread().getName() + "\t等待其他学生开会...");try {cyclicBarrier.await();} catch (InterruptedException e) {e.printStackTrace();} catch (BrokenBarrierException e) {e.printStackTrace();}}, "同学" + i).start();}}
}
结果:同学0 等待其他学生开会...
同学3 等待其他学生开会...
同学2 等待其他学生开会...
同学1 等待其他学生开会...
同学4 等待其他学生开会...
既然人都到齐了,我们就开始开会吧...
public class CyclicBarrier {/*** Each use of the barrier is represented as a generation instance.* The generation changes whenever the barrier is tripped, or* is reset. There can be many generations associated with threads* using the barrier - due to the non-deterministic way the lock* may be allocated to waiting threads - but only one of these* can be active at a time (the one to which {@code count} applies)* and all the rest are either broken or tripped.* There need not be an active generation if there has been a break* but no subsequent reset.*/private static class Generation {//内部类,这个类很重要boolean broken = false;}/** The lock for guarding barrier entry */private final ReentrantLock lock = new ReentrantLock();/** Condition to wait on until tripped */private final Condition trip = lock.newCondition();/** The number of parties */private final int parties;/* The command to run when tripped */private final Runnable barrierCommand;/** The current generation */private Generation generation = new Generation();//构建CyclicBarrier对象public CyclicBarrier(int parties, Runnable barrierAction) {if (parties <= 0) throw new IllegalArgumentException();this.parties = parties;//总共需要等几个this.count = parties;//计数的,每次调用await减1this.barrierCommand = barrierAction;}//这个方法就是await方法的实现private int dowait(boolean timed, long nanos)throws InterruptedException, BrokenBarrierException,TimeoutException {final ReentrantLock lock = this.lock;lock.lock();//加锁,防止被打断try {final Generation g = generation;if (g.broken)throw new BrokenBarrierException();if (Thread.interrupted()) {breakBarrier();throw new InterruptedException();}int index = --count;//在等线程的总量上一直减if (index == 0) { // tripped 当减到0时boolean ranAction = false;try {final Runnable command = barrierCommand;if (command != null)//如果barrierCommand这个不为null,就执行command.run();ranAction = true;nextGeneration();return 0;} finally {if (!ranAction)breakBarrier();}}// loop until tripped, broken, interrupted, or timed outfor (;;) {try {if (!timed)trip.await();else if (nanos > 0L)nanos = trip.awaitNanos(nanos);} catch (InterruptedException ie) {if (g == generation && ! g.broken) {breakBarrier();throw ie;} else {// We're about to finish waiting even if we had not// been interrupted, so this interrupt is deemed to// "belong" to subsequent execution.Thread.currentThread().interrupt();}}if (g.broken)throw new BrokenBarrierException();if (g != generation)return index;if (timed && nanos <= 0L) {breakBarrier();throw new TimeoutException();}}} finally {lock.unlock();}}
7、Semaphore
信号量主要用于两个目的,一个是用于多个共享资源的互斥使用,另外一个用于并发线程数的控制。
public class SemaphreTest {public static void main(String[] args) {Semaphore semaphore = new Semaphore(3);for (int i=0;i<6;i++) {new Thread(() -> {try {
// 减1semaphore.acquire();System.out.println(Thread.currentThread().getName()+"\t抢到了车位...");Thread.sleep(3);System.out.println(Thread.currentThread().getName()+"\t停了3秒钟就走了...");} catch (InterruptedException e) {e.printStackTrace();}finally {
// 加1semaphore.release();}}, "车" + i).start();}}
}
acquire()
获取一个令牌,在获取到令牌、或者被其他线程调用中断之前线程一直处于阻塞状态。
acquire(int permits)
获取一个令牌,在获取到令牌、或者被其他线程调用中断、或超时之前线程一直处于阻塞状态。acquireUninterruptibly()
获取一个令牌,在获取到令牌之前线程一直处于阻塞状态(忽略中断)。tryAcquire()
尝试获得令牌,返回获取令牌成功或失败,不阻塞线程。
tryAcquire(long timeout, TimeUnit unit)
尝试获得令牌,在超时时间内循环尝试获取,直到尝试获取成功或超时返回,不阻塞线程。
release()
释放一个令牌,唤醒一个获取令牌不成功的阻塞线程。
hasQueuedThreads()
等待队列里是否还存在等待线程。
getQueueLength()
获取等待队列里阻塞的线程数。
drainPermits()
清空令牌把可用令牌数置为0,返回清空令牌的数量。
availablePermits()
返回可用的令牌数量。