JAVA多线程与高并发-三道道常见的面试题(基于一到六节)

其实掌握这些类的基本用法后,一般面试题都难不倒的,我们当以不变应万变.

目录

    • 题目1:线程t1往容器加元素,线程t2监听容器的size
      • 思路1:线程t1只负责添加元素;线程t2while循环监听,当size==5时终止
        • 方法1:volatile和同步List
      • 思路2:利用线程的阻塞实现.
        • 方法1:synchronized+lock.wait(),lock.notify()
        • 方法2:CountdownLatch
        • 方法3:LockSupport
    • 题目2:固定容量同步容器,支持2个生产者线程以及10个消费者线程
        • 解法1,使用synchronized和线程间的wait,notifyall
        • 解法2:使用ReentrantLock,两个Condition
    • 题目3:两个线程,t1打印字母A到Z,t2打印数字1到26,要求字母数字交替打印(A,1,B,2...,Z,26)
      • synchronized+wait/notify
      • LockSupport + Condition.await/signal
      • ReentrantLock
      • 定制一个自旋锁
      • TransferQueue

题目1:线程t1往容器加元素,线程t2监听容器的size

题目:实现一个容器,提供两个方法,add,size.写两个线程,线程1添加10个元素到容器中.线程2有一个"监听"容器的效果.当线程1添加的元素个数到5个时,线程2给出提示并结束,然后t1线程继续执行.

思路1:线程t1只负责添加元素;线程t2while循环监听,当size==5时终止

方法1:volatile和同步List

  1. 用上volatile关键字,保证变量的可见性
  2. 用上同步容器,防止元素添加到集合中但是size还没有自增的情况

这条路子没走通,因为volatile如果修饰的引用类型对象,不能保证该对象的属性对其他线程的可见性.
volatile对于对象变量的思考在这里点我查看

public class T02_WithVolatileAndSynList {

    // 添加volatile,使t2能够得到通知
    // 使用同步容器,因为list.add是先把元素加入,然后再让size++
    volatile List lists = Collections.synchronizedList(new LinkedList<>());

    public void add(Object o) {
        lists.add(o);
    }

    public int size() {
        return lists.size();
    }

    public static void main(String[] args) {

        T02_WithVolatileAndSynList c = new T02_WithVolatileAndSynList();

        new Thread(() -> {
            while (true) {
                int size = c.size();
                if (size == 5) {
                    System.out.println("t2 end" + " at " + Instant.now());
                    break;
                }
            }
            System.out.println("t2 end");
        }, "t2").start();

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

        new Thread(() -> {
            for (int i = 0; i < 10; i++) {
                c.add(new Object());
                System.out.println("add " + i /*+ " at " + Instant.now()*/);

                /*try {
                    TimeUnit.NANOSECONDS.sleep(1);
                } catch (InterruptedException e) {
                    e.printStackTrace();
                }*/
            }
        }, "t1").start();
    }
}

思路2:利用线程的阻塞实现.

方法1:synchronized+lock.wait(),lock.notify()

  1. 线程t2先阻塞起来;
  2. 线程t1加到5个时,叫醒t2,然后t1阻塞起来;
    如果此时t1不阻塞的话,t1是不会让出锁的
    (notify不会让出锁,wait会)
  3. 等线程t2完事后,叫醒t1.
    t2如果不叫醒t1,那么t1就醒不来了…

示例如下:(这里没有用notifyAll,是因为锁上只有俩线程)

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();

        new Thread(() -> {
            synchronized (lock) {
                System.out.println("t2 start");
                if (c.size() != 5) {
                    try {
                        lock.wait();
                    } catch (InterruptedException e) {
                        e.printStackTrace();
                    }
                }
                int size = c.size();
                System.out.println("t2 end c.size():" + size);
                //通知t1继续执行
                lock.notify();
            }

        }, "t2").start();

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

        new Thread(() -> {
            System.out.println("t1 start");
            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 {
                            lock.wait();
                        } catch (InterruptedException e) {
                            e.printStackTrace();
                        }
                    }
					/*try {
						TimeUnit.SECONDS.sleep(1);
					} catch (InterruptedException e) {
						e.printStackTrace();
					}*/
                }
            }
        }, "t1").start();
    }
}

方法2:CountdownLatch

同方法1的思路一样,只不过使用CountdownLatch替代;

如果不需要考虑同步,即线程t1添加第5个元素后,可以让t1添加其他元素的同时t2去做一些操作,那就只需一个CountdownLatch;
如果需要考虑同步,即线程t1添加第5个元素后,需要等t2执行一定任务后,t1再继续添加,那就需要两个CountdownLatch

如果只是考虑线程间通信,而不需要同步,那么用synchronized + wait/notify就显得太重了,这时应该考虑countdownlatch/cyclicbarrier/semaphore

代码示例(需要考虑同步的情况):

public class T05_CountDownLatch {

    // 添加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) {
        T05_CountDownLatch c = new T05_CountDownLatch();

        CountDownLatch latchT1 = new CountDownLatch(1);
        CountDownLatch latchT2 = new CountDownLatch(1);

        new Thread(() -> {
            System.out.println("t2start");
            if (c.size() != 5) {
                try {
                    latchT2.await();
                    //也可以指定等待时间
                    //latchT2.await(5000, TimeUnit.MILLISECONDS);
                } catch (InterruptedException e) {
                    e.printStackTrace();
                }
            }
            System.out.println("t2 end");
            latchT1.countDown();
        }, "t2").start();

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

        new Thread(() -> {
            System.out.println("t1 start");
            for (int i = 0; i < 10; i++) {
                c.add(new Object());
                System.out.println("add " + i);

                if (c.size() == 5) {
                    // 打开门闩,让t2得以执行
                    latchT2.countDown();
                    try {
                        // 这个是保证线程t2处理完后,再继续执行;
                        // 如果不需要等t2处理完后再继续,那就可以省去latchT1的操作
                        latchT1.await();
                    } catch (InterruptedException e) {
                        e.printStackTrace();
                    }
                }
            }
        }, "t1").start();
    }
}

方法3:LockSupport

这个和CountdownLatch类似,LockSupport用起来更简单一些.
但是,一般情况下我们在工程中不会显式创建线程,这时候LockSupport可能就不太好使了

public class T07_LockSupport_WithoutSleep {

    // 添加volatile,使t2能够得到通知
    /*volatile*/ List lists = new ArrayList();

    public void add(Object o) {
        lists.add(o);
    }

    public int size() {
        return lists.size();
    }

    static Thread t1 = null, t2 = null;

    public static void main(String[] args) {
        T07_LockSupport_WithoutSleep c = new T07_LockSupport_WithoutSleep();
        
        t1 = new Thread(() -> {
            System.out.println("t1 start");
            for (int i = 0; i < 10; i++) {
                c.add(new Object());
                System.out.println("add " + i);

                if (c.size() == 5) {
                    LockSupport.unpark(t2);
                    LockSupport.park();
                }
            }
        }, "t1");

        t2 = new Thread(() -> {
            LockSupport.park();

            System.out.println("t2 end");
            LockSupport.unpark(t1);
        }, "t2");

        t2.start();
        t1.start();
    }
}

题目2:固定容量同步容器,支持2个生产者线程以及10个消费者线程

写一个固定容量同步容器,拥有put和get方法,以及getCount方法,能够支持2个生产者线程以及10个消费者线程的阻塞调用

思路就是内部设置个LinkedList属性,然后自己维护get和put方法.
每次get前,先while循环判断,如果size=0就wait;size!=0就拿出一个值,并唤醒生产者们;
每次put前,先while循环判断,如果size=max就wait;否则就往list中添加值,然后唤醒消费者们.

解法1,使用synchronized和线程间的wait,notifyall

public class WzContainer<T> {

    private int max = 10;
    private int size = 0;
    private LinkedList<T> values = new LinkedList<>();

    public synchronized T get() {
        while (size == 0) {
            try {
                this.wait();
            } catch (InterruptedException e) {
                e.printStackTrace();
            }
        }
        T t = values.removeFirst();
        size--;
        this.notifyAll();// 通知生产者
        return t;
    }

    public synchronized void put(T t) {
        while (size == this.max) {
            try {
                this.wait();
            } catch (InterruptedException e) {
                e.printStackTrace();
            }
        }
        values.addLast(t);
        size++;
        this.notifyAll();// 通知消费者
    }

    public static void main(String[] args) {
        WzContainer<String> c = new WzContainer<>();
        //启动消费者线程
        for (int i = 0; i < 10; i++) {
            new Thread(() -> {
                for (int j = 0; j < 4; j++) {
                    c.get();
                    System.out.println(c.get());
                }
            }, "consumer-" + 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 < 20; j++) {
                    c.put(Thread.currentThread().getName() + " at " + Instant.now());
                }
            }, "producer-" + i).start();
        }
    }
}

解法2:使用ReentrantLock,两个Condition

public class WzContainerReentrantLock<T> {

    private int max = 10;
    private int size = 0;
    private LinkedList<T> values = new LinkedList<>();
    private Lock lock = new ReentrantLock();
    private Condition producerCondition = lock.newCondition();
    private Condition consumerCondition = lock.newCondition();

    public T get() {
        T t = null;
        try {
            lock.lock();
            while (size == 0) {
                consumerCondition.await();
            }
            t = values.removeFirst();
            size--;
            producerCondition.signalAll();// 通知生产者
        } catch (InterruptedException e) {
            e.printStackTrace();
        } finally {
            lock.unlock();
        }
        return t;
    }

    public void put(T t) {
        try {
            lock.lock();
            while (size == this.max) {
                producerCondition.await();
            }
            values.addLast(t);
            size++;
            consumerCondition.signalAll();// 通知消费者
        } catch (InterruptedException e) {
            e.printStackTrace();
        } finally {
            lock.unlock();
        }
    }

    public static void main(String[] args) {
        WzContainerReentrantLock<String> container = new WzContainerReentrantLock<>();
        //启动消费者线程
        for (int i = 0; i < 10; i++) {
            new Thread(() -> {
                for (int j = 0; j < 4; j++) {
                    String s = container.get();
                    System.out.println(s);
                }
            }, "consumer-" + 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 < 20; j++) {
                    container.put(Thread.currentThread().getName() + " at " + Instant.now());
                }
            }, "producer-" + i).start();
        }
    }
}

题目3:两个线程,t1打印字母A到Z,t2打印数字1到26,要求字母数字交替打印(A,1,B,2…,Z,26)

有好几种实现方法,但是中心思想都是一样的,通过线程的阻塞来实现.

首先公用部分:

	static List<String> letters = new ArrayList<>();
    static List<String> numbers = new ArrayList<>();
    static int count = 0;

    static {
        for (char i = 'A'; i <= 'Z'; i++) {
            letters.add(String.valueOf(i));
            count = count + 1;
            numbers.add(count + "");
        }
    }

    static Thread t1;
    static Thread t2;

synchronized+wait/notify

public static void useSynchronized() {
		Object lock = new Object();
        t1 = new Thread(() -> {
            synchronized (lock) {
                letters.forEach(s -> {
                    System.out.println(s);
                    lock.notify();
                    try {
                        lock.wait();
                    } catch (InterruptedException e) {
                        e.printStackTrace();
                    }
                });
                lock.notify();
            }
        }, "t1");

        t2 = new Thread(() -> {
            synchronized (lock) {
                numbers.forEach(s -> {
                    System.out.println(s);
                    lock.notify();
                    try {
                        lock.wait();
                    } catch (InterruptedException e) {
                        e.printStackTrace();
                    }
                });
                lock.notify();
            }
        }, "t2");
        t1.start();
        t2.start();
    }

LockSupport + Condition.await/signal

public static void useLockSupport() {
        t1 = new Thread(() -> {
            letters.forEach(s -> {
                System.out.println(s);
                LockSupport.unpark(t2);
                LockSupport.park();
            });
            System.out.println("t1end");
        }, "t1");

        t2 = new Thread(() -> {
            numbers.forEach(s -> {
                LockSupport.park();
                System.out.println(s);
                LockSupport.unpark(t1);
            });
            LockSupport.unpark(t1);
            System.out.println("t2end");
        }, "t2");

        // 注意t2先开始,因为我让t2先park了
        t2.start();
        t1.start();
    }

ReentrantLock

public static void useLockSupport() {
        t1 = new Thread(() -> {
            letters.forEach(s -> {
                System.out.println(s);
                LockSupport.unpark(t2);
                LockSupport.park();
            });
            System.out.println("t1end");
        }, "t1");

        t2 = new Thread(() -> {
            numbers.forEach(s -> {
                LockSupport.park();
                System.out.println(s);
                LockSupport.unpark(t1);
            });
            LockSupport.unpark(t1);
            System.out.println("t2end");
        }, "t2");

        // 注意t2先开始,因为我让t2先park了
        t2.start();
        t1.start();
    }

    public static void useReentrantLock() {
        Lock lock = new ReentrantLock();
        Condition letterCondition = lock.newCondition();
        Condition numberCondition = lock.newCondition();
        t1 = new Thread(() -> {
            lock.lock();
            try {
                letters.forEach(s -> {
                    System.out.println(s);
                    numberCondition.signal();
                    try {
                        letterCondition.await();
                    } catch (InterruptedException e) {
                        e.printStackTrace();
                    }
                });
                numberCondition.signal();
            } finally {
                lock.unlock();
            }
        }, "t1");

        t2 = new Thread(() -> {
            lock.lock();
            try {
                letters.forEach(s -> {
                    System.out.println(s);
                    letterCondition.signal();
                    try {
                        numberCondition.await();
                    } catch (InterruptedException e) {
                        e.printStackTrace();
                    }
                });
                letterCondition.signal();
            } finally {
                lock.unlock();
            }
        }, "t2");
        t1.start();
        t2.start();
    }

定制一个自旋锁

	enum ThreadRun {
        T1, T2
    }

    static volatile ThreadRun threadRun = ThreadRun.T1;

    public static void useCustomCas() {
        t1 = new Thread(() -> {
            letters.forEach(s -> {
                while (threadRun != ThreadRun.T1) {
                }
                System.out.println(s);
                threadRun = ThreadRun.T2;
            });
        }, "t1");

        t2 = new Thread(() -> {
            numbers.forEach(s -> {
                while (threadRun != ThreadRun.T2) {
                }
                System.out.println(s);
                threadRun = ThreadRun.T1;
            });
        }, "t2");
        t1.start();
        t2.start();
    }

TransferQueue

	static TransferQueue<String> transferQueue = new LinkedTransferQueue<>();
    public static void useSemaphore() {
        t1 = new Thread(() -> {
            numbers.forEach(s -> {
                try {
                    System.out.println(transferQueue.take());
                    transferQueue.transfer(s);
                } catch (InterruptedException e) {
                    e.printStackTrace();
                }
            });
        }, "t1");
        t2 = new Thread(() -> {
            letters.forEach(s -> {
                try {
                    transferQueue.transfer(s);
                    System.out.println(transferQueue.take());
                } catch (InterruptedException e) {
                    e.printStackTrace();
                }
            });
        }, "t2");
        t1.start();
        t2.start();
    }

你可能感兴趣的:(JAVA多线程与高并发)