并发工具类和线程池

工具类

CountDownLatch

利用它可以实现类似计数器的功能。比如有一个任务A,它要等待其他4个任务执行完毕之后才能执行,此时就可以利用CountDownLatch来实现这种功能了。

复制代码
package com.yjc.juc;

import java.util.concurrent.CountDownLatch;

public class CountDownLatchDemo {
    public static void main(String[] args) throws InterruptedException {
        System.out.println("主线程启动---->等待子线程执行完毕");
        //代表等待两个线程执行完主线程才继续执行
        CountDownLatch countDownLatch=new CountDownLatch(2);
        new Thread(() ->
        {
            System.out.println("第一个子线程" + Thread.currentThread().getName() + "正在执行");
            countDownLatch.countDown();
            System.out.println("第一个子线程" + Thread.currentThread().getName() + "执行完毕");
        }).start();
        new Thread(() ->
        {
            System.out.println("第二个子线程" + Thread.currentThread().getName() + "正在执行");
            countDownLatch.countDown();
            System.out.println("第二个子线程" + Thread.currentThread().getName() + "执行完毕");
        }).start();
        //使主线程进入等待状态
        countDownLatch.await();
        System.out.println("子线程执行完毕,主线程开始执行");

    }
}
复制代码

执行结果

CyclicBarrier

CyclicBarrier初始化时规定一个数目,然后计算调用了CyclicBarrier.await()进入等待的线程数。当线程数达到了这个数目时,所有进入等待状态的线程被唤醒并继续。 

CyclicBarrier就象它名字的意思一样,可看成是个障碍, 所有的线程必须到齐后才能一起通过这个障碍。 

CyclicBarrier初始时还可带一个Runnable的参数, 此Runnable任务在CyclicBarrier的数目达到后,所有其它线程被唤醒前被执行。

复制代码
package com.yjc.juc;

import java.util.concurrent.BrokenBarrierException;
import java.util.concurrent.CyclicBarrier;

public class CyclicBarrierDemo  {
   static  CyclicBarrier cyclicBarrier=new CyclicBarrier(7, ()-> {
       System.out.println("召唤神龙!");
   });

    public static void main(String[] args){

        for (int i = 1; i <=7 ; i++) {
            final int count=i;
            new Thread(()->{
                try {
                    System.out.println(Thread.currentThread().getName()+":获得第"+count+"龙珠");
                    cyclicBarrier.await();
                    System.out.println(Thread.currentThread().getName()+"开始召唤神龙");
                } catch (InterruptedException e) {
                    e.printStackTrace();
                } catch (BrokenBarrierException e) {
                    e.printStackTrace();
                }
            }).start();
        }

    }
}
复制代码

执行结果

 Semaphore

Semaphore是一种基于计数的信号量。它可以设定一个阈值,基于此,多个线程竞争获取许可信号,做自己的申请后归还,超过阈值后,线程申请许可信号将会被阻塞。Semaphore可以用来构建一些对象池,资源池之类的,比如数据库连接池,我们也可以创建计数为1的Semaphore,将其作为一种类似互斥锁的机制,这也叫二元信号量,表示两种互斥状态。

复制代码
package com.yjc.juc;

import java.util.concurrent.Semaphore;

public class SemaphoreDemo {
    public static void main(String[] args) {
        //代表现在一共只有三个资源
        Semaphore semaphore=new Semaphore(3);
        //创建十条线程进行资源抢夺
        for ( int i = 0; i <10 ; i++) {
            final int count =i;
            new Thread(()->{
                try {
                    semaphore.acquire();//请求资源
                } catch (InterruptedException e) {
                    e.printStackTrace();
                }
                System.out.println("第"+(count+1)+"条线程抢到了资源");
                try {
                    Thread.sleep(3000);
                } catch (InterruptedException e) {
                    e.printStackTrace();
                }
                System.out.println("第"+(count+1)+"条线程释放资源");
                //释放资源
                semaphore.release();
            }).start();
        }
    }
}
复制代码

执行结果

 Exchanger

用于两个工作线程之间交换数据的封装工具类,简单说就是一个线程在完成一定的事务后想与另一个线程交换数据,则第一个先拿出数据的线程会一直等待第二个线程,直到第二个线程拿着数据到来时才能彼此交换对应数据

复制代码
package com.yjc.juc;

import java.util.concurrent.Exchanger;

public class ExchangerDemo {
    public static void main(String[] args) {
        Exchanger exchanger = new Exchanger();
        new Thread(() -> {
            for (int i = 1; i < 10; i++) {
                try {
                    Thread.sleep(800);
                    int data=i;
                    System.out.println(i+"\t"+Thread.currentThread().getName()+"交换前:"+data);
                    data = (int)exchanger.exchange(data);
                    System.out.println(i+"\t"+Thread.currentThread().getName()+"交换后:"+data);
                  
                } catch (Exception e) {
                    e.printStackTrace();
                }
            }
        }).start();

        new Thread(() -> {
            int count=0;
            while (true) {
                ++count;
                try {
                    Thread.sleep(800);
                    int data = 0;
                    System.out.println(count+"\t"+Thread.currentThread().getName() + "交换前:" + data);
                    data = (int) exchanger.exchange(data);
                    System.out.println(count+"\t"+Thread.currentThread().getName() + "交换后:" + data);


                } catch (Exception e) {
                e.printStackTrace();
                }
            }
        }).start();
    }
}
复制代码

执行结果

 线程交换的线程数需要是二的倍数,要不然会出现线程阻塞的状态,也可以通过设置响应时间,一旦在规定时间内没有完成数据的交互,那么就抛出异常

线程池

Java中的线程池是运用场景最多的并发框架,几乎所有需要异步或并发执行任务的程序
都可以使用线程池。在开发过程中,合理地使用线程池能够带来3个好处。

第一:降低资源消耗。通过重复利用已创建的线程降低线程创建和销毁造成的消耗。
第二:提高响应速度。当任务到达时,任务可以不需要等到线程创建就能立即执行。
第三:提高线程的可管理性。线程是稀缺资源,如果无限制地创建,不仅会消耗系统资源,
还会降低系统的稳定性,使用线程池可以进行统一分配、调优和监控。但是,要做到合理利用线程池,必须对其实现原理了如指掌。

ThreadPoolExecutor

Executor框架的最顶层实现是ThreadPoolExecutor类,Executors工厂类中提供的newScheduledThreadPool、newFixedThreadPool、newCachedThreadPool方法其实也只是ThreadPoolExecutor的构造函数参数不同而已。通过传入不同的参数,就可以构造出适用于不同应用场景下的线程池,那么它的底层原理是怎样实现的呢,这篇就来介绍下ThreadPoolExecutor线程池的运行过程。

  • corePoolSize: 核心池的大小。 当有任务来之后,就会创建一个线程去执行任务,当线程池中的线程数目达到corePoolSize后,就会把到达的任务放到缓存队列当中
  • maximumPoolSize: 线程池最大线程数,它表示在线程池中最多能创建多少个线程;
  • keepAliveTime: 表示线程没有任务执行时最多保持多久时间会终止。
  • unit: 参数keepAliveTime的时间单位,有7种取值

newCachedThreadPool

创建一个可缓存线程池,如果线程池长度超过处理需要,可灵活回收空闲线程,若无可回收,则新建线程。示例代码如下:

复制代码
package com.yjc.juc;

import java.util.concurrent.ExecutorService;
import java.util.concurrent.Executors;

public class CachedThreadPoolDemo {
    public static void main(String[] args) {
        //创建一个可缓存线程如果线程池长度超过处理需要,可灵活回收空闲线程,若无可回收,则新建线程
        //线程池为无限大,当执行第二个任务时第一个任务已经完成,会复用执行第一个任务的线程,而不用每次新建线程
        ExecutorService  executorService= Executors.newCachedThreadPool();
        for (int i = 0; i <100 ; i++) {
            final int count=i;
            executorService.execute(()->{
                try {
                    Thread.sleep(1);
                } catch (InterruptedException e) {
                    e.printStackTrace();
                }
                System.out.println(Thread.currentThread().getName()+"--->i:"+count);
            });

        }
    }
}
复制代码

newFixedThreadPool

创建一个定长线程池,可控制线程最大并发数,超出的线程会在队列中等待
复制代码
package com.yjc.juc;

import java.util.concurrent.ExecutorService;
import java.util.concurrent.Executors;

public class FixedThreadPoolDemo {
    public static void main(String[] args) {
        //创建一个定长线程池,可控制线程最大并发数,超出的线程会在队列中等待
        ExecutorService executorService= Executors.newFixedThreadPool(5);
        for (int i = 0; i <100 ; i++) {
            final int count=i;
            executorService.execute(()->{
                try {
                    Thread.sleep(300);
                } catch (InterruptedException e) {
                    e.printStackTrace();
                }
                System.out.println(Thread.currentThread().getName()+"--->i:"+count);
            });

        }
    }
}
复制代码

 newScheduledThreadPool

复制代码
package com.yjc.juc;


import java.util.concurrent.Executors;
import java.util.concurrent.ScheduledExecutorService;
import java.util.concurrent.TimeUnit;

public class ScheduledThreadPoolDemo {
    public static void main(String[] args) {
        //创建一个定长线程池,支持定时及周期性任务执行
        ScheduledExecutorService executorService= Executors.newScheduledThreadPool(5);
        for (int i = 0; i <100 ; i++) {
            final int count=i;
            executorService.schedule(()->{
                System.out.println(Thread.currentThread().getName()+"--->i:"+count);
                //延迟三秒开始执行
            },3, TimeUnit.SECONDS);

        }
    }
}
复制代码

延迟三秒才开始执行

newSingleThreadExecutor

创建一个单线程化的线程池,它只会用唯一的工作线程来执行任务,保证所有任务按照指定顺序(FIFO, LIFO, 优先级)执行。示例代码如下:

复制代码
package com.yjc.juc;


import java.util.concurrent.ExecutorService;
import java.util.concurrent.Executors;

public class SingleThreadExecutorDemo {
    public static void main(String[] args) {
        //创建一个单线程化的线程池,它只会用唯一的工作线程来执行任务,保证所有任务按照指定顺序(FIFO, LIFO, 优先级)执行
        ExecutorService executorService= Executors.newSingleThreadExecutor();
        for (int i = 0; i <100 ; i++) {
            final int count=i;
            executorService.execute(()->{
                try {
                    Thread.sleep(300);
                } catch (InterruptedException e) {
                    e.printStackTrace();

                }
                System.out.println(Thread.currentThread().getName()+"--->i:"+count);
            });

        }
    }}
复制代码

线程池原理剖析

提交一个任务到线程池中,线程池的处理流程如下:

1、判断线程池里的核心线程是否都在执行任务,如果不是(核心线程空闲或者还有核心线程没有被创建)则创建一个新的工作线程来执行任务。如果核心线程都在执行任务,则进入下个流程。

2、线程池判断工作队列是否已满,如果工作队列没有满,则将新提交的任务存储在这个工作队列里。如果工作队列满了,则进入下个流程。

3、判断线程池里的线程是否都处于工作状态,如果没有,则创建一个新的工作线程来执行任务。如果已经满了,则交给饱和策略来处理这个任务。

合理配置线程池

CPU密集型时,任务可以少配置线程数,大概和机器的cpu核数相当,这样可以使得每个线程都在执行任务

IO密集型时,大部分线程都阻塞,故需要多配置线程数,2*cpu核数

CountDownLatch

利用它可以实现类似计数器的功能。比如有一个任务A,它要等待其他4个任务执行完毕之后才能执行,此时就可以利用CountDownLatch来实现这种功能了。

复制代码
package com.yjc.juc;

import java.util.concurrent.CountDownLatch;

public class CountDownLatchDemo {
    public static void main(String[] args) throws InterruptedException {
        System.out.println("主线程启动---->等待子线程执行完毕");
        //代表等待两个线程执行完主线程才继续执行
        CountDownLatch countDownLatch=new CountDownLatch(2);
        new Thread(() ->
        {
            System.out.println("第一个子线程" + Thread.currentThread().getName() + "正在执行");
            countDownLatch.countDown();
            System.out.println("第一个子线程" + Thread.currentThread().getName() + "执行完毕");
        }).start();
        new Thread(() ->
        {
            System.out.println("第二个子线程" + Thread.currentThread().getName() + "正在执行");
            countDownLatch.countDown();
            System.out.println("第二个子线程" + Thread.currentThread().getName() + "执行完毕");
        }).start();
        //使主线程进入等待状态
        countDownLatch.await();
        System.out.println("子线程执行完毕,主线程开始执行");

    }
}
复制代码

执行结果

CyclicBarrier

CyclicBarrier初始化时规定一个数目,然后计算调用了CyclicBarrier.await()进入等待的线程数。当线程数达到了这个数目时,所有进入等待状态的线程被唤醒并继续。 

CyclicBarrier就象它名字的意思一样,可看成是个障碍, 所有的线程必须到齐后才能一起通过这个障碍。 

CyclicBarrier初始时还可带一个Runnable的参数, 此Runnable任务在CyclicBarrier的数目达到后,所有其它线程被唤醒前被执行。

复制代码
package com.yjc.juc;

import java.util.concurrent.BrokenBarrierException;
import java.util.concurrent.CyclicBarrier;

public class CyclicBarrierDemo  {
   static  CyclicBarrier cyclicBarrier=new CyclicBarrier(7, ()-> {
       System.out.println("召唤神龙!");
   });

    public static void main(String[] args){

        for (int i = 1; i <=7 ; i++) {
            final int count=i;
            new Thread(()->{
                try {
                    System.out.println(Thread.currentThread().getName()+":获得第"+count+"龙珠");
                    cyclicBarrier.await();
                    System.out.println(Thread.currentThread().getName()+"开始召唤神龙");
                } catch (InterruptedException e) {
                    e.printStackTrace();
                } catch (BrokenBarrierException e) {
                    e.printStackTrace();
                }
            }).start();
        }

    }
}
复制代码

执行结果

 Semaphore

Semaphore是一种基于计数的信号量。它可以设定一个阈值,基于此,多个线程竞争获取许可信号,做自己的申请后归还,超过阈值后,线程申请许可信号将会被阻塞。Semaphore可以用来构建一些对象池,资源池之类的,比如数据库连接池,我们也可以创建计数为1的Semaphore,将其作为一种类似互斥锁的机制,这也叫二元信号量,表示两种互斥状态。

复制代码
package com.yjc.juc;

import java.util.concurrent.Semaphore;

public class SemaphoreDemo {
    public static void main(String[] args) {
        //代表现在一共只有三个资源
        Semaphore semaphore=new Semaphore(3);
        //创建十条线程进行资源抢夺
        for ( int i = 0; i <10 ; i++) {
            final int count =i;
            new Thread(()->{
                try {
                    semaphore.acquire();//请求资源
                } catch (InterruptedException e) {
                    e.printStackTrace();
                }
                System.out.println("第"+(count+1)+"条线程抢到了资源");
                try {
                    Thread.sleep(3000);
                } catch (InterruptedException e) {
                    e.printStackTrace();
                }
                System.out.println("第"+(count+1)+"条线程释放资源");
                //释放资源
                semaphore.release();
            }).start();
        }
    }
}
复制代码

执行结果

 Exchanger

用于两个工作线程之间交换数据的封装工具类,简单说就是一个线程在完成一定的事务后想与另一个线程交换数据,则第一个先拿出数据的线程会一直等待第二个线程,直到第二个线程拿着数据到来时才能彼此交换对应数据

复制代码
package com.yjc.juc;

import java.util.concurrent.Exchanger;

public class ExchangerDemo {
    public static void main(String[] args) {
        Exchanger exchanger = new Exchanger();
        new Thread(() -> {
            for (int i = 1; i < 10; i++) {
                try {
                    Thread.sleep(800);
                    int data=i;
                    System.out.println(i+"\t"+Thread.currentThread().getName()+"交换前:"+data);
                    data = (int)exchanger.exchange(data);
                    System.out.println(i+"\t"+Thread.currentThread().getName()+"交换后:"+data);
                  
                } catch (Exception e) {
                    e.printStackTrace();
                }
            }
        }).start();

        new Thread(() -> {
            int count=0;
            while (true) {
                ++count;
                try {
                    Thread.sleep(800);
                    int data = 0;
                    System.out.println(count+"\t"+Thread.currentThread().getName() + "交换前:" + data);
                    data = (int) exchanger.exchange(data);
                    System.out.println(count+"\t"+Thread.currentThread().getName() + "交换后:" + data);


                } catch (Exception e) {
                e.printStackTrace();
                }
            }
        }).start();
    }
}
复制代码

执行结果

 线程交换的线程数需要是二的倍数,要不然会出现线程阻塞的状态,也可以通过设置响应时间,一旦在规定时间内没有完成数据的交互,那么就抛出异常

线程池

Java中的线程池是运用场景最多的并发框架,几乎所有需要异步或并发执行任务的程序
都可以使用线程池。在开发过程中,合理地使用线程池能够带来3个好处。

第一:降低资源消耗。通过重复利用已创建的线程降低线程创建和销毁造成的消耗。
第二:提高响应速度。当任务到达时,任务可以不需要等到线程创建就能立即执行。
第三:提高线程的可管理性。线程是稀缺资源,如果无限制地创建,不仅会消耗系统资源,
还会降低系统的稳定性,使用线程池可以进行统一分配、调优和监控。但是,要做到合理利用线程池,必须对其实现原理了如指掌。

ThreadPoolExecutor

Executor框架的最顶层实现是ThreadPoolExecutor类,Executors工厂类中提供的newScheduledThreadPool、newFixedThreadPool、newCachedThreadPool方法其实也只是ThreadPoolExecutor的构造函数参数不同而已。通过传入不同的参数,就可以构造出适用于不同应用场景下的线程池,那么它的底层原理是怎样实现的呢,这篇就来介绍下ThreadPoolExecutor线程池的运行过程。

  • corePoolSize: 核心池的大小。 当有任务来之后,就会创建一个线程去执行任务,当线程池中的线程数目达到corePoolSize后,就会把到达的任务放到缓存队列当中
  • maximumPoolSize: 线程池最大线程数,它表示在线程池中最多能创建多少个线程;
  • keepAliveTime: 表示线程没有任务执行时最多保持多久时间会终止。
  • unit: 参数keepAliveTime的时间单位,有7种取值

newCachedThreadPool

创建一个可缓存线程池,如果线程池长度超过处理需要,可灵活回收空闲线程,若无可回收,则新建线程。示例代码如下:

复制代码
package com.yjc.juc;

import java.util.concurrent.ExecutorService;
import java.util.concurrent.Executors;

public class CachedThreadPoolDemo {
    public static void main(String[] args) {
        //创建一个可缓存线程如果线程池长度超过处理需要,可灵活回收空闲线程,若无可回收,则新建线程
        //线程池为无限大,当执行第二个任务时第一个任务已经完成,会复用执行第一个任务的线程,而不用每次新建线程
        ExecutorService  executorService= Executors.newCachedThreadPool();
        for (int i = 0; i <100 ; i++) {
            final int count=i;
            executorService.execute(()->{
                try {
                    Thread.sleep(1);
                } catch (InterruptedException e) {
                    e.printStackTrace();
                }
                System.out.println(Thread.currentThread().getName()+"--->i:"+count);
            });

        }
    }
}
复制代码

newFixedThreadPool

创建一个定长线程池,可控制线程最大并发数,超出的线程会在队列中等待
复制代码
package com.yjc.juc;

import java.util.concurrent.ExecutorService;
import java.util.concurrent.Executors;

public class FixedThreadPoolDemo {
    public static void main(String[] args) {
        //创建一个定长线程池,可控制线程最大并发数,超出的线程会在队列中等待
        ExecutorService executorService= Executors.newFixedThreadPool(5);
        for (int i = 0; i <100 ; i++) {
            final int count=i;
            executorService.execute(()->{
                try {
                    Thread.sleep(300);
                } catch (InterruptedException e) {
                    e.printStackTrace();
                }
                System.out.println(Thread.currentThread().getName()+"--->i:"+count);
            });

        }
    }
}
复制代码

 newScheduledThreadPool

复制代码
package com.yjc.juc;


import java.util.concurrent.Executors;
import java.util.concurrent.ScheduledExecutorService;
import java.util.concurrent.TimeUnit;

public class ScheduledThreadPoolDemo {
    public static void main(String[] args) {
        //创建一个定长线程池,支持定时及周期性任务执行
        ScheduledExecutorService executorService= Executors.newScheduledThreadPool(5);
        for (int i = 0; i <100 ; i++) {
            final int count=i;
            executorService.schedule(()->{
                System.out.println(Thread.currentThread().getName()+"--->i:"+count);
                //延迟三秒开始执行
            },3, TimeUnit.SECONDS);

        }
    }
}
复制代码

延迟三秒才开始执行

newSingleThreadExecutor

创建一个单线程化的线程池,它只会用唯一的工作线程来执行任务,保证所有任务按照指定顺序(FIFO, LIFO, 优先级)执行。示例代码如下:

复制代码
package com.yjc.juc;


import java.util.concurrent.ExecutorService;
import java.util.concurrent.Executors;

public class SingleThreadExecutorDemo {
    public static void main(String[] args) {
        //创建一个单线程化的线程池,它只会用唯一的工作线程来执行任务,保证所有任务按照指定顺序(FIFO, LIFO, 优先级)执行
        ExecutorService executorService= Executors.newSingleThreadExecutor();
        for (int i = 0; i <100 ; i++) {
            final int count=i;
            executorService.execute(()->{
                try {
                    Thread.sleep(300);
                } catch (InterruptedException e) {
                    e.printStackTrace();

                }
                System.out.println(Thread.currentThread().getName()+"--->i:"+count);
            });

        }
    }}
复制代码

线程池原理剖析

提交一个任务到线程池中,线程池的处理流程如下:

1、判断线程池里的核心线程是否都在执行任务,如果不是(核心线程空闲或者还有核心线程没有被创建)则创建一个新的工作线程来执行任务。如果核心线程都在执行任务,则进入下个流程。

2、线程池判断工作队列是否已满,如果工作队列没有满,则将新提交的任务存储在这个工作队列里。如果工作队列满了,则进入下个流程。

3、判断线程池里的线程是否都处于工作状态,如果没有,则创建一个新的工作线程来执行任务。如果已经满了,则交给饱和策略来处理这个任务。

合理配置线程池

CPU密集型时,任务可以少配置线程数,大概和机器的cpu核数相当,这样可以使得每个线程都在执行任务

IO密集型时,大部分线程都阻塞,故需要多配置线程数,2*cpu核数

猜你喜欢

转载自www.cnblogs.com/rzbwyj/p/12525686.html
今日推荐