CountDownLanuch,CyclicBarrier,Semaphore,Lock问题

一.你在项目中用过CountDownLanuch,CyclicBarrier,Semaphore吗?

  1.CountDownLanuch是一个同步的工具类,它允许一个或多个线程一直等待,直到其他线程执行完毕后才会继续往后执行.

     通过内部的计数器实现的,计数器的初始化为线程的数量,每当一个线程执行完毕后,就减1,当计数器达到0时,表示所有的

     线程都执行完毕,可以继续执行后面的代码.(类似于火箭发射倒计时)

public static void show01(int n){
      
        CountDownLatch countDownLatch = new CountDownLatch(n);//计数器初始化线程的个数

        // 开启6个线程,当countDownLatch减到0时,主线程运行
        for (int i = 1; i <= 6; i++) {
            new Thread(() -> {
                System.out.println(Thread.currentThread().getName() + "\t晚自习,离开教室" );
                countDownLatch.countDown();//计数器减1
            },String.valueOf(i)).start();
        }

        try {
            countDownLatch.await();//计数器等待
        } catch (InterruptedException e) {
            e.printStackTrace();
        }
        System.out.println(Thread.currentThread().getName() + "\t 班长最后关门,离开");
    }

运行结果

 

 

   第二个案例:结合枚举来使用

public static void show02(int n){
        CountDownLatch countDownLatch = new CountDownLatch(n);
        for (int i = 1; i <= n; i++) {
            new Thread(() -> {
                System.out.println(Thread.currentThread().getName()+"被灭了");
                countDownLatch.countDown();
            },CountryEnum.list(i).getRetCode()).start();
        }
        try {
            countDownLatch.await();
        } catch (InterruptedException e) {
            e.printStackTrace();
        }
        System.out.println(Thread.currentThread().getName() + "\t秦朝统一");

    }
public enum CountryEnum {
    ONE(1,"齐国"),TWO(2,"楚国"),THREE(3,"燕国"),
    FOUR(4,"赵国"),FIVE(5,"魏国"),SIX(6,"韩国");

    private Integer retId;
    private String retCode;

    public Integer getRetId() {
        return retId;
    }

    public String getRetCode() {
        return retCode;
    }

    CountryEnum(Integer retId, String retCode) {
        this.retId = retId;
        this.retCode = retCode;
    }

    CountryEnum() {
    }

    public static CountryEnum list(int index){
        //通过线程的编号,获取对应的国家
        CountryEnum[] countries = CountryEnum.values();
        for (CountryEnum country : countries) {
            if(country.getRetId() == index){
                return country;
            }
        }
        return null;
    }
}

运行结果

 

 

   2.CyclicBarrier它指的是可循环的使用屏障,它的主要功能是让一组线程达到一个屏障时被阻塞,直到最后一个线程达到屏障

    时,屏障才会开门,所有被拦截的线程才会继续工作,线程进入屏障就是调用的是await方法(类比于开会,人到齐了才能够开始)

public static void main(String[] args) {
        // 当7个线程执行完毕后,CyclicBarrier中的线程才会执行,计数器加1
        CyclicBarrier cyclicBarrier = new CyclicBarrier(7,()->{
            System.out.println("人到齐了,会议开始");
        });
        for (int i = 1; i <= 7; i++) {
            final int temInt = i;
            new Thread(()->{
                try {
                    System.out.println("第"+temInt+"个人已经进入会议室等待了");
                    cyclicBarrier.await();
                } catch (InterruptedException e) {
                    e.printStackTrace();
                } catch (BrokenBarrierException e) {
                    e.printStackTrace();
                }
            },String.valueOf(i)).start();
        }


    }

运行结果

 

 

   3.Semaphore信号量,用于多个共享资源的互斥使用,用于并发线程的控制(类比于,停车场抢车位)

public static void main(String[] args) {
        //有3个停车位,6辆车
        Semaphore semaphore = new Semaphore(3);//3个车位
        for (int i = 1; i <= 6; i++) {
            final int tmpInt = i;
            new Thread(() ->{
                try {
                    semaphore.acquire();//获取资源
                    System.out.println("第"+tmpInt+"号车抢到车位");
                    TimeUnit.SECONDS.sleep(5);
                    System.out.println("第"+tmpInt+"号车,停了5秒...");
                } catch (InterruptedException e) {
                    e.printStackTrace();
                }finally {
                    semaphore.release();//释放资源,再来获取资源,直到所有的线程都执行完毕
                }
            },String.valueOf(i)).start();
        }
    }

运行结果

 

 

 

二. 请你谈谈对锁的理解,什么是公平锁/非公平锁,什么是递归锁,什么是读写锁,什么是自旋锁,是否可以写一个自旋锁

  1.公平锁/非公平锁

    公平锁:在并发环境下,每个线程在获取锁的时候会先查看锁等待的序列,如果为空,或者当前线程是等待队列的第

      一个,就占着锁,以后会按照先进先出的顺序从队列中依次的执行(类比于,排队问问题)

    非公平锁:线程上来就直接尝试占有锁,如果尝试失败,就采用公平锁的策略(类比于,排队问问题,有人插队,但被喝止,只能排队)

    非公平锁的优点在于吞吐量比公平锁大   

     1.Lock lock = new ReentrantLock(); fair默认为false 是非公平锁,如果fair为true,就是公平锁

     2.synchronized 而言,它是非公平锁

  2.递归锁(可重用锁)

     递归锁:指的是同一个线程,外层的函数获得锁之后,内层的递归函数任然可以获取该锁的递归代码.在同一个线程在外层

       方法获取锁的时候,在进入内层方法就会自动的获取锁.也就是说,线程可以进入任何一个它已经拥有锁的同步代码块

       (类比于获得了大门的钥匙,在进入厨房,不需要再开锁)

        ReentrantLock和 Synchronized 都是可重用锁

   优点:这样可以避免死锁的产生

   面试题:多个Lock对象会不会产生编译错误/运行错误

    多个Lock对象不会产生编译错误,运行错误,如果lock,unlock可以正常匹配,那么代码会正常执行,退出.如果不匹配,线程就不会释放锁

    从而,会一直请求释放锁对象,即卡死

 

class Phone implements Runnable{
    // 发短信,成功的话调用法Email的方法
    public synchronized void sendSMS() throws Exception{
        System.out.println(Thread.currentThread().getName() + "\t" + "invoke sendSMS");
        sendEmail();
    }

    public synchronized void sendEmail() throws Exception{
        System.out.println(Thread.currentThread().getName() + "\t" + "invoke sendEmail");
    }

    Lock lock = new ReentrantLock();
    @Override
    public void run() {

        get();
    }
    //get方法,里面调用了set方法
    public void get(){
        lock.lock();
        try {
            System.out.println(Thread.currentThread().getName() + "\t" + "invoke get");
            set();
        }catch (RuntimeException e){
            e.printStackTrace();
        }finally {
            lock.unlock();
        }
    }

    public void set(){
        lock.lock();
        try {
            System.out.println(Thread.currentThread().getName() + "\t" + "invoke set");
        }catch (RuntimeException e){
            e.printStackTrace();
        }finally {
            lock.unlock();
        }
    }
/*使用synchronized证明可重用锁*/
    public static void show1(Phone phone){
        new Thread(() -> {
            try {
                phone.sendSMS();
            } catch (Exception e) {
                e.printStackTrace();
            }
        },"T1").start();

        new Thread(() -> {
            try {
                phone.sendSMS();
            } catch (Exception e) {
                e.printStackTrace();
            }
        },"T2").start();
    }

运行结果:

/*使用ReentrantLock来证明可重用锁*/
    public static void show2(Phone phone) {
        Thread t3 = new Thread(phone,"T3");
        Thread t4 = new Thread(phone,"T4");
        t3.start();
        t4.start();
    }

运行结果

 

   3.读写锁

      读写锁:多线程共同读一份资源类没有问题,为了满足业务的并发量,多线程读取共享的资源可以同时进行,但是

   如果有一个线程需要对资源进行修改,就不应该让其他的线程对该资源进行读写操作

   读  -  读   可以共存

   读  -  写   不可共存

      写  -  写   不可共存

class MyCache{
    // 存放数据,操作数据
    private volatile Map<String,Object> map = new HashMap<>();
    // 读写锁
    private ReentrantReadWriteLock rwLock = new ReentrantReadWriteLock();

    // 写操作
    public void put(String key,Object value){
        rwLock.writeLock().lock();
        try {
            System.out.println(Thread.currentThread().getName() + "\t写入数据");
            TimeUnit.MICROSECONDS.sleep(500);
            map.put(key,value);
            System.out.println(Thread.currentThread().getName() + "\t写入完成");
        } catch (Exception e) {
            e.printStackTrace();
        }finally {
            rwLock.writeLock().unlock();
        }
    }
 
    // 读操作
    public void get(String key){
        rwLock.readLock().lock();
        try {
            System.out.println(Thread.currentThread().getName() + "\t读取数据");
            TimeUnit.MICROSECONDS.sleep(500);
            Object value = map.get(key);
            System.out.println(Thread.currentThread().getName() + "\t读取完成"+value);
        } catch (Exception e) {
            e.printStackTrace();
        }finally {
            rwLock.readLock().unlock();
        }
    }

    public void clear(){
        map.clear();
    }
}
 public static void main(String[] args) {
        MyCache ca = new MyCache();
        // 启用5个线程,同时进行写操作
        for (int i = 1; i <= 5; i++) {
            final int temInt = i;
            new Thread(()->{
                ca.put(temInt+"",temInt+"");
            },String.valueOf(i)).start();
        }

        try {
            TimeUnit.SECONDS.sleep(1);
        } catch (InterruptedException e) {
            e.printStackTrace();
        }
        // 启用5个线程,同时进行读操作
        for (int i = 1; i <= 5; i++) {
            final int temInt = i;
            new Thread(()->{
                ca.get(temInt+"");
            },String.valueOf(i)).start();
        }
    }

运行结果

 

 

  多线程下的读/写操作没有插队

  4.自旋锁

     自旋锁:尝试获取锁的线程不会立刻阻塞,而是采取循环的方式去获取锁,这样可以减少线程上下文切换的消耗,但会

   增大CPU的开销

public class SingleLock {

    /*手写一个自旋锁*/
    static AtomicReference<Thread> atomicReference = new AtomicReference<>();

    /*加锁*/
    public static void lock(){
        /*如果当前没有线程,则使用当前线程*/
        System.out.println(Thread.currentThread().getName() + "\tget lock");
        // 如果当前的线程不是第一次进来,就会在while这里一直死循环,程序无法进行
        while (!atomicReference.compareAndSet(null,Thread.currentThread())){

        }
    }

    /*解锁*/
    public static void unLock(){
        /*如果判断是当前的线程,则清空*/
        System.out.println(Thread.currentThread().getName() + "\tinvoke lock");
        atomicReference.compareAndSet(Thread.currentThread(),null);
    }

    /*自旋锁*/
    public static void main(String[] args) {
        
        new Thread(() ->{
            lock();
            try {
                TimeUnit.SECONDS.sleep(3);
            } catch (InterruptedException e) {
                e.printStackTrace();
            }
            unLock();
        },"T1").start();

        try {
            TimeUnit.SECONDS.sleep(1);
        } catch (InterruptedException e) {
            e.printStackTrace();
        }

        new Thread(()->{
            lock();
            unLock();
        },"T2").start();
    }
}

运行结果

三.什么是死锁,能否实现一个死锁

  死锁:指的是两个或两个以上的进程在执行的过程中,因为争夺资源而造成的一种互相等待的现象,若无外力干涉,那么这些进程将无法推进下去

     如果系统资源充足,进程的请求都能得到满足,死锁出现的可能性就会很低,否则就会因为争夺有限的资源而陷入死锁.

  原因:

    1.系统的资源不足

    2.进程推进运行的顺序不合适

    3.资源分配不当

  排查:

    jps -l  查询进程编号

    jstack +进程号查看由哪个类导致了死锁

public class DeadLockDemo {

  
    public static void main(String[] args) {

        String lockA= "lockA",lockB = "lockB";
        new Thread(new ThreadLock(lockA,lockB),"A").start();
        new Thread(new ThreadLock(lockB,lockA),"B").start();
    }
}

class ThreadLock implements Runnable{
    private String lockA;
    private String lockB;

    public ThreadLock(String lockA, String lockB) {
        this.lockA = lockA;
        this.lockB = lockB;
    }

    public void run(){
        synchronized (lockA){
            System.out.println(Thread.currentThread().getName() + "\t 持有锁A,尝试获取 \t"+lockB);
            try {
                TimeUnit.SECONDS.sleep(2);
            } catch (InterruptedException e) {
                e.printStackTrace();
            }
            synchronized (lockB){
                System.out.println(Thread.currentThread().getName() + "\t 持有锁B,尝试获取 \t"+lockA);
            }
        }
    }
}

运行结果:

 

 程序无法继续执行

 解决方法:

 第一步:查进程

 

 

第二步:jstack 进程号

 

 

第三步,修改代码,避免死锁

 

posted @ 2020-01-06 17:48  月上贺兰  阅读(307)  评论(0编辑  收藏  举报