并发编程-Executor、锁

Executor框架

Execurors,扮演线程工厂的角色,通过Executors可以创建特定功能的线程池。
Executors创建线程池方法:
- newFixedThreadPool()方法,该方法返回一个固定数量的线程池,该方法的线程数始终不变,当有一个任务提交时,若线程池中空闲,则立即执行,若没有,则会被暂缓在一个任务队列中等待有空闲的线程去执行。
- newSingleThreadExecutor()方法,创建一个线程的线程池,若空闲则执行,若没有空闲线程则暂缓在任务队列中。
- newCachedThreadPool()方法,返回一个可根据实际情况调整线程个数的线程池,不限制最大线程数量,若用空闲的线程则执行任务,若无任务则不创建线程,并且每一个空闲线程会在60秒后自动回收。
- newScheduledThreadPool()方法,该方法返回一个ScheduedExecurorService对象,但该线程池可以指定线程的数量。
本质都是创建ThreadPoolExecutor,通过参数进行控制,满足不同的需求。

若Executors工厂类无法满足我们的需求,可以自定义线程池,Executors工厂类里创建线程方法内部都是用了ThreadPoolExecutor这个类,这个类可以自定义线程。构造方法:

public ThreadPoolExecutor(int corePoolSize,
                        int maximumPoolSize,
                        long keepAliveTime,
                        BlockingQueue<Runnable> workQueue,
                        ThreadFactory threadFactory,
                        RejectedExecutionHandler handler){...}

这个构造方法对于队列是什么类型的比较关键。
在使用有界队列时,若有新的任务需要执行,如果线程池实际线程数小于corePoolSize,则优先创建线程,若大于corePoolSize,则会将任务加入队列,若队列已满,则在总线程数不大于maximumPoolSize的前提下,创建新的线程,若线程数大于maximumPoolSize,则执行拒绝策略。或其他自定义方式。
无界的任务队列时:LinkedBlockingQueue,与有界队列相比,除非系统资源耗尽,否则无界的任务队列不存在任务入队失败的情况。当有新任务到来,系统的线程数小于corePoolSize时,则新建线程执行任务。当达到corePoolSize后,就不会继续增加。若后续仍有新的任务加入,而有又没有空闲的线程资源,则任务直接进行队列等待。若任务创建赫尔处理的速度差异很大,无界队列会保持快速增长,直到耗尽系统内存。

public class MyTask implements Runnable{

    private int id;
    private String name;
    public MyTask(int id, String name) {
        this.id = id;
        this.name = name;
    }
    @Override
    public void run() {
        System.out.println("run:" + this.id);
        try {
            Thread.sleep(5000);
        } catch (InterruptedException e) {
            e.printStackTrace();
        }
    }
    public int getId() {
        return id;
    }
    public void setId(int id) {
        this.id = id;
    }
    public String getName() {
        return name;
    }
    public void setName(String name) {
        this.name = name;
    }

}

public class UseThreadPoolExecutor {

    public static void main(String[] args) {
        ThreadPoolExecutor pool = new ThreadPoolExecutor(
                                    1, //coreSize
                                    2, //maxSize
                                    60, 
                                    TimeUnit.SECONDS, 
                                    new ArrayBlockingQueue<Runnable>(3));
        MyTask mt1 = new MyTask(1,"任务1");
        MyTask mt2 = new MyTask(2,"任务2");
        MyTask mt3 = new MyTask(3,"任务3");
        MyTask mt4 = new MyTask(4,"任务4");
        MyTask mt5 = new MyTask(5,"任务5");
        MyTask mt6 = new MyTask(6,"任务6");

        pool.execute(mt1);
        pool.execute(mt2);
        pool.execute(mt3);
        pool.execute(mt4);
        pool.execute(mt5);
        pool.execute(mt6);

        pool.shutdown();
    }
}
public class UseThreadPoolExecutor2 implements Runnable{

    private static AtomicInteger count = new AtomicInteger();

    @Override
    public void run() {
        int num = count.incrementAndGet();
        System.out.println("任务" + num);
        try {
            Thread.sleep(2000);
        } catch (InterruptedException e) {
            e.printStackTrace();
        }
    }

    public static void main(String[] args) throws Exception {
//      BlockingQueue<Runnable> queue = new LinkedBlockingQueue<>();//无界队列
        BlockingQueue<Runnable> queue = new ArrayBlockingQueue<>(10);//有界队列
        ExecutorService executors = new ThreadPoolExecutor(
                            5,
                            10,
                            120L,
                            TimeUnit.SECONDS,
                            queue);
        for (int i = 0; i < 20; i++) {
            executors.execute(new UseThreadPoolExecutor2());
        }
        Thread.sleep(1000);
        System.out.println("队列数量:" + queue.size());
        Thread.sleep(2000);
    }
}

JDK拒绝策略:
- AbortPolicy:直接抛出异常组织系统正常工作。
- CallerRunsPolicy:只要线程池未关闭,该策略直接在调用者线程中,运行当前被丢弃的任务。
- DiscardOidestPolicy:丢弃最老的一个请求,尝试再次提交当前任务。
- DiscardPolicy:丢弃无法处理的任务,不给予任务处理。
- 如果需要自定义拒绝策略可以实现RejectedExecutionHandler接口。一般拒绝策略记录日志,后续对日志解析处理。

/**
 * 自定义拒绝策略
 */
public class MyRejected implements RejectedExecutionHandler{

    @Override
    public void rejectedExecution(Runnable r, ThreadPoolExecutor executor) {
        System.out.println("自定义处理。。");
        System.out.println("当前被拒绝任务" + r.toString());
    }

}

public class UseThreadPoolExecutor {

    public static void main(String[] args) {
        ThreadPoolExecutor pool = new ThreadPoolExecutor(
                                    1, //coreSize
                                    2, //maxSize
                                    60, 
                                    TimeUnit.SECONDS, 
                                    new ArrayBlockingQueue<Runnable>(3),
                                    new MyRejected());
        MyTask mt1 = new MyTask(1,"任务1");
        MyTask mt2 = new MyTask(2,"任务2");
        MyTask mt3 = new MyTask(3,"任务3");
        MyTask mt4 = new MyTask(4,"任务4");
        MyTask mt5 = new MyTask(5,"任务5");
        MyTask mt6 = new MyTask(6,"任务6");

        pool.execute(mt1);
        pool.execute(mt2);
        pool.execute(mt3);
        pool.execute(mt4);
        pool.execute(mt5);
        pool.execute(mt6);

        pool.shutdown();
    }
}
Concurrent工具类
  1. CountDownLacth:经常用于监听某些初始化操作,等初始化执行完毕后,通知主线程继续工作。

    public class UseCountDownLatch {
    
        public static void main(String[] args) {
            final CountDownLatch countDownLatch = new CountDownLatch(2);
            Thread t1 = new Thread(new Runnable() {
    
                @Override
                public void run() {
                    try {
                        System.out.println("t1开始进行。。");
                        countDownLatch.await();
                        System.out.println("t1继续进行。。");
                    } catch (InterruptedException e) {
                        e.printStackTrace();
                    }
                }
            });
    
            Thread t2 = new Thread(new Runnable() {
    
                @Override
                public void run() {
                    try {
                        System.out.println("t2进行初始化。。");
                        Thread.sleep(3000);
                        countDownLatch.countDown();
                        System.out.println("t2初始化完成。。");
                    } catch (InterruptedException e) {
                        e.printStackTrace();
                    }
                }
            });
    
            Thread t3 = new Thread(new Runnable() {
    
                @Override
                public void run() {
                    try {
                        System.out.println("t3进行初始化。。");
                        Thread.sleep(4000);
                        countDownLatch.countDown();
                        System.out.println("t3初始化完成。。");
                    } catch (InterruptedException e) {
                        e.printStackTrace();
                    }
                }
            });
    
            //t2、t3初始化完成后,t1继续进行
            t1.start();
            t2.start();
            t3.start();
        }
    }
  2. CyclicBarrier:所有线程都准备好就都同时运行,只要没一个线程没准备好,就等待。

    public class UseCyclicBarrier {
    
        static class Runner implements Runnable{
            private CyclicBarrier barrier ;
            private String name;
    
            public Runner(CyclicBarrier barrier, String name) {
                this.barrier = barrier;
                this.name = name;
            }
    
            @Override
            public void run() {
                try {
                    Thread.sleep(1000 * (new Random().nextInt(5)));
                    System.out.println(name + "准备就绪!");
                    barrier.await();
                } catch (InterruptedException e) {
                    e.printStackTrace();
                } catch (BrokenBarrierException e) {
                    e.printStackTrace();
                }
                System.out.println(name + "run!");
            }
    
        }
    
        public static void main(String[] args) {
            CyclicBarrier barrier = new CyclicBarrier(3);
            ExecutorService executors = Executors.newFixedThreadPool(3);
            //只要最后一个线程准备就绪,同时进行run
            executors.submit(new Runner(barrier, "张三"));
            executors.submit(new Runner(barrier, "李四"));
            executors.submit(new Runner(barrier, "王五"));
            executors.shutdown();
        }
    }
  3. Future:非常适合在处理很耗时很长的业务逻辑时使用,可以有效的减少系统的响应时间,提高系统的吞吐量。

    public class UseFuture implements Callable<String>{
    
        private String queryStr;
    
        public UseFuture(String queryStr) {
            this.queryStr = queryStr;
        }
    
        /**
         * 真实的业务逻辑
         */
        @Override
        public String call() throws Exception {
            //模拟业务处理耗时
            Thread.sleep(1000);
            String result = queryStr + ",处理完成";
            return result;
        }
    
        public static void main(String[] args) throws Exception {
            String queryStr = "query";
            //构造FutureTask,并且传入需要真正进行业务逻辑处理的类,该类实现了Callable接口的类
            FutureTask<String> future1 = new FutureTask<>(new UseFuture(queryStr));
            FutureTask<String> future2 = new FutureTask<>(new UseFuture(queryStr));
            //构建固定线程的线程池
            ExecutorService executorService = Executors.newFixedThreadPool(2);
            Future<?> f1 = executorService.submit(future1);
            Future<?> f2 = executorService.submit(future2);
            System.out.println("请求完毕!");
    
            try {
                System.out.println("主程序执行其他业务。。");
                Thread.sleep(1000);
            } catch (InterruptedException e) {
                e.printStackTrace();
            }
            //调用获取数据方法,如果call()没有执行完成,依然进行等待
            System.out.println("数据结果:" + future1.get());
            System.out.println("数据结果:" + future2.get());
            System.out.println("主线程继续。。");
            executorService.shutdown();
        }
    
    }
  4. Semaphore:信号量非常适合高并发访问,在新系统在上线之前,对系统的访问量进行评估。
    相关概念:

    • PV(page view)网站的总访问量,页面浏览量或点击量,用户每刷新一次会被记录一次。
    • UV(unique visitor)访问网站的一台电脑客户端为一个访客,一般24小时之内相同ip的客户端只记录一次。
    • QPS(query per second)每秒查询数,qps很大程度上代表了系统业务上的繁忙程度,每次请求的请求的背后,可能对应着多次磁盘I/O,多次网络请求,多个cpu时间片等。我们通过qps可以非常直观的了解当前系统情况,一旦当前qps超过所设定的预警阈值,可以可以考虑对集群进行扩容,以免压力过大导致宕机。
    • RT(response time)即请求的响应时间,非常关键,直接说明前端用户的体验。
    • 峰值qps=(总PV * 80%)/(60 * 60 * 24 * 24%),实际要要参考业务情况
    public class UseSemaphore {
    
        public static void main(String[] args) {
            //线程池
            ExecutorService executorService = Executors.newCachedThreadPool();
            //控制只能5个线程同时访问
            final Semaphore semaphore = new Semaphore(5);
            //模拟20个客户端访问
            for (int i = 0; i < 20; i++) {
                final int NO = i;
                Runnable run = new Runnable() {
                    public void run() {
                        try {
                            //获取许可
                            semaphore.acquire();
                            System.out.println(NO + "号正在访问!");
                            //模拟业务处理
                            Thread.sleep((long) (Math.random()*10000));
                            //释放权限
                            semaphore.release();
                        } catch (InterruptedException e) {
                            e.printStackTrace();
                        }
    
                    }
                };
                executorService.submit(run);
            }
    
            try {
                Thread.sleep(10);
            } catch (InterruptedException e) {
                e.printStackTrace();
            }
            executorService.shutdown();
        }
    }
    

Lock对象,主要有重入锁和读写锁等,比synchronized更为强大,有嗅探锁定、多路分支等功能。
ReentrantLock(重入锁),在需要进行同步的代码部分加上锁定,最后一定要释放,不然其他锁无法进入。

public class UseReentrantLock {

    private Lock lock = new ReentrantLock();
    public void method1(){
        try {
            lock.lock();
            System.out.println("当前线程:" + Thread.currentThread().getName() + "进入method1..");
            Thread.sleep(1000);
            System.out.println("当前线程:" + Thread.currentThread().getName() + "退出method1..");
        } catch (InterruptedException e) {
            e.printStackTrace();
        } finally {
            lock.unlock();//释放锁
        }
    }
    public void method2(){
        try {
            lock.lock();
            System.out.println("当前线程:" + Thread.currentThread().getName() + "进入method2..");
            Thread.sleep(1000);
            System.out.println("当前线程:" + Thread.currentThread().getName() + "退出method2..");
        } catch (InterruptedException e) {
            e.printStackTrace();
        } finally {
            lock.unlock();//释放锁
        }
    }

    public static void main(String[] args) {
        final UseReentrantLock rl = new UseReentrantLock();
        Thread t1 = new Thread(new Runnable() {

            @Override
            public void run() {
                rl.method1();
                rl.method2();
            }
        },"t1");
        t1.start();
    }
}

锁的等待和通知:
synchronized,多线程间进行协作工作需要Object的wait()和notify()、notifyAll()方法进行配合工作。
使用Lock的时候,可以使用Condition,这个Condition是针对具体某一把锁,只有在锁的基础上才会产生Condition。
公平锁和非公平锁:Lock lock = new ReentrantLock(booleanisFair);
lock用法:
tryLock():尝试获得锁,获得结果用true/false返回。
tryLock():在给定的时间内尝试获得锁,获得结果用true/false返回。
isFair():是否是公平锁。
isLocked():是否锁定。
getHoldCount():查询当前线程保持此锁的个数,也就是调用lock()的次数。
lockInterruptibly():优先响应中断的锁。
getQueueLength():返回正在等待获取此锁定的线程数。
getWaitQueueLength():返回等待与锁定相关的给定条件Condition的线程数。
hasQueuedThread(Thread thread):查询指定的线程是否正在等待此锁。
hasQueuedThreads():查询是否有线程正在等待此锁。
hasWaiters():查询是否有线程正在等待与此锁有关的condition条件。

ReentrantReadWriteLock(读写锁),核心是实现读写分离的锁,在高并发访问下,尤其是读多写少的情况下,性能要远高于重入锁。其本质是分成两个锁,即读锁、写锁。在读锁下,多个线程可以并发的进行访问,但是在写锁的时候,只能一个一个的顺序访问。
总结:读读共享,写写互斥,读写互斥。

public class UseReentrantReadWriteLock {
    //读写锁
    private ReentrantReadWriteLock lock = new ReentrantReadWriteLock();
    //读锁
    private ReadLock readLock = lock.readLock();
    //写锁
    private WriteLock writeLock = lock.writeLock();

    public void read(){
        try {
            readLock.lock();
            System.out.println("当前线程:" + Thread.currentThread().getName() + "进入...");
            Thread.sleep(3000);
            System.out.println("当前线程:" + Thread.currentThread().getName() + "退出...");
        } catch (InterruptedException e) {
            e.printStackTrace();
        } finally {
            readLock.unlock();
        }
    }

    public void write(){
        try {
            writeLock.lock();
            System.out.println("当前线程:" + Thread.currentThread().getName() + "进入...");
            Thread.sleep(3000);
            System.out.println("当前线程:" + Thread.currentThread().getName() + "退出...");
        } catch (InterruptedException e) {
            e.printStackTrace();
        } finally {
            writeLock.unlock();
        }
    }

    public static void main(String[] args) {
        final UseReentrantReadWriteLock lock = new UseReentrantReadWriteLock();
        Thread t1 = new Thread(new Runnable() {
            public void run() {
                lock.read();
            }
        },"t1");
        Thread t2 = new Thread(new Runnable() {
            public void run() {
                lock.read();
            }
        },"t2");
        Thread t3 = new Thread(new Runnable() {
            public void run() {
                lock.write();
            }
        },"t3");

//      t1.start();
//      t2.start();

        t1.start();
        t3.start();
    }
}
  • 0
    点赞
  • 3
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值