龙空技术网

Java多线程之线程池、前后台线程、发令枪、信号量、Future

顽石九变 162

前言:

而今我们对“java子线程主线程”大约比较讲究,大家都想要学习一些“java子线程主线程”的相关内容。那么小编同时在网上网罗了一些有关“java子线程主线程””的相关内容,希望兄弟们能喜欢,姐妹们快快来学习一下吧!

一、Java线程池使用

创建线程是一个耗资源的过程。创建大连线程会导致系统内存溢出。

所以为了更合理使用线程,通常我们使用线程池在解决

1、简单使用

jdk提供一个工具类Executors可以方便创建线程池

//核心线程0,最新线程Integer.MAX_VALUE,空闲线程存活时间60sExecutorService threadPool1=Executors.newCachedThreadPool();//核心线程10,最大线程10,空闲线程存活时间0ExecutorService threadPool2=Executors.newFixedThreadPool(10);//核心线程1,最大线程1,空闲线程存活时间0ExecutorService threadPool3=Executors.newSingleThreadExecutor();

2、自定义线程池

自定义线程池重点在于自定义以下几个参数

线程池名称初始值最大值空闲线程保留时间

1)使用jdk原生代码

ThreadFactory namedThreadFactory = new ThreadFactory() {    private final AtomicInteger threadNumber = new AtomicInteger(1);    @Override    public Thread newThread(Runnable r) {        Thread t = new Thread(Thread.currentThread().getThreadGroup(), r,                "threadPoolDemo_" + threadNumber.getAndIncrement(),                0);        if (t.isDaemon())            t.setDaemon(false);        if (t.getPriority() != Thread.NORM_PRIORITY)            t.setPriority(Thread.NORM_PRIORITY);        return t;    }};ExecutorService threadPool = new ThreadPoolExecutor(0, 100, 60L, TimeUnit.MILLISECONDS, new SynchronousQueue<>(),        namedThreadFactory);

上面这段代码表示最大只能100个线程,超过就会拒绝。 正常情况,如果使用SynchronousQueue,那么maximumPoolSize就要设置到足够大

ThreadFactory namedThreadFactory = new ThreadFactory() {    private final AtomicInteger threadNumber = new AtomicInteger(1);    @Override    public Thread newThread(Runnable r) {        Thread t = new Thread(Thread.currentThread().getThreadGroup(), r,                "threadPoolDemo_" + threadNumber.getAndIncrement(),                0);        if (t.isDaemon())            t.setDaemon(false);        if (t.getPriority() != Thread.NORM_PRIORITY)            t.setPriority(Thread.NORM_PRIORITY);        return t;    }};ExecutorService threadPool = new ThreadPoolExecutor(10, 10, 60L, TimeUnit.MILLISECONDS, new LinkedBlockingQueue<>(),        namedThreadFactory);

上面这段代码表示最大只能10个线程,超过部分会放在队列中等待执行

2)使用google工具类

import com.google.common.util.concurrent.ThreadFactoryBuilder;public void initThreadPool() {    ThreadFactory namedThreadFactory = new ThreadFactoryBuilder().setNameFormat("loopPrecreate-pool-%d").build();    ExecutorService    threadPool = new ThreadPoolExecutor(5, 50, 60L, TimeUnit.MILLISECONDS, new SynchronousQueue<Runnable>(),                namedThreadFactory);}

上面这段代码表示最大只能50个线程,超过就会拒绝

3)注意:

如果使用SynchronousQueue,那么maximumPoolSize就要设置到足够大,并发请求时maximumPoolSize会逐渐增加到maximumPoolSize,超过max会拒绝如果使用LinkedBlockingQueue,那么corePoolSize和maximumPoolSize要设置成一样,maximumPoolSize不会自动扩展

3、使用示例

并发100个线程池

int N = 100;CountDownLatch latch = new CountDownLatch(N);CountDownLatch lock = new CountDownLatch(1);for (int i = 0; i < N; i++) { Thread.sleep(100L); threadPool.submit(()->{     try {         lock.await();         System.out.println("---> start " + Thread.currentThread().getName());         Thread.sleep(5000L);         System.out.println("---> end " + Thread.currentThread().getName());         latch.countDown();     } catch (Exception e) {         e.printStackTrace();     } });}lock.countDown();latch.await();System.out.println("--end all ---");

二、CountDownLatch 介绍

类似发令枪,CountDownLatch的构造函数可以传入一个数字,代表倒计时多少个数。

主要方法:

countDown:每调用一次countDown(),倒计时就会减少1,await:当倒计时到0时,await()方法就会停止阻塞

1、示例代码

public static void main(String[] args) throws InterruptedException {        int N = 10;        CountDownLatch latch = new CountDownLatch(N);        CountDownLatch lock = new CountDownLatch(1);        for (int i = 0; i < N; i++) {            Thread.sleep(100L);            new Thread(() -> {                try {                    lock.await();                    System.out.println("---> start " + Thread.currentThread().getName());                    Thread.sleep(1000L);                    System.out.println("---> end " + Thread.currentThread().getName());                    latch.countDown();                } catch (Exception e) {                    e.printStackTrace();                }            }).start();        }        lock.countDown();        latch.await();    }
三、什么是Future

Future是一个未来对象,里面保存这线程处理结果,它像一个提货凭证,拿着它你可以随时去提取结果

1、什么时候使用

在两种情况下,离开Future几乎很难办。

一种情况是拆分订单,比如你的应用收到一个批量订单,此时如果要求最快的处理订单,那么需要并发处理,并发的结果如何收集,这个问题如果自己去编程将非常繁琐,此时可以使用CompletionService解决这个问题。CompletionService将Future收集到一个队列里,可以按结果处理完成的先后顺序进队。另外一种情况是,如果你需要并发去查询一些东西(比如爬虫),并发查询只要有一个结果返回,你就认为查询到了,并且结束查询,这时也需要用CompletionService和Future来解决。

使用过程既可以用CompletionService,也可以自己维护一个list将Future添加进去。

区别是:CompletionService是先完成的先返回,自己维护的list就是按顺序一个个取值

示例代码:

public class FutureDemo {    private static class Task implements Callable<String> {        @Override        public String call() throws Exception {            // 模拟真实事务的处理过程,这个过程是非常耗时的。            Thread.sleep(5000);            return "call return " + Thread.currentThread().getName();        }    }    public static void main(String[] args) throws Exception {        FutureDemo demo =new FutureDemo();        demo.testComplete();//        demo.testFutrue();    }    private void testFutrue() throws Exception{        List<Future<String>> futures = new ArrayList<Future<String>>();        ExecutorService executorService = Executors.newCachedThreadPool();        System.out.println("已经提交资源申请");        for (int i = 0; i < 10; i++) {            futures.add(executorService.submit(new Task()));        }        for (Future<String> future : futures) {            if (!future.isDone()) {                System.out.println("资源还没有准备好");            }            System.out.println(future.get());        }        executorService.shutdown();    }    private void testComplete() throws Exception {        ExecutorService executorService = Executors.newCachedThreadPool();        CompletionService<String> ecs = new ExecutorCompletionService<>(executorService);        System.out.println("已经提交资源申请");        int lenth =10;        for (int i = 0; i < lenth; i++) {            ecs.submit(new Task());        }        //CompletionService会按处理完后顺序返回结果        List<String> res =new ArrayList<>();        for(int i = 0;i<lenth;i++ ){            Future<String> f = ecs.take();            System.out.println(f.get());        }        executorService.shutdown();    }}
四、Java前台线程和后台线程

1、前台线程

定义:普通线程又可以称为用户线程,只完成用户自己想要完成的任务,不提供公共服务。

通过new Thread()产生的线程默认都是前台线程。 前台线程在程序执行完成后,才会自己退出,不会随着主线程的退出而退出。假设main线程已经退出了,但是该线程还未执行完成,还会继续执行直到线程自己退出。

java线程池启动的是前台线程

2、后台线程

定义:指在程序运行的时候在后台提供一种通用服务的线程,并且这种线程并不属于程序中不可或缺的部分。

后台线程又称守护线程,在前台线程全部退出后,后台线程会随着一起退出,并且不会执行未执行的finally语句。

在线程start之前通过setDaemon(true)方法可以将当前线程设置为后台线程

Thread t1 =new Thread(()->{    try{        System.out.println(Thread.currentThread().getName());        //如果前台线程全部退出,后台线程会一起退出,不会一直休眠下去        Thread.sleep(10000);    } catch (Exception e) {        e.printStackTrace();    } finally {        System.out.println(Thread.currentThread().getName()+"out");    }});t1.setDaemon(true);//设置为后台线程t1.start();

3、使用场景

jvm的垃圾回收器其实就是一个后台线程和主业务无关的服务线程五、信号量 Semaphore 重点语法availablePermits():判断是否有权限acquire():获得一个信号,阻塞线程,可被中断release():释放一个信号acquireUninterruptibly():获得一个信号,阻塞线程,不可被中断

代码示例

1、每次只允许2个线程执行

public class SemaphoreThread {    public static void main(String[] args) {        Semaphore semaphore = new Semaphore(2);        // 建立一个缓存线程池        ExecutorService es = Executors.newCachedThreadPool();        for (int i = 0; i < 10; i++) {            es.submit(new Thread(() -> {                String name = Thread.currentThread().getName();                try {                    semaphore.acquire();                    System.out.println(LocalDateTime.now().toLocalTime()+"  "+name + "开始执行");                    Thread.sleep(1000);                    semaphore.release();                } catch (InterruptedException e) {                    e.printStackTrace();                }            }));        }        // 关闭线程池        es.shutdown();    }}

输出:

13:42:11.763  pool-1-thread-2开始执行13:42:11.763  pool-1-thread-1开始执行13:42:12.764  pool-1-thread-3开始执行13:42:12.764  pool-1-thread-5开始执行13:42:13.764  pool-1-thread-6开始执行13:42:13.764  pool-1-thread-4开始执行13:42:14.764  pool-1-thread-9开始执行13:42:14.764  pool-1-thread-10开始执行13:42:15.765  pool-1-thread-7开始执行13:42:15.765  pool-1-thread-8开始执行

从输出日志可以看出,每秒只有2个线程在执行

2、控制两个线程执行顺序

public class SemaphoreThread {    public static void main(String[] args) {        // 设置公平锁        Semaphore semaphore = new Semaphore(1,true);        // 建立一个缓存线程池        ExecutorService es = Executors.newCachedThreadPool();        es.submit( new Thread(() -> {            String name = Thread.currentThread().getName();            for (int i = 0; i < 10; i++) {                try {                    semaphore.acquire();                    System.out.println(LocalDateTime.now().toLocalTime()+"  "+name + "正在工作");                    Thread.sleep(1000);                    semaphore.release();                } catch (InterruptedException e) {                    e.printStackTrace();                }            }        }));        es.submit( new Thread(() -> {            String name = Thread.currentThread().getName();            for (int i = 0; i < 10; i++) {                try {                    semaphore.acquire();                    System.out.println(LocalDateTime.now().toLocalTime()+"  "+name + "正在工作");                    Thread.sleep(100);                    semaphore.release();                } catch (InterruptedException e) {                    e.printStackTrace();                }            }        }));        // 关闭线程池        es.shutdown();    }}

输出:

13:45:11.661  pool-1-thread-1正在工作13:45:12.662  pool-1-thread-2正在工作13:45:12.763  pool-1-thread-1正在工作13:45:13.763  pool-1-thread-2正在工作13:45:13.863  pool-1-thread-1正在工作13:45:14.864  pool-1-thread-2正在工作13:45:14.965  pool-1-thread-1正在工作13:45:15.965  pool-1-thread-2正在工作13:45:16.066  pool-1-thread-1正在工作13:45:17.067  pool-1-thread-2正在工作13:45:17.167  pool-1-thread-1正在工作13:45:18.168  pool-1-thread-2正在工作13:45:18.268  pool-1-thread-1正在工作13:45:19.268  pool-1-thread-2正在工作13:45:19.368  pool-1-thread-1正在工作13:45:20.369  pool-1-thread-2正在工作13:45:20.470  pool-1-thread-1正在工作13:45:21.470  pool-1-thread-2正在工作13:45:21.570  pool-1-thread-1正在工作13:45:22.571  pool-1-thread-2正在工作

从输出日志可以看出,两个线程交替执行,这里的关键是设置了信号量公平锁

标签: #java子线程主线程