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{
@Override
public 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减1
this.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 out
for (;;) {
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 {
// 减1
semaphore.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 {
// 加1
semaphore.release();
}
}, "车" + i).start();
}
}
}
acquire()
获取一个令牌,在获取到令牌、或者被其他线程调用中断之前线程一直处于阻塞状态。
acquire(int permits)
获取一个令牌,在获取到令牌、或者被其他线程调用中断、或超时之前线程一直处于阻塞状态。
acquireUninterruptibly()
获取一个令牌,在获取到令牌之前线程一直处于阻塞状态(忽略中断)。
tryAcquire()
尝试获得令牌,返回获取令牌成功或失败,不阻塞线程。
tryAcquire(long timeout, TimeUnit unit)
尝试获得令牌,在超时时间内循环尝试获取,直到尝试获取成功或超时返回,不阻塞线程。
release()
释放一个令牌,唤醒一个获取令牌不成功的阻塞线程。
hasQueuedThreads()
等待队列里是否还存在等待线程。
getQueueLength()
获取等待队列里阻塞的线程数。
drainPermits()
清空令牌把可用令牌数置为0,返回清空令牌的数量。
availablePermits()
返回可用的令牌数量。