工作_Java_多线程编程面试题

一 交替打印问题

以下实现参考博客:
https://blog.csdn.net/afsvsv/article/details/86521789
https://www.cnblogs.com/ghoster/p/12503082.html

基本思路:
1.输出多次,必须要有while循环或者for循环;
2.交替输出,循环里面必须要有一个能够中止的操作,可以是wait、lock、await;

1.1 ReentrantLock + 共享变量实现

这里由共享变量负责实现交替输出,这里实际上线程并不是真正交替执行,线程1执行unlock后,不一定是线程2抢到锁,有可能是线程1继续执行lock,但是线程1即使再次获取到锁也无法进行输出了,只有某一次线程2抢到锁之后,改变flag的值,线程1才能再执行输出。也就是说:线程不是交替执行的,但是输出语句一定是交替的。这种做法的优势在于res的目标次数可以任意,缺点在于线程可能执行了很多次。

    static int res = 0;
    static boolean flag = true;
    static ReentrantLock lock = new ReentrantLock();

    public static void func1() {
        while (res < 10) {
            lock.lock();
            if(flag){
                System.out.println(Thread.currentThread().getName() + " " + res);
                res++;
                flag = !flag;
            }
            lock.unlock();
        }
    }

    public static void func2() {
        while (res < 10) {
            lock.lock();
            if(!flag){
                System.out.println(Thread.currentThread().getName() + " " + res);
                res++;
                flag = !flag;
            }
            lock.unlock();
        }
    }

    public static void ReLockTest1() {
        Thread thread1 = new Thread(new Runnable() {
            @Override
            public void run() {
                func1();
            }
        });
        Thread thread2 = new Thread(new Runnable() {
            @Override
            public void run() {
                func2();
            }
        });
        thread1.start();
        thread2.start();
    }

1.2 ReentrantLock + Condition实现

相比较上面的方法,这里线程是真正交替执行的,总共执行了res次,缺点在于交替次数必须为偶数次,不然最后线程会停在await处;

重点有两个:
1.线程1先await后signal,线程2先signal后await,这样才能让线程交替进行,而且交替的顺序是固定的,必须线程1在前;
2.方法中必须先输出,后await、signal,不然的话会少一次输出,并且线程挂起;
3.主线程中必须先执行线程1,再执行线程2,不然就会出现两个都wait;因此线程1和2直接必须加个sleep;

    // 使用Condition对象
    static ReentrantLock lock2 = new ReentrantLock();
    static Condition condition1 = lock2.newCondition();
    static Condition condition2 = lock2.newCondition();
    
    public static void ReLockTest2() {
        Thread thread1 = new Thread(new Runnable() {
            @Override
            public void run() {
                func1();
            }
        });
        Thread thread2 = new Thread(new Runnable() {
            @Override
            public void run() {
                func2();
            }
        });
        thread1.start();
        // 主线程必须sleep,不然让thread2先执行的话,程序就被阻塞死了
        try {
            Thread.currentThread().sleep(10);
        } catch (InterruptedException e) {
            e.printStackTrace();
        }
        thread2.start();
    }

这种实现,没有访问共同的数据,每一个都维护一个计数的变量,同步操作只控制交替;缺点在于:总数必须为偶数,如果为奇数,最后thread1会停在await;

    public static void func1(){
        try{
            lock2.lock();
            for(int i=0;i<10;i+=2) {
                System.out.println(Thread.currentThread().getName() + " " + i);
                // 先await后signal
                condition1.await();
                condition2.signal();
            }
        }catch (InterruptedException e){
            e.printStackTrace();
        }finally {
            lock2.unlock();
        }
    }
    public static void func2(){
        try{
            lock2.lock();
            for(int i=1;i<10;i+=2) {
                System.out.println(Thread.currentThread().getName() + " " + i);
                // 先signal后await
                condition1.signal();
                condition2.await();
            }
        }catch (InterruptedException e){
            e.printStackTrace();
        }finally {
            lock2.unlock();
        }
    }

这种实现,访问共同的数据;缺点在于:总数必须为偶数,如果为奇数,最后thread1会停在await;

    static int res = 0;
    public static void func1(){
        try{
            lock2.lock();
            while (res<10){
            	// 操作必须放在await/signal的前面,不然出错
                System.out.println(Thread.currentThread().getName() + " " + res);
                res++;
                condition1.await();
                condition2.signal();
            }
        }catch (InterruptedException e){
            e.printStackTrace();
        }finally {
            lock2.unlock();
        }
    }
    public static void func2(){
        try{
            lock2.lock();
            while (res<10){
                System.out.println(Thread.currentThread().getName() + " " + res);
                res++;
                condition1.signal();
                condition2.await();
            }
        }catch (InterruptedException e){
            e.printStackTrace();
        }finally {
            lock2.unlock();
        }
    }

错误实现:上面输出必须放在await和signal的前面,不然的话就是错的;因为要一次输出,一次await,不然的话,第一次await就浪费了,导致最后缺失一次输出,程序也会被挂起;

错误实现:网上有一些 ReentrantLock + Condition的错误实现;可以这么说吧,只要await、signal顺序不是按照上面哪种方式的,都是错误实现,错误实现也能实现交替输出,但是最终一定会出现线程被await的情况;

以下面这种情况为例:如果res=2,线程1先执行,第一个signal完全没用,啥也没干被挂起;线程2执行,唤醒线程1,啥也没干被挂起;线程1输出0,线程1唤醒线程2,被挂起;线程2输出1,循环退出;最终线程1被挂起了

为啥会这样?首先明确signal和await次数相等时才能没有线程被挂起,这里是两个函数中都把signal放在await前面了,导致先执行的那个线程的signal被浪费了;如果两个函数都把await放在signal前面,那两个线程都会被挂起;所以必须一个singal在前,一个wait在前,而且必须wait在前的线程先执行,这样signal才没有被浪费,而且还必须两个线程运行次数相同,这样await才能和signal配对,不然还是必然会有一个线程被挂起。

    public static void func1() throws InterruptedException {
        lock2.lock();
        while (res < 10) {
            condition2.signal();
            condition1.await();
            System.out.println(Thread.currentThread().getName() + " " + res);
            res++;
        }
        lock2.unlock();
    }

    public static void func2() throws InterruptedException {
        lock2.lock();
        while (res < 10) {
            condition1.signal();
            condition2.await();
            System.out.println(Thread.currentThread().getName() + " " + res);
            res++;
        }
        lock2.unlock();
    }

1.3 Synchronized + await 实现

完全类似于 ReentrantLock + Condition,一样要注意三点;

还有个特别的点:wait方法必须由锁对象调用,而且要是实例锁对象,不能是Class对象,不能是静态变量。所以这里锁对象不能用Test.class

    Integer lock = new Integer(0);
    static volatile int res = 0;
     
    // wait 不是静态方法,不能用类对象调用,只能用实例对象调用;
    public void func1(){
        while (res < 2){
            try {
                synchronized (lock) {
                    System.out.println(Thread.currentThread().getName() + " " + res);
                    res++;
                    test4.wait();
                    test4.notify();
                }
            }catch (InterruptedException e){
                e.printStackTrace();
            }
        }
    }

    public void func2() {
        while (res < 2) {
            try {
                synchronized (lock) {
                    System.out.println(Thread.currentThread().getName() + " " + res);
                    res++;
                    test4.notify();
                    test4.wait();
                }
            }catch (InterruptedException e){
                e.printStackTrace();
            }
        }
    }

    public void ReLockTest2() {
        Thread thread1 = new Thread(new Runnable() {
            @Override
            public void run() {
                func1();
            }
        });
        Thread thread2 = new Thread(new Runnable() {
            @Override
            public void run() {
                func2();
            }
        });
        thread1.start();
        // 主线程必须sleep,不然让thread2先执行的话,程序就被阻塞死了
        try {
            Thread.currentThread().sleep(100);
        } catch (InterruptedException e) {
            e.printStackTrace();
        }
        thread2.start();
    }
偶然发现的一个栈溢出问题
public class Test5 {
    // 这样会报栈溢出错误;因为main函数里面执行new Test5()创建对象时,会调用init方法,
    // init方法会先执行实例成员变量赋值语句,也就是执行Test5 test5 = new Test5(),
    // 这个里面又调用了new Test5()方法,导致又要先调用init方法,死循环了;
    // 加个static可以解决
    Test5 test5 = new Test5();
    public static void main(String[] args) {
        Test5 t = new Test5();
    }
}

1.4 Synchronized + 共享变量 实现

	// 共享变量不需要加volatile
    static int res = 0;
    static boolean flag = true;

    public void func1() {
        while (res < 10) {
            synchronized (test4) {
                if(flag){
                    System.out.println(Thread.currentThread().getName() + " " + res);
                    res++;
                    flag = !flag;
                }
            }
        }
    }

    public void func2() {
        while (res < 10) {
            synchronized (test4) {
                if(!flag){
                    System.out.println(Thread.currentThread().getName() + " " + res);
                    res++;
                    flag = !flag;
                }
            }
        }
    }

1.5 直接用共享变量,不需要同步

ReentrantLock + 共享变量、Synchronized + 共享变量,这两种方式下,锁只是保证了对共享变量的操作是同步的,对于交替真正在起作用的是共享变量,所以可以直接使用共享变量,但是必须保证共享变量的操作是同步的;【这里其实很特别,共享变量加volatile并不能保证原子性,但是这个场景下,不会出现两个线程同时写共享变量】

1.5.1 使用volatile

	// 共享变量必须加volatile
    static volatile int res = 0;
    static volatile boolean flag = true;
    
    public void func1() {
        while (res < 10) {
            if (flag) {
                System.out.println(Thread.currentThread().getName() + " " + res);
                res++;
                flag = !flag;
            }
        }
    }

    public void func2() {
        while (res < 10) {
            if (!flag) {
                System.out.println(Thread.currentThread().getName() + " " + res);
                res++;
                flag = !flag;
            }
        }
    }

1.5.2 使用原子类

共享变量用原子类也能达到上面的效果,这里有个点:只有res需要是原子变量,flag并不需要

    static AtomicInteger res = new AtomicInteger(0);
    boolean flag = true;

    public void func1() {
        while (res.get() < 10) {
            if (flag) {
                System.out.println(Thread.currentThread().getName() + " " + res);
                res.incrementAndGet();
                flag = !flag;
            }
        }
    }

    public void func2() {
        while (res.get() < 10) {
            if (!flag) {
                System.out.println(Thread.currentThread().getName() + " " + res);
                res.incrementAndGet();
                flag = !flag;
            }
        }
    }

二 生产者消费者

以下实现参考博客:
https://www.jianshu.com/p/ab013a4d5878
https://www.cnblogs.com/xindoo/p/11426659.html
https://blog.csdn.net/javazejian/article/details/77410889

生产消费者问题,首先要明确场景:
简单的场景:有个池子容量为A,里面货物初始数量为B,生产者线程每次生产一个货物放入池子,消费者每次从池子中消费一个货物,同一时刻,只能由一个生产者或消费者访问池子;当池子容量已满时,生产者不能再放入货物,当池子为空时,消费者不能消费货物。【复杂的场景:读者写者问题,同时可有多个读者,但只能有一个写者】

2.1 ReentrantLock + Condition 实现

下面这个多设置了一个运行次数,如果不设置操作次数可以将外层while (times < maxTimes)改为while(true)一直循环,里面也不需要if(times >= maxTimes) return;

    // 池子最大容量
    static int maxNum = 1;
    // 货物当前数量
    static int currentNum = 0;
    // 当前运行次数
    static int times = 0;
    // 最大运行次数
    static int maxTimes = 4;

    // 独占锁,同一时刻只有一个生产者或消费者能够进行操作
    static ReentrantLock lock = new ReentrantLock();
    // 生产者等待队列:当碰到池子容量已满时,生产者进入等待队列
    static Condition producerCon = lock.newCondition();
    // 消费者者等待队列:当碰到池子容量已空时,消费者进入等待队列
    static Condition consumerCon = lock.newCondition();

    // 生产
    public static void produce(String name){
        try {
            lock.lock();
            while (times < maxTimes){
                while (currentNum == maxNum){
                    System.out.println("容量已满 停止生产");
                    producerCon.await();
                    if(times >= maxTimes) return;
                }
                currentNum++;
                times++;
                System.out.println("运行次数:" + times + " " + name + " "+"当前容量"+" "+currentNum);
                consumerCon.signal();
            }
        }catch (InterruptedException e){
            e.printStackTrace();
        }finally {
            lock.unlock();
        }
    }

    // 消费
    public static void consume(String name){
        try {
            lock.lock();
            while (times < maxTimes){
                while (currentNum == 0){
                    System.out.println("容量已空 停止消费");
                    consumerCon.await();
                    if(times >= maxTimes) return;
                }
                currentNum--;
                times++;
                System.out.println("运行次数:" + times + " " + name +" "+"当前容量"+" "+currentNum);
                producerCon.signal();
            }
        }catch (InterruptedException e){
            e.printStackTrace();
        }finally {
            lock.unlock();
        }
    }

    public static void test(){
        Thread pro1 = new Thread(new Producer("生产者1"));
        Thread pro2 = new Thread(new Producer("生产者2"));
        Thread con1 = new Thread(new Consumer("消费者1"));
        Thread con2 = new Thread(new Consumer("消费者2"));
        pro1.start();
        pro2.start();
        con1.start();
        con2.start();
    }

    static class Producer implements Runnable{
        public String name = "";

        public Producer(String name){
            this.name = name;
        }

        @Override
        public void run() {
            produce(name);
        }
    }

    static class Consumer implements Runnable{
        public String name = "";

        public Consumer(String name){
            this.name = name;
        }

        @Override
        public void run() {
            consume(name);
        }
    }

不设置运行次数

    // 生产
    public static void produce(String name){
        try {
            lock.lock();
            while (true){
                while (currentNum == maxNum){
                    System.out.println("容量已满 停止生产");
                    producerCon.await();
                }
                currentNum++;
                System.out.println(name + " "+"当前容量"+" "+currentNum);
                consumerCon.signal();
            }
        }catch (InterruptedException e){
            e.printStackTrace();
        }finally {
            lock.unlock();
        }
    }

    // 消费
    public static void consume(String name){
        try {
            lock.lock();
            while (true){
                while (currentNum == 0){
                    System.out.println("容量已空 停止消费");
                    consumerCon.await();
                }
                currentNum--;
                System.out.println(name +" "+"当前容量"+" "+currentNum);
                producerCon.signal();
            }
        }catch (InterruptedException e){
            e.printStackTrace();
        }finally {
            lock.unlock();
        }
    }

2.2 synchronized + wait 实现

    // 生产
    public static void produce(String name){
        try {
            while (true){
                synchronized (lock){
                    while (currentNum == maxNum){
                        System.out.println("容量已满 停止生产");
                        lock.wait();
                    }
                    currentNum++;
                    System.out.println(name + " "+"当前容量"+" "+currentNum);
                    lock.notify();
                }
            }
        }catch (InterruptedException e){
            e.printStackTrace();
        }
    }

    // 消费
    public static void consume(String name){
        try {
            while (true){
                synchronized (lock){
                    while (currentNum == 0){
                        System.out.println("容量已空 停止消费");
                        lock.wait();
                    }
                    currentNum--;
                    System.out.println(name + " "+"当前容量"+" "+currentNum);
                    lock.notify();
                }
            }
        }catch (InterruptedException e){
            e.printStackTrace();
        }
    }

2.3 直接调用阻塞队列

注意:put/take方法才是阻塞的

    static int maxNum = 5;
    static ArrayBlockingQueue<Integer> pool = new ArrayBlockingQueue<>(maxNum);
    
    // 生产
    public static void produce(String name) {
        while (true) {
            boolean succ = false;
            try {
                pool.put(1);
                System.out.println(name + " " + "当前容量" + " " + pool.size());
            } catch (InterruptedException e) {
                e.printStackTrace();
            }
        }
    }

    // 消费
    public static void consume(String name) {
        while (true) {
            try {
                Integer succ = pool.take();
                System.out.println(name + " " + "当前容量" + " " + pool.size());
            } catch (InterruptedException e) {
                e.printStackTrace();
            }
        }
    }

三 AQS共享锁Demo

参考:https://blog.csdn.net/romantic_jie/article/details/100146676

只需要记住阻塞API和唤醒API即可

3.1 CountDownLatch

await阻塞,countDown唤醒;阻塞一个,唤醒达到指定次数才真正唤醒

    static CountDownLatch countDownLatch = new CountDownLatch(2);

    public static void main(String[] args) {
        Thread thread1 = new Thread(new Runnable() {
            @Override
            public void run() {
                func();
            }
        });
        Thread thread2 = new Thread(new Runnable() {
            @Override
            public void run() {
                func();
            }
        });
        thread1.start();
        thread2.start();
        try {
            countDownLatch.await();
        } catch (InterruptedException e) {
            e.printStackTrace();
        }
        System.out.println("主线程输出");
    }

    public static  void func(){
        System.out.println(Thread.currentThread().getName() + ":子线程输出");
        countDownLatch.countDown();
    }

Thread-0:子线程输出
Thread-1:子线程输出
主线程输出

3.2 CyclicBarrier

await阻塞,await唤醒;阻塞多个,阻塞达到指定次数唤醒全部

    static CyclicBarrier cyclicBarrier = new CyclicBarrier(2);

    public static void main(String[] args) {
        Thread thread1 = new Thread(new Runnable() {
            @Override
            public void run() {
                func();
            }
        });
        Thread thread2 = new Thread(new Runnable() {
            @Override
            public void run() {
                func();
            }
        });
        thread1.start();
        thread2.start();
    }

    public static  void func(){
        System.out.println(Thread.currentThread().getName() + ":栅栏前输出");
        try {
            cyclicBarrier.await();
        } catch (Exception e) {
            e.printStackTrace();
        }
        System.out.println(Thread.currentThread().getName() + ":栅栏后输出");
    }

Thread-0:栅栏前输出
Thread-1:栅栏前输出
Thread-1:栅栏后输出
Thread-0:栅栏后输出

3.3 Semaphor

await阻塞,await唤醒;阻塞多个,阻塞达到指定次数唤醒全部

    static Semaphore semaphore = new Semaphore(2);
    public static void main(String[] args) {
        Thread thread1 = new Thread(new Runnable() {
            @Override
            public void run() {
                func();
            }
        });
        Thread thread2 = new Thread(new Runnable() {
            @Override
            public void run() {
                func();
            }
        });
        Thread thread3 = new Thread(new Runnable() {
            @Override
            public void run() {
                func();
            }
        });
        Thread thread4 = new Thread(new Runnable() {
            @Override
            public void run() {
                func();
            }
        });
        thread1.start();
        thread2.start();
        thread3.start();
        thread4.start();
    }

    public static  void func(){
        try {
            semaphore.acquire();
        } catch (Exception e) {
            e.printStackTrace();
        }
        System.out.println(Thread.currentThread().getName() + " semaphore队列长度: " + semaphore.getQueueLength());
        semaphore.release();
    }

Thread-0 semaphore队列长度: 0
Thread-1 semaphore队列长度: 0
Thread-3 semaphore队列长度: 1
Thread-2 semaphore队列长度: 0

3.4 Exchanger

    static Exchanger<String> exchanger = new Exchanger<>();

    public static void main(String[] args) {
        Thread thread1 = new Thread(new Runnable() {
            @Override
            public void run() {
                func();
            }
        });
        Thread thread2 = new Thread(new Runnable() {
            @Override
            public void run() {
                func();
            }
        });
        thread1.start();
        thread2.start();
    }

    public static  void func(){
        String strSend = Thread.currentThread().getName();
        String strReceive= "";
        try {
            strReceive = exchanger.exchange(strSend);
        } catch (Exception e) {
            e.printStackTrace();
        }
        System.out.println(Thread.currentThread().getName() + ":" + strReceive);
    }

Thread-0:Thread-1
Thread-1:Thread-0

四 线程池Demo

  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值