JAVA并发编程之多线程并发同步业务场景与解决方案

 

 

1、Semaphore

  • Semaphore是一个计数信号量,它的本质是一个"共享锁"。

  • 信号量维护了一个信号量许可集。线程可以通过调用acquire()来获取信号量的许可;当信号量中有可用的许可时,线程能获取该许可;否则线程必须等待,直到有可用的许可为止。 线程可以通过release()来释放它所持有的信号量许可。

  • Semaphore是一种在多线程环境下使用的设施,该设施负责协调各个线程,以保证它们能够正确、合理的使用公共资源的设施,也是操作系统中用于控制进程同步互斥的量。Semaphore是一种计数信号量,用于管理一组资源,内部是基于AQS的共享模式。它相当于给线程规定一个量从而控制允许活动的线程数。

public class SemaphoreDemo {
    /**
     *
     * 假设现在有二十个人去售票厅买票,但是窗口只有两个,那么同时能够买票的只能有两个,当
     * 2个人中任意一个人买好票离开之后,等待的18个人中又会有一个人可以占用窗口买票
     */

    /**
     * 执行任务类,获取信号量和释放信号
     */
    class SemaphoreRunnable implements Runnable {

        private Semaphore semaphore; //信号量

        private int user;  //记录第几个用户

        public SemaphoreRunnable(Semaphore semaphore, int user) {
            this.semaphore = semaphore;
            this.user = user;
        }

        @Override
        public void run() {
            try {
                //获取信号量的许可
                semaphore.acquire();
                System.out.print("用户 【" + user + "】进入窗口,准备买票...\n");
                Thread.sleep((long) (Math.random()*10000));
                System.out.print("用户 【" + user + "】买票完成,即将离开...\n");
                Thread.sleep((long) (Math.random()*10000));
                System.out.print("用户 【" + user + "】离开售票窗口...\n");
                //释放资源
                semaphore.release();
            } catch (InterruptedException e) {
                e.printStackTrace();
            }
        }
    }

    private void execute(){
        //1: 定义窗口个数
       final Semaphore semaphore = new Semaphore(2);

       //2: 使用线程池
        ExecutorService executorService = Executors.newCachedThreadPool();

        for (int i = 0; i < 20; i++) {
            executorService.execute(new SemaphoreRunnable( semaphore,(i+1)));
        }

        executorService.shutdown();
    }

    public static void main(String[] args) {
        SemaphoreDemo semaphoreDemo = new SemaphoreDemo();
        semaphoreDemo.execute();
    }
}

执行结果:

用户 【1】进入窗口,准备买票...
用户 【2】进入窗口,准备买票...
用户 【1】买票完成,即将离开...
用户 【2】买票完成,即将离开...
用户 【1】离开售票窗口...
用户 【3】进入窗口,准备买票...
用户 【2】离开售票窗口...
用户 【4】进入窗口,准备买票...
用户 【3】买票完成,即将离开...
用户 【3】离开售票窗口...
用户 【5】进入窗口,准备买票...
用户 【4】买票完成,即将离开...

2、CyclicBarrier

  • CyclicBarrier 的字面意思是可循环使用(Cyclic)的屏障(Barrier)。它要做的事情是,让一组线程到达一个屏障(也可以叫同步点)时被阻塞,直到最后一个线程到达屏障时,屏障才会开门,所有被屏障拦截的线程才会继续干活。

  • CyclicBarrier类似于我们前面看到的CountDownLatch.但是有以下两点不同:

    • 1:CountDownLatch是当状态达到后即当计数器为零,所有线程通过后CountDownLatch不可再用。而CyclicBarrier可以当状态达到后,所有线程通过后即可重复使用。
    • 2:CountDownLatch等待的是事件。CyclicBarrier等待的是线程.这里解释下等待事件和线程的区别.由于CountDownLatch存在countDown()方法。即对计数器减去1 .
    • 假设我们有如下代码:CountDownLatch latch = new CountDownLatch(5);
    • 我们在一个线程中对countDown()调用了5次。那么这个CountDownLatch的计数器就减为零。 然后等待的线程就可以通过了。
public class CyclicBarrierDemo {

    /**
     * 1:线程阻塞,多线程计算数据,合并结果
     */

    public static void main(String[] args) {

        final CyclicBarrier cyclicBarrier = new CyclicBarrier(3, new Runnable() {
            @Override
            public void run() {
                try {
                    Thread.sleep((long) (Math.random()*10000));
                    System.out.print("人员全部到齐,先进行拍照留念....\n");

                } catch (InterruptedException e) {
                    e.printStackTrace();
                }
            }
        });

        //2: 使用线程池
        ExecutorService executorService = Executors.newCachedThreadPool();

        for (int i = 0; i < 3; i++) {
            final  int user = i+1;
            Runnable runnable = new Runnable() {
                @Override
                public void run() {
                    try {
                        Thread.sleep((long) (Math.random()*10000));
                        System.out.print("用户 【" + user + "】到达聚餐点,当前已有" + (cyclicBarrier.getNumberWaiting()+1) + "...\n");

                        // 阻塞
                        cyclicBarrier.await();

                        if (user == 1){
                            System.out.print("人员全部到齐,拍照结束,开始吃饭....\n");
                        }

                        Thread.sleep((long) (Math.random()*10000));

                        System.out.print(user + "吃完饭,准备回家....\n");

                    } catch (BrokenBarrierException | InterruptedException e) {
                        e.printStackTrace();
                    }
                }
            };
            executorService.execute(runnable);
        }
        executorService.shutdown();
    }
}

执行结果:

用户 【2】到达聚餐点,当前已有1...
用户 【3】到达聚餐点,当前已有2...
用户 【1】到达聚餐点,当前已有3...
人员全部到齐,先进行拍照留念....
人员全部到齐,拍照结束,开始吃饭....
3吃完饭,准备回家....
2吃完饭,准备回家....
1吃完饭,准备回家....

3、Exchanger

  • 两个线程可以交换对象的同步点。每个线程都在进入exchange方法时给出某个对象,并接受其他线程返回时给出的对象。用于实现两个人之间的数据交换,每个人在完成一定的事务后想与对方交换数据,第一个先拿出数据的人将一直等待第二个人拿着数据到来时,才能彼此交换数据。
public class ExchangeDemo {
    /**
     * 1:线程之间交换数据
     * 2:校对工作
     */


    public static void main(String[] args) {
        Exchanger<String> exchanger = new Exchanger<>();

        ExecutorService executorService = Executors.newCachedThreadPool();

        executorService.execute(new Runnable() {
            @Override
            public void run() {
                try {
                    String  returnString = exchanger.exchange("AKyS");
                    System.out.println("该线程用AKyS进行交换...\n");
                } catch (InterruptedException e) {
                    e.printStackTrace();
                }
            }
        });

        executorService.execute(new Runnable() {
            @Override
            public void run() {
                try {

                    //必须两个线程同时到达,同时执行。当然可以使用同步锁
                    Thread.sleep((long) (Math.random()*10000));
                    String  returnString = exchanger.exchange("BLANK");
                    System.out.println("该线程用BLANK进行交换...\n");
                } catch (InterruptedException e) {
                    e.printStackTrace();
                }
            }
        });

        executorService.shutdown();
    }

执行结果:

该线程用BLANK进行交换...

该线程用AKyS进行交换...

4、CountDownLatch

  • 当一个线程需要在另外若干个线程执行完毕后再执行时。当然我们也可以使用join()方法来实现,但是这需要去逐个线程等待,而且各个线程是串行运行的。CountDownLatch的实现是等待一个信号量count,每当线程执行一次CountDownLatch.countDown()方法时,CountDownLatch就将count-1,直到count为0时,将唤醒执行CountDownLatch.await()处的线程。
public class CountDownLatchDemo {
   /**
    * 1: 有一个任务,它需要等待其他某几个任务执行完毕之后才能执行
    */
   public static void main(String[] args) {
       final CountDownLatch countDownLatch = new CountDownLatch(2);

       //任务1
       new Thread(){
           public void run(){
               try {
                   System.out.println("任务1正在执行...\n");

                   Thread.sleep((long) (Math.random()*10000));
                   System.out.println("任务1执行完毕...\n");

                   countDownLatch.countDown();
               } catch (InterruptedException e) {
                   e.printStackTrace();
               }
           }
       }.start();

       //任务2
       new Thread(){
           public void run(){
               try {
                   System.out.println("任务2正在执行...\n");

                   Thread.sleep((long) (Math.random()*10000));
                   System.out.println("任务2执行完毕...\n");

                   countDownLatch.countDown();
               } catch (InterruptedException e) {
                   e.printStackTrace();
               }
           }
       }.start();

       //主线程
       System.out.println("等待其他线程执行完毕,主线程开始执行: " + Thread.currentThread().getName() + "...\n");

       try {
           countDownLatch.await();
           System.out.printf("其他线程执行完毕,主线程执行任务:" + Thread.currentThread().getName()+ "...\n");
       } catch (InterruptedException e) {
           e.printStackTrace();
       }
   }
}

执行结果:

任务1正在执行...

任务2正在执行...

等待其他线程执行完毕,主线程开始执行: main...

任务2执行完毕...

任务1执行完毕...

其他线程执行完毕,主线程执行任务:main...


转载自:
作者:AKyS佐毅
链接:https://www.jianshu.com/p/bb1dd7946087
 

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

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

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

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值