Java多线程

目录

1、程序、进程、线程 

2、并行、并发

3、多线程

        3.1、优点

        3.2、实现方式  

                3.2.1、继承Thread类

                3.2.2、实现Runnable接口

                3.2.3、继承Thread类+实现Runnable接口区别

                3.2.4、实现Callable接口(jdk5.0新增)

                3.2.5、实现Callable接口和实现Runnable接口区别 

        3.3、生命周期

        3.4、线程同步 

                3.4.1、同步代码块(解决线程安全问题)

                3.4.2、同步方法(解决线程安全问题)

        3.5、线程死锁

                3.5.1、概念

                3.5.2、死锁示例

                3.5.3、Lock锁(解决线程安全问题)

                3.5.4、Synchronized和Lock(ReentrantLock)区别

        3.6、线程通信

                3.6.1、wait()、notify()、notifyAll()

                3.6.2、sleep()和wiat()

        3.7、线程池

                3.7.1、思路         

                3.7.2、优点

                3.7.3、七大参数和工作顺序 

                3.7.3、相关API 

        3.8、CompletableFuture异步编排

                3.8.1、创建异步对象

                3.8.2 、计算完成时回调方法

                3.8.3、handle()方法

                3.8.4、线程串行化方法

                3.8.5、两任务组合 - 都要完成

                3.8.6、两任务组合 - 一个完成

                3.8.7、多任务组合 


1、程序、进程、线程 

        程序:一段静态的代码块。

        进程:正在运行的程序,资源分配的基本单位。每个进程都会分配不同的内存区域

        线程:

                a、一个程序内部的一条执行路径。一个进程可以有多个线程。

                b、每个线程拥有独立的运行栈和程序计数器。

                c、一个进程的多个线程共享相同的内存单元/内存地址空间,使得进程间通信更简便、高效。但是多个线程操作共享的资源可能会带来安全的隐患。

                d、一个java应用程序java.exe,其实至少有三个线程:main()主线程、gc()垃圾回收线程、异常处理线程

2、并行、并发

        并行:多个CPU同时执行多个任务(多个人同时做不同的事)

        并发:一个CPU(采用时间片)同时执行多个任务(多个人做同一件事)

3、多线程

        3.1、优点

对于单核CPU,只使用单个线程先后完成多个任务比多个线程来完成用的时间更短,因为线程之间切换需要时间。如果是多核肯定多线程更快。那为何还要使用多线程?

                a、提高应用程序的响应。对图形化界面更有意义,可增强用户体验

                b、提高计算机系统CPU的利用率

                c、改善程序结构。将既长又复杂的进程分为多个线程,独立运行,利于理解和
修改

        3.2、实现方式  

                  4种方式:继承Thread类、实现Runnable接口、实现Callable接口、线程池

                3.2.1、继承Thread类

// 1、创建一个继承于Thread类的子类
public class ThreadTest1 extends Thread {
    // 2、重写Thread类的run()
    @Override
    public void run() {
        for (int i = 0; i < 100; i++) {
            System.out.println("子线程" + i);
        }
    }

    public static void main(String[] args) {
        // 3、创建thred类的子类的对象
        ThreadTest1 threadTest1 = new ThreadTest1();
        // 4、调用start方法:1、启动当前线程    2、调用当前线程的run方法
        threadTest1.start();
        for (int i = 0; i < 100; i++) {
            System.out.println("主线程" + i);
        }
    }
}


// 其它写法
public class ThreadTest1 extends Thread {
    public static void main(String[] args) {

        // 创建Thread类的匿名子类的写法
        new Thread(){
            @Override
            public void run() {
                for (int i = 0; i < 100; i++) {
                    System.out.println("子线程1:" + i);
                }
            }
        }.start();

        // lambda表达式写法
        new Thread(() -> {
            for (int i = 0; i < 100; i++) {
                System.out.println("子线程2:" + i);
            }
        }).start();
    }
}

Thread类常用方法:

  • void start(): 启动线程,并执行对象的run()方法
  • run(): 线程在被调度时执行的操作
  • String getName(): 返回线程的名称
  • void setName(String name):设置该线程名称
  • static Thread currentThread(): 返回当前线程。在Thread子类中就是this,通常用于主线程和Runnable实现类
  • static void yield():线程让步暂停当前正在执行的线程,把执行机会让给优先级相同或更高的线程,若队列中没有同优先级的线程,忽略此方法
  • join(): 当某个程序执行流中调用其他线程的 join() 方法时,调用线程将被阻塞,直到 join() 方法加入的 join 线程执行完为止低优先级的线程也可以获得执行
  • static void sleep(long millis) :(指定时间:毫秒)令当前活动线程在指定时间段内放弃对CPU控制,使其他线程有机会被执行,时间到后重排队。抛出InterruptedException异常
  • stop():  强制线程生命期结束,不推荐使用
  • boolean isAlive(): 返回boolean,判断线程是否还活着

   线程优先级:MAX_PRIORITY :10    MIN _PRIORITY :1     NORM_PRIORITY:5

           注意:低优先级只是获得调度的概率低,并非一定是在高优先级线程之后才被调用

  • getPriority() : 返回线程优先值
  • setPriority(int newPriority) : 改变线程的优先级

                3.2.2、实现Runnable接口

// 1、创建一个实现Runnable接口的类
public class ThreadTest2 implements Runnable {
    // 2、重写run()方法
    @Override
    public void run() {
        for (int i = 0; i < 100; i++) {
            System.out.println("子线程" + i);
        }
    }

    public static void main(String[] args) {
        // 3、创建实现类
        ThreadTest2 threadTest2 = new ThreadTest2();
        // 4、将对象作为参数传入构造器中
        Thread thread = new Thread(threadTest2);
        // 5、调用start() 1、启动线程  2、调用当前线程的run()方法
        /* 当前线程指的是Thread,而ThreadTest2是实现Runnable接口。为什么能调用run方法?
           源码中new Thread(target)时调用Runnable类型的target的run()*/
        thread.start();
    }
}

                3.2.3、继承Thread类+实现Runnable接口区别

区别:

        优先选择实现Runnable接口去创建多线程。(实际开发是用线程池去创建)

        1、因为java是类是单继承,如果当前对象本身就继承了它的父类,那么它就不能使用继承Thread类的方式去创建线程

        2、使用继承Runnable接口不用创建多个对象可以实现数据共享,定义的成员变量也不需要添加static去修饰

// 继承Thread类实现卖票案例
// 注意:1、存在线程安全问题:多人共享一张票(后期加锁解决)或者票号为0和负数
//       2、控制台打印输出的ticket顺序不是依次递减的。其实ticket是递减的,只是打印语句时可能存在延时等问题
public class ThreadTicket1 extends Thread{
    // 必须设置为static。不然每个对象调用都会拥有自己的ticket=100;设置为static才能共享一个
    private static int ticket = 100;
    @Override
    public void run() {
        while (true) {
            if (ticket > 0) {
                try {
                //不加也可能会发生线程安全问题,只是加了使概率增大更直观看出来
                    Thread.sleep(100);
                } catch (InterruptedException e) {
                    e.printStackTrace();
                }
                System.out.println(getName()+"票号为:"+ticket);
                ticket--;
            } else {
                break;
            }
        }
    }
    public static void main(String[] args) {
        ThreadTicket1 threadTicket1 = new ThreadTicket1();
        ThreadTicket1 threadTicket2 = new ThreadTicket1();
        ThreadTicket1 threadTicket3 = new ThreadTicket1();

        threadTicket1.setName("窗口A");
        threadTicket2.setName("窗口B");
        threadTicket3.setName("窗口C");

        threadTicket1.start();
        threadTicket2.start();
        threadTicket3.start();
    }
}


// 实现Runnable接口实现卖票案例
// 注意:1、存在线程安全问题:多人共享一张票(后期加锁解决)或者票号为0和负数
//       2、控制台打印输出的ticket顺序不是依次递减的。其实ticket是递减的,只是打印语句时可能存在延时等问题
public class ThreadTicket2 implements Runnable{
    // 不用设置为static。因为只有一个对象去共享ticket
    private int ticket = 100;
    @Override
    public void run() {
        while (true) {
            if (ticket > 0) {
                // 这里直接使用getName()无效===this.getName()
                // 因为当前对象ThreadTicket2并没有继承Thread类,所以无法调用getName()方法。可以使用Thread.currentThread()
                System.out.println(Thread.currentThread().getName()+"票号为:"+ticket);
                ticket--;
            } else {
                break;
            }
        }
    }
    public static void main(String[] args) {
        ThreadTicket2 threadTicket2 = new ThreadTicket2();

        // 创建多个线程,使用一个对象
        Thread thread1 = new Thread(threadTicket2);
        Thread thread2 = new Thread(threadTicket2);
        Thread thread3 = new Thread(threadTicket2);

        thread1.setName("窗口A");
        thread2.setName("窗口B");
        thread3.setName("窗口C");

        thread1.start();
        thread2.start();
        thread3.start();
    }
}

                3.2.4、实现Callable接口(jdk5.0新增)

//1、创建一个实现Callable接口的实现类
public class ThreadTest3 implements Callable<Integer> {
    // 2、重写call()方法,可以有返回值
    @Override
    public Integer call() throws Exception {
        int sum = 0;
        for (int i = 0; i < 100; i++) {
            System.out.println("子线程" + i);
            sum += i;
        }
        return sum;
    }

    public static void main(String[] args) {
        // 3、创建实现类
        ThreadTest3 threadTest3 = new ThreadTest3();
        // 4、创建FutureTask对象
        FutureTask<Integer> futureTask = new FutureTask<>(threadTest3);
        Integer sum = null;
        // 5、调用start()
        new Thread(futureTask).start();
        try {
            // 6、获取Callable中call的返回值
            // get()方法的返回值即为FutureTask构造参数Callable实现类重写的call()的返回值
            sum = futureTask.get();
        } catch (InterruptedException e) {
            e.printStackTrace();
        } catch (ExecutionException e) {
            e.printStackTrace();
        }
        System.out.println(sum);
    }
}

                3.2.5、实现Callable接口和实现Runnable接口区别 

区别:

        1、callable接口的run()方法可以有返回值

        2、方法可以抛出异常

        3、支持泛型的返回值

        4、需要借助FutureTask类,比如获取返回结果

        3.3、生命周期

        3.4、线程同步 

优点:解决线程的安全问题

缺点:只有一个线程参与,其它线程等待,效率低。

                3.4.1、同步代码块(解决线程安全问题)

// 使用同步代码块解决线程安全问题(实现Runnable接口方式)
// 问题:存在线程安全问题:出现重票、错票问题(共享数据)
// 原因:当前线程操作未完成,其它线程进来进行了操作
/*
 synchronized(同步监视器) { //同步监视器:相当于锁,任何一个对象都可以充当锁。多个线程必须共用同一把锁
        // 需要被同步的代码
     }
*/

public class ThreadTicket3 implements Runnable {
    private int ticket = 100;
    Object object = new Object();

    @Override
    public void run() {
        while (true) {
            /*放在这线程会出现不安全,因为不是共用同一把锁。线程执行调用的是run方法。会创建多个object
             Object object = new Object();*/

            //synchronized (object) {
            synchronized (this) { // 一般使用this表示当前对象,ThreadTicket3对象只有一个
               
                if (ticket > 0) {
                    try {
                        //不加也可能会发生线程安全问题,只是加了使概率增大更直观看出来
                        Thread.sleep(100);
                    } catch (InterruptedException e) {
                    e.printStackTrace();
                }
                    System.out.println(Thread.currentThread().getName() + "票号为:" + ticket);
                    ticket--;
                } else {
                    break;
                }
            }
        }
    }

    public static void main(String[] args) {
        ThreadTicket3 threadTicket3 = new ThreadTicket3();

        // 创建多个线程,使用一个对象
        Thread thread1 = new Thread(threadTicket3);
        Thread thread2 = new Thread(threadTicket3);
        Thread thread3 = new Thread(threadTicket3);

        thread1.setName("窗口A");
        thread2.setName("窗口B");
        thread3.setName("窗口C");

        thread1.start();
        thread2.start();
        thread3.start();
    }
}


// 使用同步代码块解决线程安全问题(继承Thread类方式)
public class ThreadTicket4 extends Thread {
    // 必须设置为static。不然每个对象调用都会拥有自己的ticket=100;设置为static才能共享一个
    private static int ticket = 100;
    // 因为创建了多个对象就不能,每个对象都有自己的锁,所以要加static
    private static Object object = new Object();

    @Override
    public void run() {
        while (true) {
            // synchronized (this) { 不能使用this,ThreadTicket4对象有多个
            // synchronized (ThreadTicket4.class) {  可以使用类.class方式
            synchronized (object) {
                if (ticket > 0) {
                    try {
                        Thread.sleep(100);//可能会出现票号为负数的情况
                    } catch (InterruptedException e) {
                        e.printStackTrace();
                    }
                    System.out.println(getName() + "票号为:" + ticket);
                    ticket--;
                } else {
                    break;
                }
            }
        }
    }

    public static void main(String[] args) {
        ThreadTicket4 threadTicket1 = new ThreadTicket4();
        ThreadTicket4 threadTicket2 = new ThreadTicket4();
        ThreadTicket4 threadTicket3 = new ThreadTicket4();

        threadTicket1.setName("窗口A");
        threadTicket2.setName("窗口B");
        threadTicket3.setName("窗口C");

        threadTicket1.start();
        threadTicket2.start();
        threadTicket3.start();
    }
}



                3.4.2、同步方法(解决线程安全问题)

// 使用同步方法解决线程安全问题(实现Runnable接口方式)
/*
    // 同步监视器依然存在,只是我们不需要显示声明
        非静态方法:同步监视器:this
        静态方法:同步监视器:当前类本身
       public synchronized void 方法名 (参数){
    }
 */
public class ThreadTicket5 implements Runnable {
    private int ticket = 100;

    @Override
    public void run() {
        // while不能在同步方法,会造成一个线程先循环完在执行下一个线程
        while (true) {
            show();
        }
    }

    // 同步方法方式
    private synchronized void show() {
        if (ticket > 0) {
            try {
                Thread.sleep(100);
            } catch (InterruptedException e) {
                e.printStackTrace();
            }

            System.out.println(Thread.currentThread().getName() + "票号为:" + ticket);
            ticket--;
        }
    }

    public static void main(String[] args) {
        ThreadTicket5 threadTicket5 = new ThreadTicket5();

        // 创建多个线程,使用一个对象
        Thread thread1 = new Thread(threadTicket5);
        Thread thread2 = new Thread(threadTicket5);
        Thread thread3 = new Thread(threadTicket5);

        thread1.setName("窗口A");
        thread2.setName("窗口B");
        thread3.setName("窗口C");

        thread1.start();
        thread2.start();
        thread3.start();
    }
}


// 使用同步方法解决线程安全问题(继承Thread类)
public class ThreadTicket6 extends Thread {
    private static int ticket = 100;

    @Override
    public void run() {
        while (true) {
            show();
        }
    }

    // 同步方法方式
    // private synchronized void show(){ // 当前对象有多个会出现线程安全问题
    private static synchronized void show(){
        //此时的同步监视器并不是this(this和static不能共存),而是ThreadTicket6.class
        if (ticket > 0) {
            try {
                Thread.sleep(100);
            } catch (InterruptedException e) {
                e.printStackTrace();
            }

            System.out.println(Thread.currentThread().getName() + "票号为:" + ticket);
            ticket--;
        }
    }

    public static void main(String[] args) {
        ThreadTicket6 threadTicket1 = new ThreadTicket6();
        ThreadTicket6 threadTicket2 = new ThreadTicket6();
        ThreadTicket6 threadTicket3 = new ThreadTicket6();

        threadTicket1.setName("窗口A");
        threadTicket2.setName("窗口B");
        threadTicket3.setName("窗口C");

        threadTicket1.start();
        threadTicket2.start();
        threadTicket3.start();
    }
}

        3.5、线程死锁

                3.5.1、概念

                不同的线程分别占用对方需要的同步资源不放弃,都在等待对方放弃自己需要的同步资源,就形成了线程的死锁。

                出现死锁后,不会出现异常,不会出现提示,只是所有线程都处于阻塞状态,无法继续

                3.5.2、死锁示例

public class DeadLockTest {

    public static void main(String[] args) {
        StringBuffer b1 = new StringBuffer();
        StringBuffer b2 = new StringBuffer();

        // 继承Thread类匿名内部类写法
        new Thread() {
            @Override
            public void run() {
                synchronized (b1) {
                    b1.append(1);
                    b2.append("a");
                    try {
                        Thread.sleep(100);//增大死锁的可能信
                    } catch (InterruptedException e) {
                        e.printStackTrace();
                    }
                    synchronized (b2) {
                        b1.append(2);
                        b2.append("b");
                        System.out.println(b1);
                        System.out.println(b2);
                    }
                }
            }
        }.start();

        // 实现Runnable接口匿名内部类写法
        new Thread(new Runnable() {
            @Override
            public void run() {
                synchronized (b2) {
                    b1.append(3);
                    b2.append("c");
                    try {
                        Thread.sleep(100);//增大死锁的可能信
                    } catch (InterruptedException e) {
                        e.printStackTrace();
                    }
                    synchronized (b1) {
                        b1.append(4);
                        b2.append("d");
                        System.out.println(b1);
                        System.out.println(b2);
                    }
                }
            }
        }).start();
    }
}

                3.5.3、Lock锁(解决线程安全问题)

                a、Java.util.concurrent.locks.Lock接口是控制多个线程对共享资源进行访问的工具。锁提供了对共享资源的独占访问,每次只能有一个线程对Lock对象加锁,线程开始访问共享资源之前应先获得Lock对象。 

                b、ReentrantLock 类实现了 Lock ,它拥有与 synchronized 相同的并发性和内存语义,在实现线程安全的控制中,比较常用的是ReentrantLock,可以显式加锁、释放锁。

public class LockThread implements Runnable {
    private int ticket = 100;
    //1、实例化ReentrantLock对象
    ReentrantLock reentrantLock = new ReentrantLock();

    @Override
    public void run() {
        while (true) {
            try {
                // 2、调用lock()
                reentrantLock.lock();
                if (ticket > 0) {
                    try {
                        Thread.sleep(100);
                    } catch (InterruptedException e) {
                        e.printStackTrace();
                    }
                    System.out.println(Thread.currentThread().getName() + "票号为:" + ticket);
                    ticket--;
                } else {
                    break;
                }
            } finally {
                // 3、调用解锁方法unlock()
                reentrantLock.unlock();
            }

        }
    }

    public static void main(String[] args) {
        LockThread threadTicket2 = new LockThread();

        // 创建多个线程,使用一个对象
        Thread thread1 = new Thread(threadTicket2);
        Thread thread2 = new Thread(threadTicket2);
        Thread thread3 = new Thread(threadTicket2);

        thread1.setName("窗口A");
        thread2.setName("窗口B");
        thread3.setName("窗口C");

        thread1.start();
        thread2.start();
        thread3.start();
    }
}

                3.5.4、Synchronized和Lock(ReentrantLock)区别

相同点:

        都可以解决线程的安全问题  

不同点:

1、syn是关键字  -----    lock是类

2、syn自动加锁和释放锁  -----  lock必须用lock()和unlock()

3、syn是非公平锁 ----  lock通过创建对象构造函数参数是否为true觉得是否为公平锁(默false)

4、syn不可响应中断,一个线程获取不到锁就一直等着 -----  lock可以响应中断

5、syn不可限时   ----   lock可限时,通过trylock()可避免死锁

        3.6、线程通信

                3.6.1、wait()、notify()、notifyAll()

        注意: 

  1. 这三个方法只有在synchronized方法或synchronized代码块中才能使用,否则会报java.lang.IllegalMonitorStateException异常
  2.  这三个方法的调用者必须是synchronized方法或synchronized代码块中的同步监视器 
  3. 这三个方法是定义在java.lang.Object类中
  • wait():令当前线程挂起并放弃CPU、同步资源并等待,使别的线程可访问并修改共享资源,而当前线程排队等候其他线程调用notify()或notifyAll()方法唤醒,唤醒后等待重新获得对监视器的所有权后才能继续执行。
  • notify():唤醒正在排队等待同步资源的线程中优先级最高者结束等待
  • notifyAll ():唤醒正在排队等待资源的所有线程结束等待.

// 两个线程交互打印
public class communicationThread implements Runnable {
    private int number = 1;

    @Override
    public void run() {
        while (true) {
            synchronized (this) {
                notify();
                if (number <= 100) {
                    System.out.println(Thread.currentThread().getName() + ":" + number);
                    number++;
                    try {
                        // 调用wait()方法的线程进入阻塞状态
                        wait();
                    } catch (InterruptedException e) {
                        e.printStackTrace();
                    }
                } else {
                    break;
                }
            }
        }
    }

    public static void main(String[] args) {
        communicationThread threadTicket2 = new communicationThread();

        // 创建多个线程,使用一个对象
        Thread thread1 = new Thread(threadTicket2);
        Thread thread2 = new Thread(threadTicket2);

        thread1.setName("窗口A");
        thread2.setName("窗口B");

        thread1.start();
        thread2.start();
    }
}

                3.6.2、sleep()和wiat()

相同点:

        都可以使当前的线程进入阻塞状态

不同点:

1、sleep是Thread类的方法,wait是Object类的方法

2、sleep指定当前线程阻塞,时间到达后进入就绪状态,不会释放锁。wait会释放锁

3、sleep方法会抛异常(interrupted exception),wait不会

4、sleep方法在任何地方可以使用,wait方法只能在同步方法或者同步代码块中·

        3.7、线程池

                3.7.1、思路         

                 经常创建和销毁、使用量特别大的资源,比如并发情况下的线程,对性能影响很大。所以需要使用线程池去处理。 

                 提前 创建好多个线程,放入线程池中,使用时直接获取,使用完放回池中。可以避免频繁创建销毁、实现重复利用。类似生活中的公共交通工具。

                3.7.2、优点

  • 提高响应速度(减少了创建新线程的时间)
  • 降低资源消耗(重复利用线程池中线程,不需要每次都创建)
  • 便于线程管理

                3.7.3、七大参数和工作顺序 

七大参数: 

1、corePoolSize:核心线程数。线程池创建好就等待执行异步任务的线程数量。一直存在除非设置了allowCoreThreadTimeOut

2、maximumPoolSize:最大线程数量。控制资源

3、keepAliveTime:空闲线程存活时间。当线程数>核心线程数,如果线程空闲时间>指定的存活时间,那么就会释放空闲的线程(maximumPoolSize-corePoolSize) 

4、unit:时间单位

5、BlockingQueue<Runnable> workQueue:阻塞队列。如何异步任务(线程)很多,就会将多的任务放到队列中,如果线程空闲,就会去队列取出新的任务继续执行

6、threadFactory:线程的创建工厂

7、RejectedExecutionHandler handler:如果队列满了,按照指定拒绝策略拒绝执行任务

工作顺序:

 1、创建线程池,初始化化核心线程数,准备接受任务

 2、工作线程数<=核心线程数。正常执行任务

 3、工作线程数>核心线程数。多出的任务放到阻塞队列中,空闲的核心线程就会自己去阻塞队列中获取任务执行

 4、队列满了,工作线程数量<最大线程数量。直接开启新的线程执行

 5、最大线程数量都执行好了,空闲的线程会在keepAliveTime指定的时间后释放maximumPoolSize-corePoolSize线程数量,最终保持到核心线程的大小

 6、工作线程数量>最大线程数量。依然有新任务进来。会执行拒绝策略进行处理

         拒绝策略

                AbortPolicy:丢弃任务并抛出RejectedExecutionException异常

                DiscardOldestPolicy:丢弃任务,但是不抛出异常。如果线程队列已满,则后续提交的任务都会被丢弃,且是静默丢弃。

                DiscardOldestPolicy:丢弃队列最前面的任务,然后重新提交被拒绝的任务。

                CallerRunsPolicy:由调用线程处理该任务(可以选择不抛弃)

                3.7.3、相关API 

JDK 5.0起提供了线程池相关API:ExecutorService 和 Executors
ExecutorService:真正的线程池接口。常见子类ThreadPoolExecutor

  • void execute(Runnable command) :执行任务/命令,没有返回值,一般用来执行Runnable
  • <T> Future<T> submit(Callable<T> task):执行任务,有返回值,一般又来执行Callable
  • void shutdown() :关闭连接池

Executors:工具类、线程池的工厂类,用于创建并返回不同类型的线程池

  • Executors.newCachedThreadPool():可缓存的线程池;线程池大小完全依赖于操作系统能够创建的最大线程大小
  • Executors.newFixedThreadPool(n); 固定数量的线程池;每提交一个任务就是一个线程,  直到达到线程此的最大数量(max=core)
  • Executors.newSingleThreadExecutor() :单线程的线程池;线程池中每次只有一个线程在工作。单线程串行执行任务
  • Executors.newScheduledThreadPool(n):一个大小无限的线程池;支持定时以及周期性执行任务的需求
// 创建线程的方式:使用线程池
public class NumberThread implements Runnable {
    @Override
    public void run() {
        for (int i = 0; i < 100; i++) {
            System.out.println(Thread.currentThread().getName() + ":" + i);
        }
    }
}

// 一个线程池:core 7、max 20、queue 50    100个并发进来如何分配
// 7个会立即执行,50个进入队列,再开13个进行执行。剩下的30个使用拒绝策略执行    

class ThreadPool {
    public static void main(String[] args) {

    // 方式一:
        // 1、提供指定线程数量的线程池(如果不用默认的则使用步骤2进行修改)
        ExecutorService executorService = Executors.newFixedThreadPool(7);
        // 2、设置属性(ExecutorService是个接口,设置属性需要它的实现类ThreadPoolExecutor)
        ThreadPoolExecutor threadPoolExecutor = (ThreadPoolExecutor) executorService;
        threadPoolExecutor.setMaximumPoolSize(20);

    // 方式二:
        ThreadPoolExecutor threadPoolExecutor1 = new ThreadPoolExecutor(
                7,
                20,
                10,
                TimeUnit.SECONDS,
                /*
                    根据情况选择不同的队列,LinkedBlockingQueue默认Integer.MAX_VALUE,线程过多会使的内存被占满
                    这里的大小可以根据压测的最大值进行设置
                 */
                new LinkedBlockingQueue<>(50),
                Executors.defaultThreadFactory(),
                // 根据情况选择不同的拒绝策略
                new ThreadPoolExecutor.AbortPolicy()
        );    

        // 3、执行指定线程的操作。需要提供时实现Runnable接口或Callable接口实现类的对象
        executorService.execute(new NumberThread());// 适合适用于Runnable接口
        //executorService.submit(Callable callable);// 适合适用于Callable接口
        // ....
 
        // 4、关闭连接池
        executorService.shutdown();
    }
}

        3.8、CompletableFuture异步编排

概念: 线程B依赖线程A执行后的返回结果。可以用异步编排实现

  • CompletableFuture 类实现了 Future 接口,所以你还是可以像以前一样通过`get`方法阻塞或者轮询的方式获得结果,但是这种方式不推荐使用。
  • CompletableFuture 和 FutureTask 同属于 Future 接口的实现类,都可以获取线程的执行结果。 

                3.8.1、创建异步对象

创建异步对象

       static CompletableFuture<Void> runAsync(Runnable runnable)

       static CompletableFuture<Void> runAsync(Runnable runnable,Executor executor)

       static <U> CompletableFuture<U> supplyAsync(Supplier<U> supplier)

       static <U> CompletableFuture<U> supplyAsync(Supplier<U> supplier,Executor executor)

注意:a、runXxxx 都是没有返回结果的,supplyXxx 都是可以获取返回结果的 

           b、可以传入自定义的线程池,否则就用默认的线程池;

                3.8.2 、计算完成时回调方法

2、计算完成时回调方法 

        CompletableFuture<T> whenComplete(BiConsumer<? super T, ? super Throwable> action)        

        CompletableFuture<T> whenCompleteAsync(BiConsumer<? super T, ? super Throwable> action)

        CompletableFuture<T> whenCompleteAsync(BiConsumer<? super T, ? super Throwable> action, Executor executor)

        CompletableFuture<T> exceptionally(Function<Throwable, ? extends T> fn)

 注意:a、whenComplete 可以处理正常和异常的计算结果,exceptionally 处理异常情况      

            b、whenComplete 和 whenCompleteAsync 的区别:
                    whenComplete:是执行当前任务的线程执行继续执行 whenComplete 的任务。
                    whenCompleteAsync:是执行把 whenCompleteAsync 这个任务继续提交给线程池来进行执行

            c、 方法不以 Async  结尾,意味着 Action  使用相同的线程执行,而 Async  可能会使用其他线程执行(如果是使用相同的线程池,也可能会被同一个线程选中执行)

public class CompletableTest {
    private static ExecutorService executorService = Executors.newFixedThreadPool(10);

    public static void main(String[] args) throws ExecutionException, InterruptedException {

    // runAsync和supplyAsync使用
        // 1.无返回值,传入自定义的线程池
        CompletableFuture.runAsync(() -> {
            System.out.println(Thread.currentThread().getName());
        }, executorService);

        // 2.有返回值,传入自定义的线程池
        CompletableFuture<Integer> completableFuture = CompletableFuture.supplyAsync(() -> {
            int i = 10 / 5;
            return i;
        }, executorService);
        // 获取返回值
        Integer integer = completableFuture.get();
        System.out.println(integer);

    // whenComplete和exceptionally使用
        CompletableFuture<Integer> completableFuture1 = CompletableFuture.supplyAsync(() -> {
            int i = 10 / 0;
            return i;
        }, executorService).whenComplete((req, exception) -> {
            // 虽然能得到异常信息,但是没法修改返回数据
            System.out.println("异步任务完成:结果是" + req + ";异常是:" + exception);
        }).exceptionally(throwable -> {
            // 可以感知异常,同时返回默认值
            return 10001;
        });
        // 获取返回值
        Integer integer1 = completableFuture1.get();
        System.out.println(integer1);

    // apply使用(方法执行完后的处理)
        CompletableFuture<Integer> completableFuture2 = CompletableFuture.supplyAsync(() -> {
            int i = 10 / 0;
            return i;
        }, executorService).handle((req, thr) -> {
            if (null != req) {
                return 10002;
            }
            if (null != thr) {
                return 10003;
            }
            return 10004;
        });
        // 获取返回值
        Integer integer2 = completableFuture2.get();
        System.out.println(integer2);

        executorService.shutdown();
    }
}

                3.8.3、handle()方法

handle()方法            

        <U> CompletableFuture<U> handle(BiFunction<? super T, Throwable, ? extends U> fn) 

       <U> CompletableFuture<U> handleAsync(BiFunction<? super T, Throwable, ? extends U> fn)

        <U> CompletableFuture<U> handleAsync( BiFunction<? super T, Throwable, ? extends U> fn, Executor executor)

 注意:和 complete 一样,可对结果做最后的处理(可处理异常),可改变返回值。

                3.8.4、线程串行化方法

 注意:

        a、thenApply 方法:当一个线程依赖另一个线程时,获取上一个任务返回的结果,并返回当前任务的返回值。
        b、thenAccept 方法:消费处理结果。接收任务的处理结果,并消费处理,无返回结果

        c、thenRun 方法:只要上面的任务执行完成,就开始执行 thenRun,只是处理完任务后,执行thenRun 的后续操作
        d、带有 Async 默认是异步执行的。同之前。

        e、以上都要前置任务成功完成。Function<? super T,? extends U>
              T:上一个任务返回结果的类型
              U:当前任务的返回值类型 

public class CompletableTest1 {
    private static ExecutorService executorService = Executors.newFixedThreadPool(10);

    public static void main(String[] args) throws ExecutionException, InterruptedException {
        /**
         * 线程串行化
         *  1、thenRunAsync:异步方式、不能获取上一步的执行结果,无返回值
         *  2、thenAcceptAsync:异步方式、能接受上一步返回结果,但是无返回值
         *  3、thenApplyAsync:异步方式、既能接受上一步返回结果,有返回值
         */
        // thenRunAsync使用
        CompletableFuture.supplyAsync(() -> {
            int i = 10 / 5;
            return i;
        }, executorService).thenRunAsync(() -> {
            System.out.println("异步任务1启动");
        }, executorService);

        // thenAcceptAsync使用
        CompletableFuture.supplyAsync(() -> {
            int i = 10 / 5;
            return i;
        }, executorService).thenAcceptAsync(res -> {
            System.out.println("异步任务2启动"+res);
        }, executorService);

        // thenApplyAsync
        CompletableFuture<String> future = CompletableFuture.supplyAsync(() -> {
            int i = 10 / 2;
            return i;
        }, executorService).thenApplyAsync(res -> {
            System.out.println("异步任务3启动" + res);
            return res + "修改值";
        }, executorService);
        System.out.println(future.get());

        executorService.shutdown();
    }
}

                3.8.5、两任务组合 - 都要完成

 注意:两个任务必须都完成,触发该任务。

        a、thenCombine:组合两个 future,获取两个 future 的返回结果,并返回当前任务的返回值
        b、thenAcceptBoth:组合两个 future,获取两个 future 任务的返回结果,然后处理任务,没有返回值。
        c、runAfterBoth:组合两个 future,不需要获取 future 的结果,只需两个 future 处理完任务后,处理该任务。  

                3.8.6、两任务组合 - 一个完成

public class CompletableTest2 {
    private static ExecutorService executorService = Executors.newFixedThreadPool(10);

    public static void main(String[] args) throws ExecutionException, InterruptedException {

    // 两任务组合都要完成
        CompletableFuture<String> future1 = CompletableFuture.supplyAsync(() -> {
            return "hello";
        }, executorService);

        CompletableFuture<String> future2 = CompletableFuture.supplyAsync(() -> {
            return "world";
        }, executorService);

        // runAfterBothAsync使用---不会获取future1和future2的返回结果、无返回值
        future1.runAfterBothAsync(future2,()->{
            System.out.println("future1和future2都执行完毕");
        },executorService);

        // thenAcceptBothAsync---会获取future1和future2的返回结果、无返回值
        future1.thenAcceptBothAsync(future2,(f1,f2)->{
            System.out.println("future1和future2都执行完毕,future1为:"+f1+";future2为:"+f2);
        },executorService);

        // thenAcceptBothAsync---会获取future1和future2的返回结果、有返回值
        CompletableFuture<String> future3 = future1.thenCombineAsync(future2, (f1, f2) -> {
            return f1 + f2;
        }, executorService);
        System.out.println(future3.get());

        executorService.shutdown();
    }
}

 

 

注意:当两个任务中,任意一个 future 任务完成的时候,执行任务。

        a、applyToEither:两个任务有一个执行完成,获取它的返回值,处理任务并有新的返回值。
        b、acceptEither:两个任务有一个执行完成,获取它的返回值,处理任务,没有新的返回值。
        c、runAfterEither:两个任务有一个执行完成,不需要获取 future 的结果,处理任务,也没有返回值。

                3.8.7、多任务组合 

注意:

        a、allOf:等待所有任务完成
        b、anyOf:只要有一个任务完成

 

public class CompletableTest3 {
    private static ExecutorService executorService = Executors.newFixedThreadPool(10);

    public static void main(String[] args) throws ExecutionException, InterruptedException {

        // 多任务组合
        CompletableFuture<String> future1 = CompletableFuture.supplyAsync(() -> {
            System.out.println("任务1执行");
            return "任务1";
        }, executorService);

        CompletableFuture<String> future2 = CompletableFuture.supplyAsync(() -> {
            System.out.println("任务2执行");
            return "任务2";
        }, executorService);

        CompletableFuture<String> future3 = CompletableFuture.supplyAsync(() -> {
            System.out.println("任务3执行");
            return "任务3";
        }, executorService);

        /**
         * 如果按照之前:future1.get();future2.get();future3.get()
         * 需要等待前面的执行完才执行后面的,等待时间过长
         * allOf的方式,因为每个线程是异步执行的,所以当全部完成后再执行其它的时间会相对减少
         */

        // allOf使用---等待任务执行完成
        CompletableFuture<Void> allOf = CompletableFuture.allOf(future1, future2, future3);
        allOf.get();
        System.out.println("全部执行完毕;future1:"+future1.get()+";future2"+future2.get()+";future3"+future3.get());

        // anyOf使用---只要有一个任务完成
        CompletableFuture<Object> anyOf = CompletableFuture.anyOf(future1, future2, future3);
        System.out.println("当前执行完的任务是:"+anyOf.get());
        executorService.shutdown();
    }
}

 

  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值