zoukankan      html  css  js  c++  java
  • 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() + "	晚自习,离开教室" );
                    countDownLatch.countDown();//计数器减1
                },String.valueOf(i)).start();
            }
    
            try {
                countDownLatch.await();//计数器等待
            } catch (InterruptedException e) {
                e.printStackTrace();
            }
            System.out.println(Thread.currentThread().getName() + "	 班长最后关门,离开");
        }

    运行结果

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

    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() + "	秦朝统一");
    
        }
    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() + "	" + "invoke sendSMS");
            sendEmail();
        }
    
        public synchronized void sendEmail() throws Exception{
            System.out.println(Thread.currentThread().getName() + "	" + "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() + "	" + "invoke get");
                set();
            }catch (RuntimeException e){
                e.printStackTrace();
            }finally {
                lock.unlock();
            }
        }
    
        public void set(){
            lock.lock();
            try {
                System.out.println(Thread.currentThread().getName() + "	" + "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() + "	写入数据");
                TimeUnit.MICROSECONDS.sleep(500);
                map.put(key,value);
                System.out.println(Thread.currentThread().getName() + "	写入完成");
            } catch (Exception e) {
                e.printStackTrace();
            }finally {
                rwLock.writeLock().unlock();
            }
        }
     
        // 读操作
        public void get(String key){
            rwLock.readLock().lock();
            try {
                System.out.println(Thread.currentThread().getName() + "	读取数据");
                TimeUnit.MICROSECONDS.sleep(500);
                Object value = map.get(key);
                System.out.println(Thread.currentThread().getName() + "	读取完成"+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() + "	get lock");
            // 如果当前的线程不是第一次进来,就会在while这里一直死循环,程序无法进行
            while (!atomicReference.compareAndSet(null,Thread.currentThread())){
    
            }
        }
    
        /*解锁*/
        public static void unLock(){
            /*如果判断是当前的线程,则清空*/
            System.out.println(Thread.currentThread().getName() + "	invoke 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() + "	 持有锁A,尝试获取 	"+lockB);
                try {
                    TimeUnit.SECONDS.sleep(2);
                } catch (InterruptedException e) {
                    e.printStackTrace();
                }
                synchronized (lockB){
                    System.out.println(Thread.currentThread().getName() + "	 持有锁B,尝试获取 	"+lockA);
                }
            }
        }
    }

    运行结果:

     程序无法继续执行

     解决方法:

     第一步:查进程

     

    第二步:jstack 进程号

     

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

  • 相关阅读:
    java web 资源文件读取
    页面跳转
    验证码的随机图片
    spring 注解
    回文字符串系列问题
    【leetcode】Find All Anagrams in a String
    斐波那契数列
    【leetcode】 First Missing Positive
    Trapping Rain Water
    区间合并问题
  • 原文地址:https://www.cnblogs.com/luhuajun/p/12157601.html
Copyright © 2011-2022 走看看