(四)Java多线程 -- 并发同步工具CountDownLatch 倒数门栓、CyclicBarrier 循环栅栏、Phaser 阶段、ReadWriteLock 读写锁、Semaphore 信号灯

1、CountDownLatch 倒数门栓

代码解释:
门栓计数100,每一个线程结束的时候我让latch.countDown(),然后所有线程start(),再latch.await()
最后结束。

作用:
latch.await() 的作用就相当于门卫,每个线程执行完成都会减一,直到=0时,门卫就会开门。
然后执行>后面的代码。

相比join更为灵活
/**
     * 多线程生成雪花ID
     */
    public static void main(String[] args) throws InterruptedException {
        //计时开始时间
        long start = System.currentTimeMillis();
        //让100个线程同时进行
        final CountDownLatch latch = new CountDownLatch(100);
        //判断生成的20万条记录是否有重复记录
        final Map<Long, Integer> map = new ConcurrentHashMap();
        for (int i = 0; i < 100; i++) {
            //创建100个线程
            new Thread(() -> {
                for (int s = 0; s < 2000; s++) {
                    long snowID = SnowIdUtils.uniqueLong();
                    log.info("生成雪花ID={}",snowID);
                    Integer put = map.put(snowID, 1);
                    if (put != null) {
                        throw new RuntimeException("主键重复");
                    }
                }
                latch.countDown();
            }).start();
        }
        //让上面100个线程执行结束后,在走下面输出信息
        latch.await();
        log.info("生成20万条雪花ID总用时={}", System.currentTimeMillis() - start);
    }
join 用法示例
代码解释:
启动10个线程去累加一万次。正确结果为10万。

count 是使用CAS原子类的计数方法
c是传统的synchronized锁的计数方法
import java.util.ArrayList;
import java.util.List;
import java.util.concurrent.atomic.AtomicInteger;

/**
 * @program: bike-lease
 * @description: test
 * @author: Jack.Fang
 * @date:2021-06-24 下午 3:20
 **/
public class Test {
    AtomicInteger count = new AtomicInteger(0);

    int c = 0;

    void m(){
        for(int i=0;i<10000;i++){
            count.incrementAndGet();
            synchronized (this){
                c++;
            }
        }
    }

    public static void main(String[] args) {
        Test test = new Test();

        List<Thread> threadList = new ArrayList<>();

        for(int i=0;i<10;i++){
            threadList.add(new Thread(test::m,"thread-"+i));
        }

        threadList.forEach((o) -> o.start());

        threadList.forEach((o) -> {
            try{
                o.join();
            }catch (Exception e){
                e.printStackTrace();
            }
        });

        System.out.println(test.count);
        System.out.println(test.c);
    }
}

2、CyclicBarrier 循环栅栏

什么时候人满了就把栅栏推到,让、把人放出去,然后栅栏又立起来继续循环。。。

代码解释:
两个参数,第二个参数可以不传也可以,就是满了什么也不做。
第一个参数是20,满了之后就帮我调用第二个参数指定的动作,就是一个Runnable对象。等够20人
则执行barrier.await()
CyclicBarrier barrier = new CyclicBarrier(20,()-> System.out.println("满人"));
for(int i=0;i<100;i++){
    new Thread(()->{
        try {
            barrier.await();
        } catch (InterruptedException e) {
            e.printStackTrace();
        } catch (BrokenBarrierException e) {
            e.printStackTrace();
        }
    }).start();
}

3、Phaser 阶段

它更像是结合了 CountDownLatch CyclicBarrier 。Phaser 是按照不同的阶段来对线程进行执行。

代码解释:
模拟结婚的场景,有好多人参加,因此写了一个Person类,是一个Runnable可以new处理,扔给Thread去执行,
模拟我们每个人要做一些操作,有这些方法:arrive()到达,eat()吃,leave()离开,hug()拥抱。

作为一个婚礼来说它会分为好几个阶段,第一阶段大家都得到齐了,第二阶段大家开始吃饭,三阶段大家离开,
第四个阶段新郎新娘入洞房。每个人都有这几个方法,在方法的实现里头就简单的睡1000毫秒。

再看主程序,一共有5个人参加婚礼了,接下来新郎新娘参加婚礼,一共7个人。它一start就好调用我们得run()
方法,它会挨着牌的调用每一个阶段的方法。那好,我们在每一个阶段是不是得控制人数,第一个阶段得要人到期
了才能开始,二阶段所有人吃饭,三阶段所有人都离开,但是,到了四阶段进入洞房的时候就只能是新郎新娘参与了
所以,要模拟一个程序就要把整个过程很好几个阶段,而且每个阶段必须要等这些线程给我刚完事了你才能进入
下一个阶段。

怎么模拟阶段,首先定义了一个phaser,这个phaser是从Phaser这个类继承,重写onAdvance方法,前进,
新城抵达这个栅栏的时候,所有的线程都满足了这个第一个栅栏的条件了onAdvance会被自动调用,
目前我们有好几个阶段,这个阶段是被写死的,必须是数字0开始,onAdvance会传来两个参数phaser
是第几个阶段registeredParties是目前这个阶段有几个人参加,每一个阶段都有一个打印,返回false,
一直到最后一个阶段返回true,所有线程结束,整个栅栏组,Phaser栅栏组就结束了。

怎么才能让我的线程再一个栅栏面前给停住呢,就是调用phaser.arriveAndAwaitAdvance()这个方法,
这个方法的意思是到达等待继续往前走,知道新郎新娘入洞房,其他人不再参与,
调用phaser.arriveAndDeregister()这个方法。还有可以调用方法phaser.register()往上加,
不仅可以控制栅栏上的个数还可以控制栅栏上的等待数量,这个就叫做phaser。这个主要拓宽知识面用的

import java.util.Random;
import java.util.concurrent.Phaser;
import java.util.concurrent.TimeUnit;

/**
 * @program: bike-lease
 * @description: 阶段线程
 * @author: Jack.Fang
 * @date:2021-06-28 下午 2:30
 **/
public class TestPhaser {
    static Random r = new Random();
    static MarriagePhaser phaser = new MarriagePhaser();

    static void milliSleep(int milli){
        try {
            TimeUnit.MICROSECONDS.sleep(milli);
        }catch (InterruptedException e) {
            e.printStackTrace();
        }
    }

    public static void main(String[] args) {
        phaser.bulkRegister(7);
        for(int i=0;i<5;i++){
            new Thread(new Person("p"+i)).start();
        }
        new Thread(new Person("新郎")).start();
        new Thread(new Person("新娘")).start();
    }

    static class MarriagePhaser extends Phaser{
        @Override
        protected boolean onAdvance(int phase, int registeredParties) {

            switch (phase){
                case 0:
                    System.out.println("所有人到齐了!"+registeredParties);
                    System.out.println();
                    return false;
                case 1:
                    System.out.println("所有人都吃完了!"+registeredParties);
                    System.out.println();
                    return false;
                case 2:
                    System.out.println("所有人都离开了!"+registeredParties);
                    System.out.println();
                    return false;
                case 3:
                    System.out.println("婚礼结束!新郎新娘抱抱!"+registeredParties);
                    return true;
                default:
                    return true;
            }
        }
    }

    static class Person implements Runnable{

        String name;

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

        public void arrive(){
            milliSleep(r.nextInt(1000));
            System.out.printf("%s 到达现场!\n",name);
            phaser.arriveAndAwaitAdvance();
        }

        public void eat(){
            milliSleep(r.nextInt(1000));
            System.out.printf("%s 吃完!\n",name);
            phaser.arriveAndAwaitAdvance();
        }

        public void leave(){
            milliSleep(r.nextInt(1000));
            System.out.printf("%s 离开!\n",name);
            phaser.arriveAndAwaitAdvance();
        }

        public void hug(){
            if(name.equals("新郎")||name.equals("新娘")){
                milliSleep(r.nextInt(1000));
                System.out.printf("%s 洞房!\n",name);
                phaser.arriveAndAwaitAdvance();
            }else{
                phaser.arriveAndDeregister();
            }
        }
        @Override
        public void run() {
            arrive();
            eat();
            leave();
            hug();
        }
    }

}

4、ReadWriteLock 读写锁

概念其实就是共享锁和排他锁,读锁就是共享锁,写锁就排他锁。

代码解释:
例如获取公司架构,读多写少。要求他不产生数据不一致的情况。可以做成这种锁,当读线程上来的时候加一把锁
是允许其他读线程可以读,写线程来了我不给它,你先别写,等我读完了你在写。读线程进来的时候我们大家
一起读,因为你不改原来的内容,写线程上来把整个线程全锁定,你先不要读,等我写完你再读。

读写锁怎么用,我们这有两个方法,read()读一个数据,write()写一个数据。read这个数据的是否我们需要
你往里面传一把锁,这个传那把锁你自己定,我们可以划船自己定义的全部都是排他锁,也可以传读写锁里面的
读锁或写锁。write的时候也需要传把锁,同事需要你传一个新值完成后解锁unlock。

我们现在的问题是往里传这个lock有两种方法,第一种直接new ReentrantLock()传进去,分析下这种方法,
主程序定义了一个Runnable对象,第一个是调用read()方法,第二个是调用write()方法同时往里头扔一个
随机值,然后起了18个读线程,起了2个写线程,这个两个我要想执行完的话,我现在传的是一个ReentranLock,
这把锁上了之后没有其他任何人可以拿到这把锁,而这里面每一个线程执行都需要1秒钟,在这种情况下你必须
得等20秒才能干完这事儿;

第二种,我们换了锁 new ReentrantReadWriteLock()是ReadWriteLock的一种实现,
在这种思想里头我又分出两把锁来,读锁我就拿到了。
通过readWriteLock.writeLock()拿到writeLock对象。

这两把锁在我读的时候登进去,因此,读线程是可以一起读的,也就是说这18个线程可以一秒钟完成工作结束,
所以使用读写锁效率会大大的提升。
import java.util.Random;
import java.util.concurrent.locks.Lock;
import java.util.concurrent.locks.ReentrantLock;
import java.util.concurrent.locks.ReentrantReadWriteLock;

/**
 * @program: bike-lease
 * @description: 读写锁
 * @author: Jack.Fang
 * @date:2021-06-28 下午 3:02
 **/
public class ReadWriterLock {
    static Lock lock = new ReentrantLock();
    private static int value;

    static ReentrantReadWriteLock readWriterLock = new ReentrantReadWriteLock();
    static Lock readLock = readWriterLock.readLock();
    static Lock writeLock = readWriterLock.writeLock();

    public static void read(Lock lock){
        try{
            lock.lock();
            Thread.sleep(1000);
            System.out.println("read over!");
        } catch (InterruptedException e) {
            e.printStackTrace();
        }finally {
            lock.unlock();
        }
    }

    public static void write(Lock lock,int v){
        try{
            lock.lock();
            Thread.sleep(1000);
            value = v;
            System.out.println("write over!");
        } catch (InterruptedException e) {
            e.printStackTrace();
        }finally {
            lock.unlock();
        }
    }

    public static void main(String[] args) {
//        Runnable readR = ()->read(lock);
        Runnable readR = ()->read(readLock);

//        Runnable writeR = ()->write(lock,new Random().nextInt());
        Runnable writeR = ()->write(writeLock,new Random().nextInt());

        for(int i=0;i<18;i++) new Thread(readR).start();
        for(int i=0;i<2;i++) new Thread(writeR).start();
    }

}

5、Semaphore 信号灯

可以往里面传一个数,permits是允许的数量,你可以想着有几盏信号灯,一个灯你们闪着数字表示到底
允许几个来参考我这个信号灯。

s.acquire()这个方法叫阻塞方法,阻塞方法的意思是说我大概acquire不到的话我就停在这,
acquire的意思就是得到。如果我Semaphore s = new Semaphore(1)写的是1,我取一下,
acquire一下他就变成0,当变成0之后别人是acquire不到的,然后继续执行,线程结束之后注意
要s.release(),执行完该方法就会把0变成1,还原化。

Semaphore的含义就是限流,比如说你在买票,Semaphore写5就是只能有5个人可以同时买票。
acquire的意思叫获得这把锁,线程如果想要继续往下执行,必须得从Semaphore里面获得一个许可,
他一共有5个许可用到0你就得给我等着。

例如有一个8条车道的机动车,这里只有2个收费站,到这儿,谁acquire得到其中一个谁执行,
默认Semaphore是非公平的,new Semaphore(2,true)第二个值传true才是设置公平。
公平这个事儿是有一堆队列在哪儿等,大家伙过来排队,用这个车道和收费站来举例,
就是我们有4辆车都在等着进入一个车道,然后买再来一辆新的时候,他不会超到前面去,
要在后面排队这叫公平。所以说内部是有队列的。从上面的CountDownLatch 、CyclicBarrier 、Phaser、
ReadWriteLock、Semaphore、Exchanger都是用用一个队列,同一个类来实现的,这个类叫AQS
import java.util.concurrent.Semaphore;

/**
 * @program: bike-lease
 * @description: 信号灯
 * @author: Jack.Fang
 * @date:2021-06-28 下午 3:48
 **/
public class TestSemaphore {

    public static void main(String[] args) {
        //非公平
//      Semaphore s = new Semaphore(2);
        //公平
        Semaphore s = new Semaphore(2,true);
        //允许一个线程同时执行
//      Semaphore s = new Semaphore(1);

        new Thread(()->{
            try{
                s.acquire();
                System.out.println("T1 running...");
                Thread.sleep(1000);
                System.out.println("T1 running...");
            } catch (InterruptedException e) {
                e.printStackTrace();
            }finally {
                s.release();
            }
        }).start();

        new Thread(()->{
            try{
                s.acquire();
                System.out.println("T2 running...");
                Thread.sleep(1000);
                System.out.println("T2 running...");

                s.release();
            } catch (InterruptedException e) {
                e.printStackTrace();
            }
        }).start();
    }
}

6、Exchanger 交换机

Exchanger 主要是拓展知识面用的,两人之间互相交换数据用的。

代码解释:
第一个相册有一个成员变量s,然后exchanger.exchange(s),第二个也是这样,t1线程名字叫T1,
第二个线程名字叫T2。到最后,打印出来你会发现他们两交换了一下。线程间通信的方式非常多,
这只是其中一种,就是线程之间交换数据用的。

exchanger可以想象是一个容器,容器中有2个值,两个线程,有两个格子的未知,第一个相册执行到
exchanger.exchange的时候,阻塞,但是要注意我这个exchange方法的是否是往里面扔了一个值,
你可以认为把T1扔到了第一个格子,然后第二个线程开始执行,也执行到这句话了,exchange,
他把自己的这个值T2扔到第二个格子里,接下来这两个值交换了一个下,T1扔给T2,T2扔给T1。
两个线程继续往前跑。exchange只能是两个线程之间,交换这个东西只能两两进行。

exchange的使用场景,比如在游戏中两个人装备交换。
import java.util.concurrent.Exchanger;

/**
 * @program: bike-lease
 * @description: 交换机
 * @author: Jack.Fang
 * @date:2021-06-28 下午 4:12
 **/
public class TestExchanger {
    static Exchanger<String> exchanger = new Exchanger<>();

    public static void main(String[] args) {
        new Thread(()->{
            String s = "T1";
            try{
                s = exchanger.exchange(s);
            } catch (InterruptedException e) {
                e.printStackTrace();
            }
            System.out.println(Thread.currentThread().getName()+" "+s);
        },"t1").start();

        new Thread(()->{
            String s = "T2";
            try{
                s = exchanger.exchange(s);
            } catch (InterruptedException e) {
                e.printStackTrace();
            }
            System.out.println(Thread.currentThread().getName()+" "+s);
        },"t2").start();
    }
}
  • 0
    点赞
  • 4
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

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

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

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值