并发编程面试

  • 经典面试题
    ー个容器,提供两个方法,add,size写两个线程,线程1添加10个元素到容器中,线程2实现监控元素的个数,当个数到5个时,线程2给出提示并结束,如何实现?

方法一:使用volatile

使用 volatile关键字,volatile保证了线程间的可见性,但是其并不保证原子性。

在这简单介绍一下volatile

  • 对于volatile修饰的变量,jvm虚拟机只是保证从主内存加载到线程工作内存的值是最新的。可以理解为当线程1改变了缓存中值,则会提醒线程2在read时,先去主存中重新load一下,确保每次加载到的都是最新的值。
  • 在.class文件中,volatile关键字所修饰的变量,写操作时都会加上lock信号量。这样做的目的是:
    (1)保证在该信号生效期间会独占共享内存,并将处理器缓存行的数据写回到内存中。
    (2)当缓存行的数据写回到内存的操作会使得其他cpu缓存了该地址的数据变为无效,必须重新去主存中重洗获取。

具体分析

看了上述的原理分析,我们用volatile修饰存储元素的容器(数组、集合)。从而保证了当一个线程改变了容器的值,
都会提醒其他线程去重新加载这个容器,进而达到了信息同步的效果。

代码实现

public class Test01 {
//    volatile修饰List集合
    volatile List<Object> lists = new ArrayList<>();
    void adds(Object object) {
        lists.add(object);
    }

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

    }

    public static void main(String[] args) {
        Test01 t = new Test01();
        new Thread(() -> {
            for (int i = 1; i <= 10; i++) {
                t.adds(new Object());
                System.out.println(t.size());
                try {
                    Thread.sleep(10);
                } catch (InterruptedException e) {
                    e.printStackTrace();
                }
            }
        }).start();

        new Thread(() -> {
            while (true) {
                if (t.size() == 5) {
                    System.out.println("daole");
                    break;
                }
            }
        }).start();

    }
}

运行结果截图:
在这里插入图片描述

方法二:使用wait 、notify

简单介绍

想必大家对wait 、notify、notifyAll都很熟悉了,他们都是object类下的方法,使用他们时必须把他们放在synchronized代码块中。
并且需要注意的是wait()会释放当前所占的锁,notify()和 notifyAll()不会释放锁 

具体分析

  • 在这里先启动监听的线程,再启动添加元素的线程。先让监听线程进入wait状态,释放所占有的锁,然后添加元素线程启动获取锁,当添加元素的个数到达5时,唤醒监听线程,并进入wait状态(释放锁),等待监听线程唤醒。
  • 在唤醒监听线程的时候,当监听线程打印提示信息后,需要重新进入wait状态,原因是:因为synchronized锁的是对象,此时监听线程获取了锁,当执行打印完提示信息后,需要释放锁资源,以便添加元素的线程重新获得锁,并且继续添加剩余的元素。

代码实现

public class Test03 {
    //    volatile List<Object> lists = new ArrayList<>();
    int count = 0;

    void adds() {
        count++;
    }

    int size() {
        return count;

    }

    public static void main(String[] args) {
        Test03 t = new Test03();

        new Thread(() -> {
            synchronized (t) {
                try {
//                    监听线程启动,并且此时进入了wait状态,并且释放了锁资源
                    t.wait();
                } catch (InterruptedException e) {
                    e.printStackTrace();
                }
                System.out.println("daole");
//                打印完提示信息,并唤醒添加元素的线程
                t.notify();
                try {
//                    释放锁,因为监听线程的目的已经实现,为了节约资源让其等待1s后关闭
                    t.wait(1000);
                } catch (InterruptedException e) {
                    e.printStackTrace();
                }
            }

        }).start();


        new Thread(() -> {
            synchronized (t) {
                for (int i = 1; i <= 10; i++) {
                    t.adds();
//                    判断集合中的元素个数,当到达5时 唤醒监听线程并且进入wait状态并且释放锁。
                    if (t.size() == 5) {
                        t.notify();
                        try {
                            t.wait();
                        } catch (InterruptedException e) {
                            e.printStackTrace();
                        }
                    }
                    System.out.println(t.size());
                    try {
                        Thread.sleep(10);
                    } catch (InterruptedException e) {
                        e.printStackTrace();
                    }
                }
            }
        }).start();


    }
}

运行结果截图
在这里插入图片描述

方法三:使用门闩 CountDownLatch 替代 wait notify(推荐)

简单介绍

CountDownLatch 不涉及锁定,利用它可以实现类似计数器的功能,每次调用countDown()方法计数器减 1,
当count的值为0时当前线程继续执行

具体分析

  • 先创建一个CountDownLatch类对象,并且使count值等于1.
  • 先启动监听线程,让其进入await状态(await和wait的功能一致,只是属于不同的package下)
  • 再启动添加元素线程,当长度到达5时,调用countDown()方法,继续执行监听线程的run()方法,打印提示信息。

代码实现

public class Test04 {
    //    volatile List<Object> lists = new ArrayList<>();
     int count = 0;

     void adds() {
        count++;
    }

     int size() {
        return count;

    }

    public static void main(String[] args) {
        Test04 t = new Test04();
        CountDownLatch countDownLatch = new CountDownLatch(1);
        new Thread(() -> {
            try {
                countDownLatch.await();
            } catch (InterruptedException e) {
                e.printStackTrace();
            }
            System.out.println("daole");
        }).start();


        new Thread(() -> {
                for (int i = 1; i <= 10; i++) {
                    t.adds();
                    if (t.size() == 5) {
                        countDownLatch.countDown();
                    }
                    System.out.println(t.size());
                    try {
                        Thread.sleep(10);
                    } catch (InterruptedException e) {
                        e.printStackTrace();
                    }
                }
        }).start();
    }
}

运行截图

在这里插入图片描述

方式四:还可以使用CyclicBarrier、Semaphore。实现起来也比较简单,这里就不一一列举了。

如有错误请提出指正。
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值