JUC学习总结

JUC相关基本概念

进程与线程

进程:进程是程序的一次执行过程,是系统运行程序的基本单位,系统运行一个程序后关闭,就是一个进程从开始到运行到死亡的全部过程。

线程:线程的粒度比进程更小,一个进程的执行过程会产生多个线程。所有线程会共享进程的堆内存,元空间区域,而每个线程都有自己的本地方法栈,虚拟机栈,程序计数器。由于系统在线程之间切换比在进程之间切换效率更高,因此线程也称为轻量级线程。

并发与并行

并发:同一间内多个线程抢夺同一资源,典型场景就是秒杀。

并行:同一段时间内多个线程同时在执行。

线程调用start方法后,并不是立即启动,而是进入就绪状态,还需要等待CPU调度时间片切换,拿到时间片后才切换成runnable状态。

线程有哪几种状态?

new,runnable,blocked,wating,timed_wating,terminated

wating和timed_wating有什么区别?

wating一直等,timed_wating有时间限制,只在规定时间内进行等待,过时不候。

wait和sleep都会导致线程进入Blocked状态,这两者有什么区别呢?

当一个线程调用wait方法进入到Blocked状态时,线程会释放掉当前锁的控制权;而调用sleep方法时则不会放掉当前锁。

JAVA8的Lambda表达式简介

Lambda表达式版的卖票程序

//卖票程序,高内聚低耦合下,线程操作资源类
class Tickets{
    private int num = 30;
    private Lock lock = new ReentrantLock();
    public  void sale(){
        lock.lock();
        try {
            if (num > 0){
                System.out.println(Thread.currentThread().getName() + "\t 卖出第 \t" + (num--) + "\t票,还剩\t" + num + "\t张票");
            }
        }catch (Exception e){
            e.printStackTrace();
        }finally {
            lock.unlock();
        }
    }
}
public class SaleTest {
    public static void main(String[] args) {
        Tickets tickets = new Tickets();
        new Thread(() -> {for (int i = 0;i < 40;i++){tickets.sale();}},"A").start();
        new Thread(() -> {for (int i = 0;i < 40;i++){tickets.sale();}},"B").start();
        new Thread(() -> {for (int i = 0;i < 40;i++){tickets.sale();}},"C").start();
    }
}

lambda表达式要点

1、拷贝小括号,写死右箭头,落地大括号。

2、如果接口中只有一个方法即为函数式接口,会默认添加@FunctionalInterface注解。

3、Java8后接口允许有多个default方法的实现,允许有多个静态方法的实现。

//函数式接口:接口中只有一个待实现方法
interface SayHello{
    public int add(int x,int y);

    default int div(int x,int y){
        return x/y;
    }

    default int div2(int x,int y){
        return x/y;
    }

    public static int  mov(int x,int y){ 
        return x*y;
    }
    public static int  mov2(int x,int y){
        return x*y;
    }

}
public class LambdaExpressDemo {
    public static void main(String[] args) {
        SayHello sayHello = (x,y)->{return x + y;};
        System.out.println(sayHello.add(3,4));
        System.out.println(sayHello.div(4,4));
        System.out.println(SayHello.mov(3,4));
    }

}

synchronized版生产消费模型与ReentrantLock版

生产者消费者传统版,由于判断条件使用的是if,存在虚假唤醒的情况

//三大要点:判断,工作,通信
class Air{
    private int num = 0;

    public synchronized void incr() throws InterruptedException {
        if (num != 0){
            this.wait();
        }
        num++;
        System.out.println(Thread.currentThread().getName() + "\t :" + num);
        this.notifyAll();
    }

    public synchronized void dec() throws InterruptedException {
        if (num == 0){
            this.wait();
        }
        num--;
        System.out.println(Thread.currentThread().getName() + "\t :" + num);
        this.notifyAll();
    }
}
public class AirContain {
    public static void main(String[] args) {
        Air air = new Air();
        new Thread(() -> {
            for (int i = 1;i<=10;i++){
                try {
                    air.incr();
                } catch (InterruptedException e) {
                    e.printStackTrace();
                }
            }
        },"A").start();

        new Thread(() -> {
            for (int i = 1;i<=10;i++){
                try {
                    air.dec();
                } catch (InterruptedException e) {
                    e.printStackTrace();
                }
            }
        },"B").start();

    }
}

多线程四大要点:

1、高内聚低耦合前提下,线程操作资源类。

2、三大要点:判断,工作,通信。

3、防止线程虚假唤醒(多线程中判断不能用if,必须用while)。

4、标志位。

class Air{
    private int num = 0;

    public synchronized void incr() throws InterruptedException {
        while (num != 0){
            this.wait();
        }
        num++;
        System.out.println(Thread.currentThread().getName() + "\t :" + num);
        this.notifyAll();
    }

    public synchronized void dec() throws InterruptedException {
        while (num == 0){
            this.wait();
        }
        num--;
        System.out.println(Thread.currentThread().getName() + "\t :" + num);
        this.notifyAll();
    }
}
public class AirContain {
    public static void main(String[] args) {
        Air air = new Air();
        new Thread(() -> {
            for (int i = 1;i<=10;i++){
                try {
                    air.incr();
                } catch (InterruptedException e) {
                    e.printStackTrace();
                }
            }
        },"A").start();

        new Thread(() -> {
            for (int i = 1;i<=10;i++){
                try {
                    air.dec();
                } catch (InterruptedException e) {
                    e.printStackTrace();
                }
            }
        },"B").start();
        new Thread(() -> {
            for (int i = 1;i<=10;i++){
                try {
                    air.incr();
                } catch (InterruptedException e) {
                    e.printStackTrace();
                }
            }
        },"C").start();
        new Thread(() -> {
            for (int i = 1;i<=10;i++){
                try {
                    air.dec();
                } catch (InterruptedException e) {
                    e.printStackTrace();
                }
            }
        },"D").start();

    }
}

使用while替代if后,可以防止虚假唤醒的情况发生,但是如果想要精准唤醒(比如上面程序中,A线程执行后让C线程执行)就实现不了。下面是使用ReentrantLock锁的新版生产消费程序

class AirNew{
    private int num = 0;
    private Lock lock = new ReentrantLock();
    private Condition condition = lock.newCondition();

    public void incr(){
        lock.lock();
        try {
            while (num != 0){
                condition.await();
            }
            num++;
            System.out.println(Thread.currentThread().getName() + "\t :" + num);
            condition.signalAll();
        }catch (Exception e){
            e.printStackTrace();
        }finally {
            lock.unlock();
        }
    }

    public void dec(){
        lock.lock();
        try {
            while (num == 0){
                condition.await();
            }
            num--;
            System.out.println(Thread.currentThread().getName() + "\t :" + num);
            condition.signalAll();
        }catch (Exception e){
            e.printStackTrace();
        }finally {
            lock.unlock();
        }
    }
}
public class AirContainNew {
    public static void main(String[] args) {
        AirNew airNew = new AirNew();
        new Thread(() -> {for(int i = 1;i<10;i++){airNew.incr();}},"A").start();
        new Thread(() -> {for(int i = 1;i<10;i++){airNew.dec();}},"B").start();
        new Thread(() -> {for(int i = 1;i<10;i++){airNew.incr();}},"C").start();
        new Thread(() -> {for(int i = 1;i<10;i++){airNew.dec();}},"D").start();
    }
}
class PrintABC{
//    标志位
    private int num = 1;
   private Lock lock = new ReentrantLock();
// 创建3个condition,以达到精准唤醒的目的
   private Condition condition1 = lock.newCondition();
   private Condition condition2 = lock.newCondition();
   private Condition condition3 = lock.newCondition();

   public void A(){
       lock.lock();
       try {
           while (num != 1){
               condition1.await();
           }
           for (int i = 1;i<=5;i++){
               System.out.println(Thread.currentThread().getName()+"\t " + i);
           }
           num = 2;
           condition2.signal();
       }catch (Exception e){
           e.printStackTrace();
       }finally {
           lock.unlock();
       }
   }

    public void B(){
        lock.lock();
        try {
            while (num != 2){
                condition2.await();
            }

            for (int i = 1;i<=10;i++){
                System.out.println(Thread.currentThread().getName()+"\t " + i);
            }
            num = 3;
            condition3.signal();
        }catch (Exception e){
            e.printStackTrace();
        }finally {
            lock.unlock();
        }
    }

    public void C(){
        lock.lock();
        try {
            while (num != 3){
                condition3.await();
            }
            for (int i =1;i<=15;i++){
                System.out.println(Thread.currentThread().getName()+"\t " + i);
            }
            num = 1;
            condition1.signal();
        }catch (Exception e){
            e.printStackTrace();
        }finally {
            lock.unlock();
        }
    }

}
public class PrintTen {
    public static void main(String[] args) {
        PrintABC printABC = new PrintABC();
        new Thread(() -> {
            for (int i = 1;i<=10;i++){
                printABC.A();
            }
        },"A").start();

        new Thread(() -> {
            for (int i = 1;i<=10;i++){
                printABC.B();
            }
        },"B").start();

        new Thread(() -> {
            for (int i = 1;i<=10;i++){
                printABC.C();
            }
        },"C").start();

    }
}

上述程序中使用ReentrantLock锁配合newCondition()方法,创建多个condition,需要唤醒哪个condition时,直接调用其对应的signal()方法,达到精准唤醒的目的。每个Condition对象都包含着一个队列(以下称为等待队列),该队列是Condition对象实现等待/通知功能的关键。condition的具体原理,日后深入了解了再述。

线程8锁

根据下面的程序,有以下8个问题

1、标准访问,请问先打印邮件还是短信?

2、邮件方法暂停4秒,请问先打印邮件还是短信?

3、新增一个普通方法hello(),请问先打印邮件还是hello?

4、两部手机,请问先打印邮件还是短信?

5、两个静态同步方法,同一部手机,请问先打印邮件还是短信?

6、两个静态同步方法,2部手机,请问先打印邮件还是短信?

7、1个普通同步方法,1个静态同步方法(邮件),1部手机,请问先打印邮件还是短信?

8、1个普通同步方法,1个静态同步方法(邮件),2部手机(第一部调邮件,第二部调短信),请问先打印邮件还是短信?

class Phone{
    public synchronized void sendEmail() throws InterruptedException {
        TimeUnit.SECONDS.sleep(4);
        System.out.println("------send email");
    }
    public synchronized void sendSMS(){
        System.out.println("send SMS");
    }
    public void hello(){
        System.out.println("hello");
    }
}
public class Lock8 {
    public static void main(String[] args) {
        Phone phone = new Phone();
        new Thread(() -> {
            try {
                phone.sendEmail();
            } catch (InterruptedException e) {
                e.printStackTrace();
            }
        },"A").start();
        TimeUnit.SECONDS.sleep(2);
        new Thread(() -> {
            phone.sendSMS();
        },"B").start();

    }
}

1:先邮件,后短信。

2:先邮件,后短信。

3:先hello,后邮件。

4:先短信,后邮件。

5:先邮件,后短信。

6、先邮件,后短信。

7、先短信,后邮件。

8、先短信,后邮件。

8锁小总结

synchronized实现同步的基础:Java中的每一个对象都可以作为锁。且具体表现为3种以下行式:

对于普通同步方法,锁当前实例对象。

对于静态同步方法,锁当前类的class对象。

对于同步方法块,锁是synchonized括号里面配置的对象。

一个线程如果有多个方法同时被synchronized修饰,某一时刻内,只要有一个线程A去访问其中的一个synchronized修饰的方法,其他的线程不管是否访问线程A访问的方法,都需要等待这个线程A释放锁后,才能去访问。换句话说,某一时刻内,只有能一个线程去访问这些synchronized方法。因为锁的是当前对象this,被锁定后,其他的线程都不能进入当前对象的其他synchronized方法。

当一个线程试图访问的同步代码块时,它首先必须得到锁,退出或抛出异常时必须释放锁。

也就是说如果一个实例对象的非静态同步方法获取锁后,该实例对象的其他非静态同步方法必须等待获取锁的方法释放锁后,才能获取锁,可是别的实例对象的非静态同步方法因为跟该实例对象的非静态同步方法用的是不同的锁,所以无须等待该实例对象已获取锁的非惊天同步方法释放锁就可用获取他们自己的锁。

所有的静态同步方法用的也是同一把锁——类对象本身。

这两把锁(this和class)是两个不同的对象,所以静态同步方法与非静态同步方法之间是不会有竞争关系的。但是一旦一个静态同步方法获取锁后,其他的静态同步方法都必须等待该方法释放锁后才能获取锁。而不管是同一个实例对象的静态同步方法之间,还是不同的实例对象的静态同步方法之间。只和他们是同一个类有关。

synchronized和Lock区别?

原始构成:synchronized是关键字属于JVM层面依赖操作系统的调度,lock是juc包中具体的类属于api层面的锁。

使用方法:synchronized不需要手动释放锁,当synchronized代码执行完后系统会自动让线程释放对锁的占用;lock则需要手动去释放锁,如果未释放就有可能发生死锁。

等待是否可中断:synchronized不可中断,除非抛出异常或者正常运行完成;lock可中断,有两种方法,第一种设置超时时间,第二种是调用interrupt()方法。

加锁是否公平:synchronized是非公平锁。lock两者都可以,默认非公平锁,构造方法可以传入boolean值,true为公平锁,false为非公平锁。

锁能否绑定多个条件:synchronized不可用,lock可以用来实现精确唤醒线程,而不是像synchronized要么随机唤醒,要么唤醒全部线程。

list线程不安全问题

public class ListNotSafe {
    public static void main(String[] args) {
//        ArrayList list = new ArrayList<>();
//        List<Object> list = Collections.synchronizedList(new ArrayList<>());
        List<Object> list = new CopyOnWriteArrayList<>();

        for(int i = 0;i< 30;i++){
            new Thread(() -> {
                list.add(UUID.randomUUID().toString().substring(0,3));
                System.out.println(list);
            },"A").start();

        }
    }
}

解决方案有3种:

1、用vector。

2、用Collections.synchronizedList方法包住list。

3、用CopyOnWriteArrayList。

CopyOnWrite容器即写时复制的容器,往一个容器添加元素的时候,不直接往当前容器Object[]添加,而是先将当前容器Object[]进行Copy,复制出一个新的容器Object[] newElements,然后新的容器Object[] newElements里添加元素,添加完元素之后,再将原容器的引用指向新的容器setArray(newElements);这样做的好处是可用对CopyOnWrite容器进行并发的读,而且不需要加锁,因为当前容器不会添加任何元素。所以CopyOnWrite容器也是一种读写分离的思想,读和写操作不同的容器。

CopyOnWriteArrayList的add方法源码如下

    public boolean add(E e) {
        final ReentrantLock lock = this.lock;
        lock.lock();
        try {
            Object[] elements = getArray();
            int len = elements.length;
            Object[] newElements = Arrays.copyOf(elements, len + 1);
            newElements[len] = e;
            setArray(newElements);
            return true;
        } finally {
            lock.unlock();
        }
    }

set线程不安全问题

同样可用使用CopyOnWriteSet解决

HashSet底层是HashMap实现,add方法时是将值放进hashmap的key中,value是一个Obeject类型的常量

Callable接口与Runnable接口区别?

主要有3点区别,第一,callable接口带返回值,第二Callable接口可以抛异常,第三Callable接口落地方法是call。

实现Callable接口的类,通过创建FutureTask对象后,将对象作为参数传入Thread构造方法进行调用。其中体现了多态的思想,因为FutureTask实现了RunnableFuture接口,RunnableFuture同时实现了Runnalbe接口和Callable接口,而Thread类的构造器需要传入Runnable类型的接口,所以可以传入FutureTask对象。

Callable采用异步的思想,如果调用get方法获取返回结果时,如果接口还没算出来,那么线程将会被挂起,执行其他的线程,直到计算结果完成,解除挂起线程,get方法得到返回结果。

class MyThread implements Callable<Integer>{

    @Override
    public Integer call() throws Exception {
        System.out.println("call");
        return 1024;
    }
}
public class CallableDemo {
    public static void main(String[] args) throws ExecutionException, InterruptedException {
        MyThread myThread = new MyThread();
        FutureTask futureTask = new FutureTask(myThread);

        new Thread(futureTask,"A").start();
        new Thread(futureTask,"B").start();
        System.out.println(futureTask.get());
    }
}

CountDownLatch与CyclicBarrier

CountDownLatch用来使一个线程等待其他N个线程执行完毕之后,再执行。它主要又两个方法,当一个或多个线程调用await方法时,这些线程会阻塞;其他线程调用countDown方法将计数器减1(调用countDown方法的线程不会阻塞);当计数器的值变为0时,因await方法阻塞的线程会被唤醒继续执行。

public class CountDownDemo {
    public static void main(String[] args) throws InterruptedException {
        CountDownLatch countDownLatch = new CountDownLatch(6);
        for (int i = 1;i<=6;i++){
            final int temp = i;
            new Thread(() -> {
                System.out.println("第" + temp + "个人出门");
                countDownLatch.countDown();

            },String.valueOf(i)).start();
        }
        countDownLatch.await();
        System.out.println("锁门!");
    }
}

上面程序中,只有所有人都出门了,main线程才能锁门,main线程等待其他都执行完了再执行。下面再看看CyclicBarrier。

CyclicBarrier用来使所有线程都到达指定状态后,再继续下面的操作,各个线程彼此等待,所有线程都执行完成后,才继续后面的操作。

public class CyclicBarrierDemo {
    public static void main(String[] args) throws BrokenBarrierException, InterruptedException {
        CyclicBarrier cyclicBarrier = new CyclicBarrier(7,()->{
            System.out.println("召唤神龙!");
        });
        for (int i = 1;i<=7;i++){
            final int  temp = i;
            new Thread(() -> {
                System.out.println("集齐第" + temp +"颗龙珠");
                try {
                    cyclicBarrier.await();
                } catch (InterruptedException e) {
                    e.printStackTrace();
                } catch (BrokenBarrierException e) {
                    e.printStackTrace();
                }
            },String.valueOf(i)).start();
        }
    }
}

Semaphore

在信号量上有两种操作:

acquire(获取),当一个线程调用acquire操作时,它要么通过成功获取信号量(信号量减1),要么一直等下去,直到有线程释放信号量,或超时。

release(释放)实际上会将信号量的值加1,然后唤醒等待的线程。

信号量主要用于两个目的,一个是用于多个共享资源的互斥使用,另一个用于并发线程数的控制。

public class SemaphoreDemo {
    public static void main(String[] args) {
        Semaphore semaphore = new Semaphore(3);//初始3个车位

        for (int i = 1;i <= 10;i++){
            new Thread(() -> {
                try {
                    semaphore.acquire();
                    System.out.println(Thread.currentThread().getName() + "号线程抢到了车位,停车3秒钟");
                    TimeUnit.SECONDS.sleep(3);
                    System.out.println(Thread.currentThread().getName()+ "号线程把车开走了......");
                } catch (InterruptedException e) {
                    e.printStackTrace();
                }finally {
                    semaphore.release();
                }

            },String.valueOf(i)).start();

        }
    }
}

ReenReadWriteLock

多个线程同时读一个资源类没有任何问题,所以为了满足并发量,读取共享资源应该可以同时进行。但是如果有一个线程想要去写共享资源,就不应该再有其他线程可以对该资源进行读或写(不能读是为了保证数据一致性)。

class MyCache{
    private volatile HashMap map = new HashMap();
    private ReadWriteLock lock = new ReentrantReadWriteLock();

    public  void put(String key,String value){
        lock.writeLock().lock();
        try {
            System.out.println(Thread.currentThread().getName() + "号线程开始写数据。。。。");
            TimeUnit.MILLISECONDS.sleep(300);
            map.put(key,value);
            System.out.println(Thread.currentThread().getName() + "号线程写数据完成-----------");
        } catch (InterruptedException e) {
            e.printStackTrace();
        }finally {
            lock.writeLock().unlock();
        }
    }
    public  void get(String key){
        lock.readLock().lock();
        try {
            System.out.println(Thread.currentThread().getName() + "号线程开始读数据~~~~~");
            TimeUnit.MILLISECONDS.sleep(300);
            Object o = map.get(key);
            System.out.println(Thread.currentThread().getName() + "号线程读到数据:\t" + o);
        } catch (InterruptedException e) {
            e.printStackTrace();
        }finally {
            lock.readLock().unlock();
        }
    }
}
public class ReadWriteLockDemo {
    public static void main(String[] args) throws InterruptedException {
        MyCache myCache = new MyCache();
        for (int i = 1;i<= 6;i++){
            final int temp = i;
            new Thread(() -> {
                myCache.put(temp + "",temp+"");
            },String.valueOf(i)).start();
        }
        for (int i = 1;i<= 6;i++){
            final int temp = i;
            new Thread(() -> {
                myCache.get(temp + "");
            },String.valueOf(i)).start();
        }
    }
}

小总结:

读-读能共存

读-写不能共存

写-写不能共存

阻塞队列

当队列是空的,向队列中获取元素的线程会阻塞

当队列是满的,向队列中添加元素的线程会阻塞

在多线程领域:所谓阻塞,是指在某些情况下将挂起线程;而当满足某些条件时,再将挂起的线程自动唤醒。

为什么需要BlockingQueue?

好处是我们不需要关心什么时候需要阻塞线程,什么时候需要唤醒线程,因为这一切BlockingQueue都已经实现了。在JUC包发布以前,多线程环境下,我们不仅需要自己去控制这些细节,还要兼顾效率和线程安全,而这会给我们的程序带来不小的复杂度。

ArrayBlockingQueue:由数组结构组成的有界阻塞队列。

LinkedBlockingQueue:由链表结构组成的有界(大小默认值为integer.MAX_VALUE)阻塞队列。

SynchronousQueue:不存储元素的阻塞队列,也即单个元素的队列。

线程池

10年前单核CPU电脑,假的多线程,像马戏团小丑玩多个球,CPU需要来回切换

限制是多核电脑,多个线程各自跑在独立的CPU上,不用切换效率高。

线程池的优势:

线程池做的工作只需要控制运行的线程数量,处理过程中将任务放入队列,然后在线程创建后启动这些任务,如果线程数量超过了最大数量,超出数量的线程排队等候,等其他线程执行完毕,再从队列中取出任务来。

它的主要特点为:线程服用;控制最大并发数;管理线程。

第一:降低资源消耗。通过重复利用已创建的线程降低线程创建和销毁造成的消耗。

第二:提高响应速度。当任务到达时,任务可以不需要等待线程创建就能立即执行。

第三:提高线程的可管理性。线程是稀缺资源,如果无限制的创建,不仅会消耗系统资源,还会降低系统的稳定性,使用线程池可以进行统一的分配,调优和监控。


JUC自带的三种线程池如下图所示

线程池7大参数

corePoolSize:核心线程数。

maximumPoolSize:线程池中最大线程数,必须大于1。

keepAliveTime:非核心线程存活时间,当线程池的数量超过corePoolSize时,如果多余的线程在keepAliveTime的时间内空闲,就会被销毁。

unit:非核心线程存活时间单位。

workQueue:存放被提交但还未执行任务的阻塞队列。

threadFactory:创建线程的工厂,一般都用默认。

handler:线程池拒绝策略。表示当队列满了,而且工作线程数等于maximumPoolSize时,如何来拒绝请求的策略。

    public ThreadPoolExecutor(int corePoolSize,
                              int maximumPoolSize,
                              long keepAliveTime,
                              TimeUnit unit,
                              BlockingQueue<Runnable> workQueue,
                              ThreadFactory threadFactory,
                              RejectedExecutionHandler handler) {
        if (corePoolSize < 0 ||
            maximumPoolSize <= 0 ||
            maximumPoolSize < corePoolSize ||
            keepAliveTime < 0)
            throw new IllegalArgumentException();
        if (workQueue == null || threadFactory == null || handler == null)
            throw new NullPointerException();
        this.acc = System.getSecurityManager() == null ?
                null :
                AccessController.getContext();
        this.corePoolSize = corePoolSize;
        this.maximumPoolSize = maximumPoolSize;
        this.workQueue = workQueue;
        this.keepAliveTime = unit.toNanos(keepAliveTime);
        this.threadFactory = threadFactory;
        this.handler = handler;
    }

线程池执行流程如下图所示

线程池运行流程:

1、在创建了线程池后,开始等待请求。

2、当调用execute()方法添加一个请求任务时,线程池会做出如下判断:

 2.1、如果正在运行的线程数量小于corePoolSize,那么马上创建线程运行这个任务;

 2.2、如果正在运行的线程数量大于或等于corePoolSize,那么将这个任务放入队列;

 2.3、如果这个时候队列满了且正在运行的线程数量还小于maximumPoolSize,那么还是要创建非核心线程立刻执行这个任务;

 2.4、如果队列满了,且正在运行的线程数大于或等于maximumPoolSize,那么线程池会启动饱和拒绝策略来执行。

3、当一个线程完成任务时,它会从队列中取下一个任务来执行。

4、当一个线程无事可做超过一定的时间(keepAliveTime)时,线程会判断:如果当前运行的线程数大于corePoolSize,那么这个线程就会被停掉。所有线程池的所有任务完成后,它最终会收缩到corePoolSize的大小。

线程池不允许使用Executors去创建,而是通过ThreadPoolExecutor的方式,这样处理的方式是让写的人更加明确线程池的运行规则,规避资源耗尽的风险。

Executors返回的线程池对象的弊端如下:

1、FixedThreadPool和SingleThreaadPool:允许的请求队列长度为Integer.MAX_VALUE,可能会堆积大量的请求,从而导致OOM。

2、CachedThreadPool和ScheduledThreadPool:允许的创建线程数量为Integer.MAX_VALUE,可能会创建大量的线程,从而导致OOM。

等待队列已经满了,再也塞不下新任务了,同时,线程池中的max线程数也到了,无法继续为新任务服务。这个时候我们就需要拒绝策略机制合理的处理这个问题。

线程池拒绝策略

AbortPolicy(默认):直接抛出RejectedExecutionException异常阻止系统正常运行。

CallerRunsPolicy:调用者运行一种调节机制,该策略既不会抛弃任务,也不会抛出异常,而是将某些任务退回到调用者,从而降低新任务的流量。

DiscardOldestPolicy:抛弃队列中等待最久的任务,然后把当前任务加入队列中尝试再次提交当前任务。

DiscardPolicy:该策略默默的丢弃无法处理的任务,不予任何处理也不抛出异常。如果允许任务丢失,这是最好的一种策略。

分支合并框架

Fork用来把一个大任务化小。

Join用来把拆分的任务进行合并

ForkJoinPool继承树如下图所示

ForkJoinTask

RecursiveTask用来实现递归任务,即自己调用自己。

下面程序使用Fork计算1累加到100

class MyFork extends RecursiveTask<Integer> {
    private int ADD_PARM_CONTROL = 10;
    private int begin;
    private int end;

    public MyFork(int begin, int end) {
        this.begin = begin;
        this.end = end;
    }

    @Override
    protected Integer compute() {
        int result = 0;
        if (end - begin <= ADD_PARM_CONTROL){
            for (int i=begin;i <= end;i++){
                result = result + i;
            }
        }else {
            int mid = (end + begin) / 2 ;
            MyFork myFork1 = new MyFork(begin, mid);
            MyFork myFork2 = new MyFork(mid + 1, end);
            ForkJoinTask<Integer> fork1 = myFork1.fork();
            ForkJoinTask<Integer> fork2 = myFork2.fork();
            result = fork1.join()+fork2.join();
        }
        return result;
    }
}
public class ForkJoinDemo {
    public static void main(String[] args) throws ExecutionException, InterruptedException {
        MyFork myFork = new MyFork(1, 100);
        ForkJoinPool forkJoinPool = new ForkJoinPool();
        ForkJoinTask<Integer> submit = forkJoinPool.submit(myFork);
        System.out.println(submit.get());
    }
}

流式计算

流是数据渠道,用于操作数据源(集合、数组等)所生成的元素序列

集合讲的是数据,流讲的是计算。

流的特点:stream自己不能存储元素,不会改变源对象。相反,他们会返回一个持有结果的新stream。stream操作是延迟执行的,这意味着他们会等到需要结果的时候才执行。

stream有3个阶段

创建stream:一个数据源(数组或集合)

中间操作:一个中间操作,处理数据源数据

终止操作:一个终止操作,执行中间操作连,产生结果

public class StreamDemo {
    public static void main(String[] args) {
        List<Integer> list = Arrays.asList(1,2,3,4,5);

        List<Integer> streamList = list.stream().filter(num -> {
            return num % 2 == 0;
        }).map(num -> {
            return num / 2;
        }).collect(Collectors.toList());
        System.out.println(streamList);

        Map<Integer, Integer> collect = list.stream().filter(num -> {
            return num % 2 != 0;
        }).map(x -> {
            return x * 2;
        }).collect(Collectors.toMap(x -> {
            return x;
        }, y -> {
            return y * 2;
        }));
        System.out.println(collect);
    }
}
class User implements Comparable<User>{
    private int id;
    private String name;
    private int age;

    public User(int id, String name, int age) {
        this.id = id;
        this.name = name;
        this.age = age;
    }

    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 int getAge() {
        return age;
    }

    public void setAge(int age) {
        this.age = age;
    }

    @Override
    public int compareTo(User o) {
        return age - o.getAge();
    }

    @Override
    public String toString() {
        return "User{" +
                "id=" + id +
                ", name='" + name + '\'' +
                ", age=" + age +
                '}';
    }
}public class StreamDemo {
    public static void main(String[] args) {
        User u1 = new User(1, "a", 23);
        User u2 = new User(2, "b", 24);
        User u3 = new User(3, "c", 25);
        User u4 = new User(4, "d", 26);
        User u5 = new User(5, "e", 27);

//        按照给出数据,找出同时满足以下条件的用户,也即以下条件全部满足:
//        偶数ID且年龄大于等于24,且用户名转为大写,且用户年龄最大的一个用户
        List<User> users = Arrays.asList(u1, u2, u3, u4, u5);
        List<User> collect = users.stream()
                .filter(user -> {
                    return user.getId() % 2 == 0;
                })
                .filter(user -> {
                    return user.getAge() >= 24;
                })
                .map(user -> {
                    return new User(user.getId(),user.getName().toUpperCase(),user.getAge());
                }).sorted((o1, o2) -> {
                    return o2.compareTo(o1);
                })
                .limit(1).collect(Collectors.toList());
        System.out.println(collect);
    }
}

 

發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章