JDK多线程基础(14):同步工具

Semaphore

简介

  1. 无论是内部锁synchronized 还是 ReentrantLock ,一次都只允许一个线程访问一个资源
  2. Semaphore 是对锁的扩展,允许多个线程同时访问某一资源

源码

  1. 构造函数
  • public Semaphore(int permits):参数 permits 指定Semaphore 的准入数,即同时能申请多少个许可。默认是非公平锁
  • public Semaphore(int permits, boolean fair):第二个参数是指定时候为公平锁
  1. 逻辑方法:默认每一个线程每次只申请一个许可证,相当于指定了同时有多少线程可以访问某一个资源
  • public void acquire():尝试获取,没有,则线程等待;等待的过程中总会响应线程中断
  • public void acquireUninterruptibly():与acquire()类似,只是不会响应中断
  • public boolean tryAcquire():尝试获取,没有,则立即返回,不等待
  • public boolean tryAcquire(long timeout, TimeUnit unit):尝试获取,没有,等待
  • public void release() 释放
  1. 注意:一个线程其实可以设置获取多个许可(只有当每一个线程每次只申请),如以下组方法:
  • public void acquire(int permits)
  • public void acquireUninterruptibly(int permits)
  • public void release(int permits)

示例

  1. 简单示例
// 创建线程池,newCachedThreadPool,在没有空闲的情况下,有多少任务,创建多少线程
ExecutorService service = Executors.newCachedThreadPool();

// 建立信号灯,有参数fair,实现线程进入优先级。true的时候,先到先进
final Semaphore sp = new Semaphore(3);

//创建10个线程
for (int i = 0; i < 5; i++) {
    Runnable runnable = new Runnable() {
        @Override
        public void run() {
            try {
                sp.acquire();//获取信号灯
            } catch (InterruptedException e) {
                e.printStackTrace();
            }


            //sp.availablePermits() 被拿走的灯的个数
            System.out.println("线程" + Thread.currentThread().getName() +
                    "进入,当前已有" + (3 - sp.availablePermits()) + "线程");
            try {
                Thread.sleep((long) (Math.random() * 10000));
            } catch (InterruptedException e) {
                e.printStackTrace();
            }
            System.out.println("线程" + Thread.currentThread().getName() +
                    "即将离开");
            sp.release();//释放获取的灯
            //下面代码有时候执行不准确
            System.out.println("线程" + Thread.currentThread().getName() +
                    "已离开,当前已有" + (3 - sp.availablePermits()) + "线程");
        }
    };
    //执行10个线程
    service.execute(runnable);
}

输出:
线程pool-1-thread-1进入,当前已有1线程
线程pool-1-thread-2进入,当前已有2线程
线程pool-1-thread-3进入,当前已有3线程
线程pool-1-thread-1即将离开
线程pool-1-thread-4进入,当前已有3线程
线程pool-1-thread-1已离开,当前已有3线程
线程pool-1-thread-2即将离开
线程pool-1-thread-5进入,当前已有3线程
线程pool-1-thread-2已离开,当前已有3线程
线程pool-1-thread-4即将离开
线程pool-1-thread-4已离开,当前已有2线程
线程pool-1-thread-5即将离开
线程pool-1-thread-5已离开,当前已有1线程
线程pool-1-thread-3即将离开
线程pool-1-thread-3已离开,当前已有0线程
  1. 简单实现一个对象池
public class SemaphorePool {

    private static final int MAX_AVAILABLE = 100;

    /**
     * 最大可以有 100 个许可
     */
    private final Semaphore available = new Semaphore(MAX_AVAILABLE);


    public Object getItem() throws InterruptedException {
        // 申请一个许可
        // 同时只能申请 100 个线程进入取得可用项,超过100个则需要等待
        available.acquire();
        return getNextAvailableItem();
    }

    public void putItem(Object x) {
        if (markAsUnused(x)) {
            // 释放一个许可证
            available.release();
        }
    }

    /**
     * 存放对象
     */
    Object[] items = new Object[MAX_AVAILABLE];
    Boolean[] used = new Boolean[MAX_AVAILABLE];

    private Object getNextAvailableItem() {

        for (int i = 0; i < MAX_AVAILABLE; i++) {
            // 如果当前项未被使用,则获得它
            if (!used[i]) {
                // 将当前项标记为已经使用
                used[i] = true;
                return items[i];
            }
        }
        return null;
    }

    private synchronized boolean markAsUnused(Object item) {
        for (int i = 0; i < MAX_AVAILABLE; i++) {
            // 找到给定项的索引
            if (item == items[i]) {
                if (used[i]) {
                    // 将给定项标记为未被使用
                    used[i] = false;
                    return true;
                } else {
                    return false;
                }
            }
        }
        return false;
    }

}

CountDownLatch

简介

  1. CountDownLatch:闭锁、倒计时门栓。可以实现如一个或多个线程一直等待,直到其他线程的操作执行完后再执行。同样的功能类似与主线程提交一组任务,等所有子线程任务完成后,主线程再次执行,期间一直阻塞。
  2. zookeeper 的原生watcher 就可以使用CountDownLatch 实现
  3. 我们自己写多线程并发测试,可以使用 CountDownLatch,一定程度上模拟实现并发

源码API

  1. 构造函数:public CountDownLatch(int count)
  • count 代表等待的线程(await())被count 个线程提醒,会开始执行
  1. await():等待,执行该方法的线程会阻塞等待
  2. await(long timeout, TimeUnit unit):等待,超时后抛出异常
  3. countDown():提醒,调用该方法提醒,会给阻塞的方法一个提醒(其实是内部计数器减1),当计数器(count)减为0,阻塞线程开始执行
  4. getCount():获取计数器

示例

  1. 简单例子
public class CountDownLatchFirst {

    public static void main(String[] args) throws InterruptedException {
        CountDownLatchFirst count = new CountDownLatchFirst();
        count.test1();
    }

    void test1() throws InterruptedException {
        CountDownLatch countDownLatch = new CountDownLatch(2);

        ExecutorService executor = Executors.newCachedThreadPool();
        executor.execute(new MyThread("Main", countDownLatch));
        executor.execute(new MyThread("Sub", countDownLatch));

        // main方法线程等待上面的两个线程完成后才会执行
        countDownLatch.await();

        System.out.println("~~~ 可以执行了 ~~~");

    }


    class MyThread implements Runnable {

        private final String name;
        private final CountDownLatch latch;

        public MyThread(final String name, final CountDownLatch latch) {
            this.name = name;
            this.latch = latch;
        }

        @Override
        public void run() {
            try {
                Thread.sleep((long) (Math.random() * 10000));
            } catch (InterruptedException e) {
                e.printStackTrace();
            }
            System.out.println("线程 " + name + " 完成工作");

            latch.countDown();
        }
    }
}

  1. zookeeperwatcher实现
public class ZkClientSecond implements Watcher {

    CountDownLatch downLatch = new CountDownLatch(1);
        
    void getZkClient() throws Exception {
        zkClient = Objects.isNull(zkClient) ? new ZooKeeper(connectString, 2000, this) : zkClient;
        downLatch.await();
    }

    @Override
    public void process(WatchedEvent event) {
        System.out.println("状态 : " + event.getState());
        System.out.println("path : " + event.getPath());
        System.out.println("已经触发了 " + event.getType() + " 事件!");

        if (Event.KeeperState.SyncConnected == event.getState()) {
            downLatch.countDown();

            switch (event.getType()) {
                case NodeCreated:
                    System.out.println("NodeCreated~~~");
                    break;
                case NodeDeleted:
                    System.out.println("NodeDeleted~~");
                    break;
                case NodeDataChanged:
                    System.out.println("NodeDataChanged~~");
                    break;
                case NodeChildrenChanged:
                    System.out.println("NodeChildrenChanged~~");
                    break;
                default:
                    System.out.println("default ~~~");
            }
        }
    }

}
  1. 模拟并发访问

    /**
     * 模拟2000 并发
     */
    private static final int THREAD_NUM = 2000;
    /**
     * 使用 CountDownLatch 尽量的模拟并发
     */
    private CountDownLatch countDownLatch = new CountDownLatch(THREAD_NUM);
    
    public void doService() throws InterruptedException {
        // 创建,并不是马上发起请求
        Thread[] threads = new Thread[THREAD_NUM];

        for (int i = 0; i < THREAD_NUM; i++) {
            // 多线程模拟用户查询请求
            Thread thread = new Thread(() -> {

                try {
                    // 代码在这边等待,等待countDownLatch 的计数器为0,再运行后续代码
                    countDownLatch.await();
                    // 调用业务方法
                    service.doService(name);

                } catch (InterruptedException e) {
                    e.printStackTrace();
                }
            });
            threads[i] = thread;

            thread.start();
            // 计数器递减
            countDownLatch.countDown();
        }

        for (Thread thread : threads) {
            thread.join();
        }
    }

CyclicBarrier

  1. CyclicBarrier 可以实现类似集合出发的功能,也可以用来实现模拟并发
    /**
     * 模拟2000 并发
     */
    private static final int THREAD_NUM = 50;

    /**
     * 使用 CyclicBarrier 尽量的模拟并发
     */
    private CyclicBarrier cyclicBarrier = new CyclicBarrier(THREAD_NUM);
    
    public void createOrder2() throws InterruptedException {

        for (int i = 0; i < THREAD_NUM; i++) {
            new Thread(() -> {

                // 模拟分布式环境
                OrderService orderService = new OrderServiceImplWithLock();

                try {
                    // 代码在这边等待一起出发
                    cyclicBarrier.await();
                } catch (InterruptedException | BrokenBarrierException e) {
                    e.printStackTrace();
                }

                // 调用业务方法
                orderService.createOrder();
            }).start();
        }

        Thread.sleep(50000L);
    }

Exchanger

参考

  1. 源码地址

Fork me on Gitee

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值