锁---一些锁学习

1、java锁之公平锁和非公平锁

公平锁 是指多个线程按照申请的顺序来获取,类似排队打饭,先来后到。

非公平锁  是指多个线程获取锁的顺序并不是按照申请锁的顺序,有可能后申请的线程比先申请的线程优先获取锁,在高并发的情况下,有可能会造成优先级反转或者饥饿现象。

关于两者的区别:

  1. 公平锁:Thread acquire a fair lock in the order in which they requested it
  2. 公平锁,就是很公平,在并发环境中每个线程在获取锁时会查看此锁维护的等待队列,如果为空,或者当前线程是等待队列的第一个,就是占有锁,否则就会加入到等待队列中,以后会按照FIFO的规则从队列中取到自己。
  3. 非公平锁: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
  4. 非公平锁比较粗鲁,上来就直接占有锁,如果尝试失败,就再采用类似公平锁那种方式。

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()
返回可用的令牌数量。
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值