• 两个线程小程序面试题


    实现生产者和消费者

    wait()和notify()实现生产者和消费者

    /**
     * 面试题:写一个固定容量同步容器,拥有put和get方法,以及getCount方法,
     * 能够支持2个生产者线程以及10个消费者线程的阻塞调用
     * 
     * 使用wait和notify/notifyAll来实现
     * 
     * @author mashibing
     */public class MyContainer1<T> {
        final private LinkedList<T> lists = new LinkedList<>();
        final private int MAX = 10; //最多10个元素
        private int count = 0; 
        
        public synchronized void put(T t) {
            while(lists.size() == MAX) { //想想为什么用while而不是用if? while会在进行一次判断
                try {
                    this.wait(); //effective java
                } catch (InterruptedException e) {
                    e.printStackTrace();
                }
            }
            
            lists.add(t);
            ++count;
            this.notifyAll(); //通知消费者线程进行消费 如果用notify()的话 叫醒的可能还是一个生产者
        }
        
        public synchronized T get() {
            T t = null;
            while(lists.size() == 0) {
                try {
                    this.wait();
                } catch (InterruptedException e) {
                    e.printStackTrace();
                }
            }
            t = lists.removeFirst();
            count --;
            this.notifyAll(); //通知生产者进行生产
            return t;
        }
        
        public static void main(String[] args) {
            MyContainer1<String> c = new MyContainer1<>();
            //启动消费者线程
            for(int i=0; i<10; i++) {
                new Thread(()->{
                    for(int j=0; j<5; j++) System.out.println(c.get());
                }, "c" + i).start();
            }
            
            try {
                TimeUnit.SECONDS.sleep(2);
            } catch (InterruptedException e) {
                e.printStackTrace();
            }
            
            //启动生产者线程
            for(int i=0; i<2; i++) {
                new Thread(()->{
                    for(int j=0; j<25; j++) c.put(Thread.currentThread().getName() + " " + j);
                }, "p" + i).start();
            }
        }
    }

    Lock和Condition实现生产者与消费者

    /**
     * 面试题:写一个固定容量同步容器,拥有put和get方法,以及getCount方法,
     * 能够支持2个生产者线程以及10个消费者线程的阻塞调用
     * 
     * 使用wait和notify/notifyAll来实现
     * 
     * 使用Lock和Condition来实现
     * 对比两种方式,Condition的方式可以更加精确的指定哪些线程被唤醒
     * 
     * @author mashibing
     */public class MyContainer2<T> {
        final private LinkedList<T> lists = new LinkedList<>();
        final private int MAX = 10; //最多10个元素
        private int count = 0;
        
        private Lock lock = new ReentrantLock();
        private Condition producer = lock.newCondition();
        private Condition consumer = lock.newCondition();
        
        public void put(T t) {
            try {
                lock.lock();
                while(lists.size() == MAX) { //想想为什么用while而不是用if?
                    producer.await();
                }
                
                lists.add(t);
                ++count;
                consumer.signalAll(); //通知消费者线程进行消费
            } catch (InterruptedException e) {
                e.printStackTrace();
            } finally {
                lock.unlock();
            }
        }
        
        public T get() {
            T t = null;
            try {
                lock.lock();
                while(lists.size() == 0) {
                    consumer.await();
                }
                t = lists.removeFirst();
                count --;
                producer.signalAll(); //通知生产者进行生产
            } catch (InterruptedException e) {
                e.printStackTrace();
            } finally {
                lock.unlock();
            }
            return t;
        }
        
        public static void main(String[] args) {
            MyContainer2<String> c = new MyContainer2<>();
            //启动消费者线程
            for(int i=0; i<10; i++) {
                new Thread(()->{
                    for(int j=0; j<5; j++) System.out.println(c.get());
                }, "c" + i).start();
            }
            
            try {
                TimeUnit.SECONDS.sleep(2);
            } catch (InterruptedException e) {
                e.printStackTrace();
            }
            
            //启动生产者线程
            for(int i=0; i<2; i++) {
                new Thread(()->{
                    for(int j=0; j<25; j++) c.put(Thread.currentThread().getName() + " " + j);
                }, "p" + i).start();
            }
        }
    }
    View Code

    对比两种方式,Condition的方式可以更加精确的指定哪些线程被唤醒

    写两个线程,线程1添加10个元素到容器中,线程2监控元素的个数,当个数到5个时,线程2给出提示并结束

    使用wait()和notify()实现

    /**
     * 曾经的面试题:(淘宝?)
     * 实现一个容器,提供两个方法,add,size
     * 写两个线程,线程1添加10个元素到容器中,线程2实现监控元素的个数,当个数到5个时,线程2给出提示并结束
     * 
     * 给lists添加volatile之后,t2能够接到通知,但是,t2线程的死循环很浪费cpu,如果不用死循环,该怎么做呢?
     * 
     * 这里使用wait和notify做到,wait会释放锁,而notify不会释放锁
     * 需要注意的是,运用这种方法,必须要保证t2先执行,也就是首先让t2监听才可以
     * 
     * 阅读下面的程序,并分析输出结果
     * 可以读到输出结果并不是size=5时t2退出,而是t1结束时t2才接收到通知而退出
     * 想想这是为什么?
     * 
     * notify之后,t1必须释放锁,t2退出后,也必须notify,通知t1继续执行
     * 整个通信过程比较繁琐
     * @author mashibing
     */public class T04_NotifyFreeLock {
    
        //添加volatile,使t2能够得到通知
        volatile List lists = new ArrayList();
    
        public void add(Object o) {
            lists.add(o);
        }
    
        public int size() {
            return lists.size();
        }
        
        public static void main(String[] args) {
            T04_NotifyFreeLock c = new T04_NotifyFreeLock();
            
            final Object lock = new Object();
    
            Thread t2 = new Thread(() -> {
                synchronized(lock) {
                    System.out.println("t2启动");
                    if(c.size() != 5) {
                        try {
                            lock.wait();
                        } catch (InterruptedException e) {
                            e.printStackTrace();
                        }
                    }
                    System.out.println("t2 结束");
                    //通知t1继续执行
                    lock.notify();
                }
                
            }, "t2");
            t2.start();
            try {
                TimeUnit.SECONDS.sleep(1);
            } catch (InterruptedException e1) {
                e1.printStackTrace();
            }
    
            new Thread(() -> {
                System.out.println("t1启动");
                synchronized(lock) {
                    for(int i=0; i<10; i++) {
                        c.add(new Object());
                        System.out.println("add " + i);
                        
                        if(c.size() == 5) {
                            lock.notify();
    
                            //释放锁,让t2得以执行
                            try {
                                //t2.join();
                                lock.wait();
                            } catch (InterruptedException e) {
                                e.printStackTrace();
                            }
                        }
                    }
                }
            }, "t1").start();
            
            
        }
    }

    使用LockSupport实现

    1.和wait() 相比,park()不需要加锁,

    2.和notify()相比unPark(t)指定的线程

    3.如果先unPark(t),后面的park()不会生效

    /**
     * 曾经的面试题:(淘宝?)
     * 实现一个容器,提供两个方法,add,size
     * 写两个线程,线程1添加10个元素到容器中,线程2实现监控元素的个数,当个数到5个时,线程2给出提示并结束
     * 
     * 给lists添加volatile之后,t2能够接到通知,但是,t2线程的死循环很浪费cpu,如果不用死循环,该怎么做呢?
     * 
     * 这里使用wait和notify做到,wait会释放锁,而notify不会释放锁
     * 需要注意的是,运用这种方法,必须要保证t2先执行,也就是首先让t2监听才可以
     * 
     * 阅读下面的程序,并分析输出结果
     * 可以读到输出结果并不是size=5时t2退出,而是t1结束时t2才接收到通知而退出
     * 想想这是为什么?
     * 
     * notify之后,t1必须释放锁,t2退出后,也必须notify,通知t1继续执行
     * 整个通信过程比较繁琐
     * 
     * 使用Latch(门闩)替代wait notify来进行通知
     * 好处是通信方式简单,同时也可以指定等待时间
     * 使用await和countdown方法替代wait和notify
     * CountDownLatch不涉及锁定,当count的值为零时当前线程继续运行
     * 当不涉及同步,只是涉及线程通信的时候,用synchronized + wait/notify就显得太重了
     * 这时应该考虑countdownlatch/cyclicbarrier/semaphore
     * @author mashibing
     *///TODO park unpark
    
    public class T06_LockSupport {
    
        // 添加volatile,使t2能够得到通知
        volatile List lists = new ArrayList();
    
        public void add(Object o) {
            lists.add(o);
        }
    
        public int size() {
            return lists.size();
        }
    
        public static void main(String[] args) {
            T06_LockSupport c = new T06_LockSupport();
    
            CountDownLatch latch = new CountDownLatch(1);
    
            Thread t2 = new Thread(() -> {
                System.out.println("t2启动");
                if (c.size() != 5) {
                    LockSupport.park();
                }
                System.out.println("t2 结束");
    
    
            }, "t2");
    
            t2.start();
    
            new Thread(() -> {
                System.out.println("t1启动");
                for (int i = 0; i < 10; i++) {
                    c.add(new Object());
                    System.out.println("add " + i);
                    if (c.size() == 5) {
                        LockSupport.unpark(t2);
                        try {
                            t2.join();
                        } catch (InterruptedException e) {
                            e.printStackTrace();
                        }
                    }
                }
            }, "t1").start();
    
        }
    }
    View Code

    ReadWriteLock的用法

    ReadWriteLock分出了两把锁 readLock 和writeLock

        static ReadWriteLock readWriteLock = new ReentrantReadWriteLock();
        static Lock readLock = readWriteLock.readLock();
        static Lock writeLock = readWriteLock.writeLock();
    package com.mashibing.juc.c_020;
    
    import java.util.Random;
    import java.util.concurrent.atomic.LongAdder;
    import java.util.concurrent.locks.Lock;
    import java.util.concurrent.locks.ReadWriteLock;
    import java.util.concurrent.locks.ReentrantLock;
    import java.util.concurrent.locks.ReentrantReadWriteLock;
    
    public class T10_TestReadWriteLock {
        static Lock lock = new ReentrantLock();
        private static int value;
    
        static ReadWriteLock readWriteLock = new ReentrantReadWriteLock();
        static Lock readLock = readWriteLock.readLock();
        static Lock writeLock = readWriteLock.writeLock();
    
        public static void read(Lock lock) {
            try {
                lock.lock();
                Thread.sleep(1000);
                System.out.println("read over!");
                //模拟读取操作
            } catch (InterruptedException e) {
                e.printStackTrace();
            } finally {
                lock.unlock();
            }
        }
    
        public static void write(Lock lock, int v) {
            try {
                lock.lock();
                Thread.sleep(1000);
                value = v;
                System.out.println("write over!");
                //模拟写操作
            } catch (InterruptedException e) {
                e.printStackTrace();
            } finally {
                lock.unlock();
            }
        }
    
    
    
    
    
        public static void main(String[] args) {
            //Runnable readR = ()-> read(lock);
            Runnable readR = ()-> read(readLock);
    
            //Runnable writeR = ()->write(lock, new Random().nextInt());
            Runnable writeR = ()->write(writeLock, new Random().nextInt());
    
            for(int i=0; i<18; i++) new Thread(readR).start();
            for(int i=0; i<2; i++) new Thread(writeR).start();
    
    
        }
    }
    View Code

    如果读的时候不加锁可能产生脏读(类似事物的脏读)

    对一个对象,如果读写都加Synchronized,那么读写都是互斥的 18个线程读的时候 会串行18次,两个线程写的时候 串行两次,需要18+2=20

    在读线程里面加读锁 一个线程读的时候 其他线程可以一起读(写锁是排它的,读锁不上排它的),18个线程读的时候 可以一并读

    JUC其他的线程类

    CountDownLatch

    1、类介绍
    一个同步辅助类,在完成一组正在其他线程中执行的操作之前,它允许一个或多个线程一直等待。
    用给定的计数 初始化 CountDownLatch。由于调用了 countDown() 方法,所以在当前计数到达零之前,await 方法会一直受阻塞。
    之后,会释放所有等待的线程,await 的所有后续调用都将立即返回。
    这种现象只出现一次——计数无法被重置。 一个线程(或者多个), 等待另外N个线程完成某个事情之后才能执行

    2、使用场景
    在一些应用场合中,需要等待某个条件达到要求后才能做后面的事情;
    同时当线程都完成后也会触发事件,以便进行后面的操作。
    这个时候就可以使用CountDownLatch。
    CountDownLatch最重要的方法是countDown()和await(),前者主要是倒数一次,后者是等待倒数到0,如果没有到达0,就只有阻塞等待了(加个门栓)。

    package com.mashibing.juc.c_020;
    
    import java.util.concurrent.CountDownLatch;
    
    public class T06_TestCountDownLatch   {
        private static final int MAX_PRINT_NUM = 100;
        private static volatile int count = 0;
    
        public static void printAB() {
            // 声明CountDownLatch
            CountDownLatch countDownLatch = new CountDownLatch(2);
    
            new Thread(() -> {
                while (count < MAX_PRINT_NUM) {
                    if (count % 2 == 0) {
                        System.out.println("num偶数:" + count);
                        count++;
                    }
                }
                // 偶数线程执行完则计数器减一
                countDownLatch.countDown();
            }).start();
    
            new Thread(() -> {
                while (count < MAX_PRINT_NUM) {
                    if (count % 2 == 1) {
                        System.out.println("num奇数:" + count);
                        count++;
                    }
                }
                // 奇数线程执行完则计数器减一
                countDownLatch.countDown();
            }).start();
    
            try {
               // countDownLatch.await();
            } catch (Exception e) {
            }
        }
    
        public static void main(String[] args) {
            printAB();
        }
    }
    View Code

    CyclicBarrier

    栅栏的意思是 大家伙都在这等着,什么时候等人数够了在执行(类似大家拼车 满人的时候发车)。和上面的CountDownLatch完全不同,一个线程可以CountDown多次

    package com.mashibing.juc.c_020;
    
    import java.util.concurrent.BrokenBarrierException;
    import java.util.concurrent.CyclicBarrier;
    
    public class T07_TestCyclicBarrier {
        public static void main(String[] args) {
            //CyclicBarrier barrier = new CyclicBarrier(20);
    
            CyclicBarrier barrier = new CyclicBarrier(20, () -> System.out.println("满人"));
    
            /*CyclicBarrier barrier = new CyclicBarrier(20, new Runnable() {
                @Override
                public void run() {
                    System.out.println("满人,发车");
                }
            });*/
    
            for(int i=0; i<100; i++) {
    
                    new Thread(()->{
                        try {
                            barrier.await();
    
                        } catch (InterruptedException e) {
                            e.printStackTrace();
                        } catch (BrokenBarrierException e) {
                            e.printStackTrace();
                        }
                    }).start();
                
            }
        }
    }
    View Code

    Semaphore

    信号灯,灯亮的时候可以执行:

    限流:最多允许多少个线程同时允许

    比如卖票,我们最多开五个窗口 semaphore就写5

    package com.mashibing.juc.c_020;
    
    import java.util.concurrent.Semaphore;
    
    public class T11_TestSemaphore {
        public static void main(String[] args) {
            //Semaphore s = new Semaphore(2);
            Semaphore s = new Semaphore(2, true);
            //允许一个线程同时执行
            //Semaphore s = new Semaphore(1);
    
            new Thread(()->{
                try {
                    s.acquire();
    
                    System.out.println("T1 running...");
                    Thread.sleep(200);
                    System.out.println("T1 running...");
    
                } catch (InterruptedException e) {
                    e.printStackTrace();
                } finally {
                    s.release();
                }
            }).start();
    
            new Thread(()->{
                try {
                    s.acquire();
    
                    System.out.println("T2 running...");
                    Thread.sleep(200);
                    System.out.println("T2 running...");
    
                    s.release();
                } catch (InterruptedException e) {
                    e.printStackTrace();
                }
            }).start();
        }
    }
    View Code
  • 相关阅读:
    Zookeeper 系列(五)Curator API
    Zookeeper 系列(四)ZKClient API
    Zookeeper 系列(三)Zookeeper API
    Zookeeper 系列(二)安装配制
    [bzoj 2393] Cirno的完美算数教室 (容斥原理+dfs剪枝)
    [Sdoi2013] [bzoj 3198] spring (hash+容斥原理)
    [bzoj 1471] 不相交路径 (容斥原理)
    [bzoj 3701] Olympic Games (莫比乌斯反演)
    [bzoj 2693] jzptab & [bzoj 2154] Crash的数字表格 (莫比乌斯反演)
    [51Nod 1244]
  • 原文地址:https://www.cnblogs.com/ssskkk/p/12659224.html
Copyright © 2020-2023  润新知