线程之间的通信(面试题)

实现一个容器,提供两个方法,add,size
写两个线程,线程1添加10个元素到容器中, 线程2实现监控元素的个数,当个数到5个时,线程2给出提示并结束
这是一道线程的面试题,看起来挺简单的,做起来实际上有不少小坑,是一道很有意思的面试题,目前我能想到的是大概七种解法,下面把我的解法分享给大家。你们有不一样的解法欢迎到评论区留言

解法一:传统的wait and notify实现

public class WithoutVolatile {

//创建一个线程安全的ArrayList集合,其实在这个解法中不用这个线程安全类也是可以的,这样加上反而会影响效率,因为wait and notify是在synchronized 基础上才可以实现的
//因为synchronized 本身就是保证线程安全的锁
//    private List<Integer> list=new ArrayList ();
    private List<Integer> list= Collections.synchronizedList(new ArrayList <> ());
    
    public void add(int i){
        list.add (i);
    }
    public int getSize(){
        return list.size ();
    }

    public static void main(String[] args) {
    //创建容器类
        WithoutVolatile withoutVolatile = new WithoutVolatile ( );
        Thread thread = new Thread (() -> {
        //锁住共有的容器类
            synchronized (withoutVolatile){
                for (int i = 0; i < 10; i++) {
                    try {
                    	//每次检测一下容器是否等于5
                        if (withoutVolatile.getSize ()==5){
                        //等于5,让改线程进入等待状态
                            withoutVolatile.wait ();
                        }
                        withoutVolatile.add (i);
                        System.out.println ("添加:"+i);
                        TimeUnit.SECONDS.sleep (1);
                        //每次执行完添加唤醒在等待队列的线程
                        //notify 只有唤醒功能并没有释放锁的功能
                        withoutVolatile.notify ();
                    } catch (InterruptedException e) {
                        e.printStackTrace ( );
                    }
                }
            }
        });
        Thread thread1 = new Thread (() -> {
            synchronized (withoutVolatile){
                try {
                //检测一下容器是否等5
                    if (withoutVolatile.getSize ()!=5){
                    //不等于五,就让该线程进入等待
                        withoutVolatile.wait ();
                    }
                    System.out.println ("监听到了,结束。。。。");
                    //监听后,唤醒在等待的线程继续往下执行
                    withoutVolatile.notify ();
                } catch (InterruptedException e) {
                    e.printStackTrace ( );
                }
            }
        });
        thread1.start ();
        thread.start ();

    }
}

解法一看起来比较繁乱,notify之后,t1必须释放锁,t2退出后,也必须notify,通知t1继续执行整个通信过程比较繁琐。
我门能不能尝试用join的方式呢,不用线程通信的形式?

解法二 join+synchronized/ReentrantLock的方式

public class WithoutVolatile5 {
    private volatile List list= Collections.synchronizedList (new LinkedList<> ());
    static Thread t2=null;
    static Thread t1=null;
    public  void add(Object i){
        list.add (i);
    }
    public  int size(){
        return list.size ();
    }

    public static void main(String[] args) {
        WithoutVolatile5 w=new WithoutVolatile5 ();
        t1 = new Thread (() -> {
            try {
                synchronized (w){
                    for (int i = 0; i < 5; i++) {
                        w.add (i);
                        System.out.println("添加 " + i);
                    }
                }
                //在for循环添加到5时让改线程释放锁然后启动t2线程,通过调用join方法来保证t2线程执行时不被打断,保证了线程的安全性
                t2.start ();
                t2.join ();
                synchronized (w){
                    for (int i = 5; i < 10; i++) {
                        w.add (i);
                        System.out.println("添加 " + i);
                    }
                }
            } catch (InterruptedException e) {
                e.printStackTrace ( );
            }
        });
        t2 = new Thread (() -> {
           System.out.println ("监听到了,结束。。。。");
        });
        t1.start ();
    }
}

这种解法虽然没有wait跟notify繁琐,但是加了两次锁,会导致效率的降低。synchronized跟ReentrantLock是一样写法,我在这就不写了,(注意用ReentrantLock一定要解锁!!!)因为synchronized是jvm自动解锁,但还是ReentrantLock没有。所以一定要解锁。
接下来的解法都是JUC并发包下的类来实现的

解法三 用门闩(CountDownLatch)的方式实现

public class WithoutVolatile1 {
    private volatile List list=Collections.synchronizedList (new LinkedList <> ());
    public  void add(Object i){
        list.add (i);
    }
    public  int size(){
        return list.size ();
    }

    public static void main(String[] args) {
        WithoutVolatile1 w=new WithoutVolatile1();
        //为什么要创建两个门闩呢?
        //假如你用一个CountDownLatch 的话会导致线程二在执行的同时出现了CPU的上下文切换,导致数据的不一致,可以多试几次就会发现问题
        CountDownLatch countDownLatch = new CountDownLatch (1);
        CountDownLatch latch = new CountDownLatch (1);
        Thread thread = new Thread (() -> {
            try {
                for (int i = 0; i < 10; i++) {
                    if (w.size ( ) == 5) {
                        countDownLatch.countDown ( );
                        latch.await ();
                    }
                    w.add (i);
                    System.out.println("添加 " + i);
                }
            } catch (InterruptedException e) {
                e.printStackTrace ( );
            }
        });
        Thread thread1 = new Thread (() -> {
            try {
                if(w.size ()!=5){
                    countDownLatch.await ( );
                }
                System.out.println ("监听到了,结束。。。。");
                latch.countDown ();
            } catch (InterruptedException e) {
                e.printStackTrace ( );
            }
        });
        thread.start ();
        thread1.start ();

    }
}

解法四 ReentrantLock中的Condition方式

做法跟wait跟notify一样的只是参数不一而已

public class WithoutVolatile6 {
    private volatile List list=Collections.synchronizedList (new LinkedList <> ());
    public  void add(Object i){
        list.add (i);
    }
    public  int size(){
        return list.size ();
    }

    public static void main(String[] args) {
        WithoutVolatile6 w=new WithoutVolatile6 ();
        ReentrantLock lock=new ReentrantLock ();
        //用condition必须要结合lock锁,跟synchronized搭配wait与notify是一个原则
        Condition condition = lock.newCondition ( );   
        Thread thread = new Thread (() -> {
            try {
                lock.lock ();
                for (int i = 0; i < 10; i++) {
                    if (w.size ( ) == 5) {
                        condition.await ();
                    }
                    w.add (i);
                    System.out.println("添加 " + i);
                    condition.signal ();
                }
            } catch (InterruptedException e) {
                e.printStackTrace ( );
            }finally {
                lock.unlock ();
            }
        });
        Thread thread1 = new Thread (() -> {
            try {
                lock.lock ();
                if(w.size ()!=5){
                    condition.await ();
                }
                System.out.println ("监听到了,结束。。。。");
                condition.signal ();
            } catch (InterruptedException e) {
                e.printStackTrace ( );
            }finally {
                lock.unlock ();
            }
        });
        thread.start ();
        thread1.start ();

    }
}

解法五 用LockSupport阻塞的方式实现

public class WithoutVolatile2 {
    private volatile List list= Collections.synchronizedList (new LinkedList<> ());
    //为什么将线程声明在此?
    //是因为LockSupport.park是静态方法,需要传参的话就必须是静态的,那为什么要声明在外部呢,这是因为lambada内要声明final,所以直接定义在外部了
    static Thread t2=null;
    static Thread t1=null;
    public  void add(Object i){
        list.add (i);
    }
    public  int size(){
        return list.size ();
    }

    public static void main(String[] args) {
        WithoutVolatile2 w=new WithoutVolatile2();

       t2 = new Thread (() -> {
            if(w.size ()!=5){
            //指定让那个线程阻塞
                LockSupport.park (t2);
            }
            System.out.println ("监听到了,结束。。。。");
           LockSupport.unpark (t1);
        });

        t1 = new Thread (() -> {
            for (int i = 0; i < 10; i++) {
                if (w.size () == 5) {
                    LockSupport.unpark (t2);
                    LockSupport.park (t1);
                }
                w.add (i);
                System.out.println ("添加:" + i);
            }
        });
        t1.start ();
        t2.start ();
    }
}

解法六 用信号量(Semaphore)的方式实现

public class WithoutVolatile3 {
    private volatile List list= Collections.synchronizedList (new LinkedList<> ());
    static Thread t2=null;
    static Thread t1=null;
    public  void add(Object i){
        list.add (i);
    }
    public  int size(){
        return list.size ();
    }

    public static void main(String[] args) {
        WithoutVolatile3 w=new WithoutVolatile3();
        Semaphore semaphore=new Semaphore (1);

        t1=new Thread (()->{
            try {
                for (int i = 0; i < 5; i++) {
                //通过信号量的方式来保证这个区间是线程安全的
                    semaphore.acquire ();
                    w.add (i);
                    System.out.println ("添加:" + i);
                    semaphore.release ();
                }
                t2.start ();
                t2.join ();
                for (int i = 5; i < 10; i++) {
                    semaphore.acquire ();
                    w.add (i);
                    System.out.println ("添加:" + i);
                    semaphore.release ();
                }
            } catch (InterruptedException e) {
                e.printStackTrace ( );
            }
        });
        t2 = new Thread (() -> {
            try {

                semaphore.acquire ( );
                System.out.println ("监听到了,结束。。。。");
                semaphore.release ( );
            } catch (InterruptedException e) {
                e.printStackTrace ( );
            }
        });
        t1.start ();
    }
}

这种解法跟join是一样的,没有线程之间的互调,所以不是很合理

解法七 用栅栏(Cyclicbarrier)的方式实现

public class WithoutVolatile4 {
    private volatile List list= Collections.synchronizedList (new LinkedList<> ());
    static Thread t2=null;
    static Thread t1=null;
    public  void add(Object i){
        list.add (i);
    }
    public  int size(){
        return list.size ();
    }

    public static void main(String[] args) {
        WithoutVolatile4 w=new WithoutVolatile4();
        CyclicBarrier cyclicBarrier=new CyclicBarrier (1);
        t1 = new Thread (() -> {
            try {
                for (int i = 0; i < 5; i++) {
                    w.add (i);
                    System.out.println("添加 " + i);
                    cyclicBarrier.await ();
                }
                t2.start ();
                t2.join ();
                for (int i = 5; i < 10; i++) {
                    w.add (i);
                    System.out.println("添加 " + i);
                    cyclicBarrier.await ();
                }
            } catch (InterruptedException e) {
                e.printStackTrace ( );
            }catch (BrokenBarrierException e) {
                e.printStackTrace ( );
            }
        });
        t2 = new Thread (() -> {
           System.out.println ("监听到了,结束。。。。");
        });
        t1.start ();
    }
}

我个人觉得用栅栏也可以实现门闩的做法,只是我感觉声明两个栅栏有点浪费,所以就用了上面这种做法
以上就是我所发现的解法!有写的不对的或者有新的解法欢迎评论区指正

  • 2
    点赞
  • 8
    收藏
    觉得还不错? 一键收藏
  • 5
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论 5
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值