Java 并发编程相关面试题

T1:写两个线程,针对一个List容器,线程1 添加10个元素到容器中,线程2 实现监控元素个数,当到达5个时,线程2给出提示并结束

错误的示范:使用volatile

import java.util.ArrayList;
import java.util.List;

/**
 * 使用volatile.
 * 这是一个错误的解法,volatile只能保证基本类型和对象的引用的可见性,对象内部属性发生变化时,无法保证可见性
 * 所以线程二的退出不能依据 size == 5 来执行,所以此题是一个线程通讯问题
 * 
 * @author yangsx
 * @version 1.0
 * @date 2019/11/15
 */
public class ANS1_Volatile {

    private static volatile List<Object> myList = new ArrayList<>();

    public static void main(String[] args) {
        Thread t1 = new Thread(() -> {
            for (int i = 0; i < 10; i++) {
                myList.add(i);
                System.out.println("线程1添加了一个元素: " + i);
            }
        });
        Thread t2 = new Thread(() -> {
            while (true) {
                if (myList.size() == 5) {
                    System.out.println("线程2准备退出: " + myList.size());
                    break;
                }
            }
        });
        t1.start();
        t2.start();
    }

}

使用wait和notify

import java.util.ArrayList;
import java.util.List;

/**
 * 使用wait和notify
 * 注意:wait会释放锁,notify不会释放锁,线程结束会自定释放锁
 *
 * @author yangsx
 * @version 1.0
 * @date 2019/11/15
 */
public class ANS2_WaitNotify {

    private static List<Object> myList = new ArrayList<>();

    private static final Object lock = new Object();

    public static void main(String[] args) {

        Thread t1 = new Thread(() -> {
            for (int i = 0; i < 10; i++) {
                synchronized (lock) {
                    myList.add(i);
                    System.out.println("线程1添加了一个元素: " + i);
                    if (i == 4) {
                        lock.notify(); // 通知t2元素已经5个
                        lockWait(); // 不释放锁t2无法执行
                    }
                }
            }
        });
        Thread t2 = new Thread(() -> {
            while (true) {
                synchronized (lock) {
                    if (myList.size() < 5) { // 如果元素不足5个,等待并释放锁,让t1继续增加
                        lockWait();
                    } else if (myList.size() == 5) { // 如果已经五个,显示退出消息,并通知t1继续执行,然后退出线程(自动释放锁)
                        System.out.println("线程2准备退出: " + myList.size());
                        lock.notify();
                        break;
                    }
                }
            }
        });
        t1.start();
        t2.start();

    }

    private static void lockWait() {
        try {
            lock.wait();
        } catch (InterruptedException e) {
            e.printStackTrace();
        }
    }

}

使用LockSupport.park()

import java.util.ArrayList;
import java.util.List;
import java.util.concurrent.locks.LockSupport;

/**
 * 使用LockSupport, 最简单的方式
 *
 * @author yangsx
 * @version 1.0
 * @date 2019/11/15
 */
public class ANS3_LockSupportPark {

    private static List<Object> myList = new ArrayList<>();

    private static Thread t1, t2 = null;

    public static void main(String[] args) {

        t1 = new Thread(() -> {
            for (int i = 0; i < 10; i++) {
                myList.add(i);
                System.out.println("线程1添加了一个元素: " + i);
                if (myList.size() == 5) {
                    LockSupport.unpark(t2); // 通知t2已经5个了
                    LockSupport.park(); // t1暂停执行,等待t2打印输出完毕后再次执行
                }
            }
        });
        t2 = new Thread(() -> {
            LockSupport.park(); // 一开始就park住,等待t1 unpark
            System.out.println("线程2准备退出: " + myList.size());
            LockSupport.unpark(t1);
        });
        t2.start(); // 保证t2优先执行
        t1.start();
    }

}

使用CountDownLatch

import java.util.ArrayList;
import java.util.List;
import java.util.concurrent.CountDownLatch;

/**
 * 使用门闩: 保证不了打印的顺序
 *
 * @author yangsx
 * @version 1.0
 * @date 2019/11/15
 */
public class ANS4_CountDownLatch {

    private static List<Object> myList = new ArrayList<>();
    private static volatile int size = myList.size(); // 用来获取准确的list size

    public static void main(String[] args) {
        CountDownLatch cdl = new CountDownLatch(5);

        Thread t1 = new Thread(() -> {
            for (int i = 0; i < 10; i++) {
                myList.add(i);
                System.out.println("线程1添加了一个元素: " + i);
                size = myList.size();
                cdl.countDown();
            }
        });
        Thread t2 = new Thread(() -> {
            try {
                cdl.await();
            } catch (InterruptedException e) {
                e.printStackTrace();
            }
            System.out.println("线程2准备退出: " + size);
        });
        t1.start();
        t2.start();
    }

}

使用CountDownLatch作为通讯标记

package com.yangsx95.notes.mst.t1;

import java.util.ArrayList;
import java.util.List;
import java.util.concurrent.CountDownLatch;

/**
 * 使用 cdl 作为通讯标记
 *
 * @author yangsx
 * @version 1.0
 * @date 2019/11/17
 */
public class ANS5_CountDownLatch2 {

    private static List<Object> myList = new ArrayList<>();
    private static CountDownLatch cdl1 = new CountDownLatch(1);
    private static CountDownLatch cdl2 = new CountDownLatch(1);
    private static volatile int size = myList.size(); // 用来获取准确的list size

    public static void main(String[] args) {

        Thread t1 = new Thread(() -> {
            for (int i = 0; i < 10; i++) {
                myList.add(i);
                size = myList.size();
                System.out.println("线程1添加了一个元素: " + i);
                if (myList.size() == 5) {
                    cdl1.countDown();
                    cdl2Await();
                }
            }
        });
        Thread t2 = new Thread(() -> {
            cdl1Await();
            System.out.println("线程2准备退出: " + size);
            cdl2.countDown();
        });
        t1.start();
        t2.start();

    }

    private static void cdl1Await() {
        try {
            cdl1.await();
        } catch (InterruptedException e) {
            e.printStackTrace();
        }
    }

    private static void cdl2Await() {
        try {
            cdl2.await();
        } catch (InterruptedException e) {
            e.printStackTrace();
        }
    }

}

T2:用两个线程顺序打印 A1B2…Z26, T1打印 A\~Z , T2 打印 1\~26

使用wait和notify

/**
 * 同样是一个线程通讯问题,第一道题是通知一次,而此题是通知多次,并且存在线程先后执行的问题
 * @author yangsx
 * @version 1.0
 * @date 2019/11/17
 */
public class ANS1_WaitNotify {

    private static char[] abc = PrepareData.abc();
    private static int[] num = PrepareData.num();
    private static final Object lock = new Object();

    public static void main(String[] args) {
        Thread t1 = new Thread(() -> {
            for (char a : abc) {
                synchronized (lock) {
                    System.out.print(a);
                    lock.notify();
                    lockWait();
                }
            }
        });
        Thread t2 = new Thread(() -> {
            for (int i : num) {
                synchronized (lock) {
                    System.out.print(i);
                    lock.notify();
                    if (i != 26) { // 如果i不是26则释放锁等待下一次执行,否则直接退出
                        lockWait();
                    }
                }
            }
        });
        t1.start(); // 保证t1线程先执行
        t2.start();

    }

    private static void lockWait() {
        try {
            lock.wait();
        } catch (InterruptedException e) {
            e.printStackTrace();
        }
    }

}

使用LockSupport

import java.util.concurrent.locks.LockSupport;

/**
 * 使用LockSupport实现
 *
 * @author yangsx
 * @version 1.0
 * @date 2019/11/17
 */
public class ANS2_LockSupportPark {

    private static char[] abc = PrepareData.abc();
    private static int[] num = PrepareData.num();
    private static Thread t1, t2 = null;

    public static void main(String[] args) {

        t1 = new Thread(() -> {
            for (char a : abc) {
                System.out.print(a);
                LockSupport.unpark(t2);
                if (a != 'z') {
                    LockSupport.park();
                }
            }
        });
        t2 = new Thread(() -> {
            for (int i : num) {
                System.out.print(i);
                LockSupport.unpark(t1);
                if (i != 26) {
                    LockSupport.park();
                }
            }
        });
        t1.start(); // 保证t1线程先执行
        t2.start();
    }

}

经典的生产者消费者问题:写一个固定容量的同步容器,拥有put、get方法,以及getCount方法,可以支持两个生产者线程以及10个消费者线程的阻塞调用

生产者消费者问题,为了方便测试,我写了个测试类:

import java.util.concurrent.atomic.AtomicInteger;

/**
 * @author yangsx
 * @version 1.0
 * @date 2019/11/17
 */
public class MyContainerTestUtil {

    public static void test(MyContainer myContainer) {

        AtomicInteger producerCounter = new AtomicInteger();

        Runnable prodRunnable = () -> {
            for (int i = 0; i < 50; i++) {
                String ele = "ELE" + producerCounter.incrementAndGet();
                myContainer.put(ele);
                System.out.println(Thread.currentThread().getName() + "放入了一个元素: \"" + ele + "\"");
            }
        };
        Runnable consumerRunnable = () -> {
            for (int i = 0; i < 10; i++) {
                String ele = myContainer.get();
                System.out.println(Thread.currentThread().getName() + "取得了一个元素: \"" + ele + "\"");
            }
        };

        // 两个生产者线程
        for (int i = 0; i < 2; i++) {
            new Thread(prodRunnable, "producer" + i).start();
        }

        // 十个消费者线程
        for (int i = 0; i < 10; i++) {
            new Thread(consumerRunnable, "consumer" + i).start();
        }

    }
}
public interface MyContainer {
    void put(String ele);
    String get();
    int getCount();
}

使用wait和notify

import java.util.LinkedList;

/**
 * 使用wait和notify,
 * 如果容器中没有足够的元素可以消费,消费者线程将会阻塞
 * 如果容器中的元素已经满了,那么生产者线程也会阻塞
 *
 * @author yangsx
 * @version 1.0
 * @date 2019/11/17
 */
public class ANS1_WaitNotify implements MyContainer{

    private LinkedList<String> list = new LinkedList<>();
    private final int MAX_SIZE = 10; // 最多添加十个元素
    private int count = 0;

    public synchronized void put(String ele) {
        while (count >= MAX_SIZE) { // 注意,需要使用while循环,if走完后count不见得小于0
            containerWait();
        }
        list.add(ele);
        count++;
        containerNotifyAll(); // 注意,使用notifyAll,唤醒单个线程的话可能唤醒生产者,没有唤醒消费者
    }

    public synchronized String get() {
        while (count == 0) {
            containerWait();
            containerNotifyAll();
        }
        count--;
        return list.remove();
    }

    public int getCount() {
        return count;
    }

    private void containerWait() {
        try {
            this.wait();
        } catch (InterruptedException e) {
            e.printStackTrace();
        }
    }

    private void containerNotifyAll() {
        this.notifyAll();
    }

    public static void main(String[] args) {
        ANS1_WaitNotify container = new ANS1_WaitNotify();
        MyContainerTestUtil.test(container);
    }
}

使用ReentrantLock和Condition

ReentrantLock 和 Condition.await()、Condition.signal()的关系,类似synchronized和wait、notify之间的关系。

import java.util.LinkedList;
import java.util.concurrent.locks.Condition;
import java.util.concurrent.locks.ReentrantLock;

/**
 * 使用ReentrantLock和Condition对象
 * 使用wait和notify有个缺陷,就是在生产者生产完后,调用notifyAll方法唤醒时,有可能会唤醒其他生产者线程,此时又会wait一次,
 * 这个问题可以使用Condition对象来解决
 *
 * @author yangsx
 * @version 1.0
 * @date 2019/11/17
 */
public class ANS2_ReentrantLockCondition implements MyContainer {

    private LinkedList<String> list = new LinkedList<>();
    private final int MAX_SIZE = 10; // 最多添加十个元素
    private int count = 0;
    private ReentrantLock lock = new ReentrantLock();
    private Condition producer = lock.newCondition();
    private Condition consumer = lock.newCondition();

    @Override
    public void put(String ele) {
        try {
            lock.lock();
            while (count >= MAX_SIZE) {
                producer.await(); // 满了就等待
            }
            count++;
            list.add(ele);
            consumer.signalAll(); // 然后唤醒消费者线程消费
        } catch (InterruptedException e) {
            e.printStackTrace();
        } finally {
            lock.unlock();
        }
    }

    @Override
    public String get() {
        String ele = null;
        try {
            lock.lock();
            while (count <= 0) {
                consumer.await(); // 没有元素了让消费者等着
            }
            count--;
            ele = list.remove();
            producer.signalAll(); // 唤醒生产者线程生产
        } catch (InterruptedException e) {
            e.printStackTrace();
        } finally {
            lock.unlock();
        }
        return ele;
    }

    @Override
    public int getCount() {
        return count;
    }

    public static void main(String[] args) {
        ANS2_ReentrantLockCondition container = new ANS2_ReentrantLockCondition();
        MyContainerTestUtil.test(container);
    }
}
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值