创建多线程的四种方式 看完不懂,你来捶我!!!

线程(thread)是一个程序内部的一条执行路径。我们之前启动程序执行后,main方法的执行其实就是一条单独的执行路径,程序中如果只有一条执行路径,那么这个程序就是单线程的程序。
多线程是指从软硬件上实现多条执行流程的技术。

一、继承Thread类

实现步骤:
1、定义一个子类MyThread继承线程类java.lang.Thread,重写run()方法
2、创建MyThread类的对象(new一个新线程对象)
3、调用线程对象的start()方法启动线程(启动后还是执行run方法的)

优点:编码简单
缺点:线程类已经继承Thread,无法继承其他类,不利于扩展

public class ThreadDemo1 {
    public static void main(String[] args) {
        // 2、new一个新线程对象
        Thread t = new MyThread();
        // 3、调用start方法启动子线程(执行的还是run方法)
        t.start();
        
        //主线程代码
        for (int i = 0; i < 5; i++) {
            System.out.println("主线程执行输出:" + i);
        }

    }
}

/**
 1、定义一个线程类继承Thread类
 */
class MyThread extends Thread{
    /**
     重写run方法,里面是定义线程以后要干啥
     */
    @Override
    public void run() {
        for (int i = 0; i < 5; i++) {
            System.out.println("子线程执行输出:" + i);
        }
    }
}

注意:
1、重写的是run()方法,通过start()方法启动线程,但是占用了继承的名额,Java中的类是单继承的。
2、直接调用run方法会当成普通方法执行,此时相当于还是单线程执行。
只有调用start方法才是启动一个新的线程执行。
3、把主线程任务放在子线程之前,这样主线程一直是先跑完的,相当于是一个单线程的效果了

二、实现Runnable接口

实现步骤:
1、定义一个线程任务类MyRunnable实现Runnable接口,重写run()方法
2、创建MyRunnable任务对象
3、把MyRunnable任务对象交给Thread处理。
4、调用线程对象的start()方法启动线程

Thread的构造器:
public Thread(String name) 可以为当前线程指定名称
public Thread(Runnable target) 封装Runnable对象成为线程对象
public Thread(Runnable target ,String name ) 封装Runnable对象成为线程对象,并指定线程名称

优点:线程任务类只是实现接口,可以继续继承类和实现接口,扩展性强
缺点:编程多一层对象包装,如果线程有执行结果是不可以直接返回的

public class ThreadDemo2 {
    public static void main(String[] args) {
        // 3、创建一个任务对象
        Runnable target = new MyRunnable();
        // 4、把任务对象交给Thread处理
        Thread t = new Thread(target);
        // Thread t = new Thread(target, "1号");  可以给线程命名
        // 5、启动线程
        t.start();

        for (int i = 0; i < 10; i++) {
            System.out.println("主线程执行输出:" + i);
        }
    }
}

     /**
     1、定义一个线程任务类 实现Runnable接口
     */
    class MyRunnable  implements Runnable {
     /**
     2、重写run方法,定义线程的执行任务的
     */
      @Override
      public void run() {
          for (int i = 0; i < 10; i++) {
              System.out.println("子线程执行输出:" + i);
          }
      }
     }

实现Runnable接口,重写run()方法,使用依然要用到Thread,这种方式更常用。

可以使用匿名内部类来实现:


/**
线程的创建方式二(匿名内部类方式实现,语法形式)
 */
public class ThreadDemo2Other {
    public static void main(String[] args) {
        Runnable target = new Runnable() {
            @Override
            public void run() {
                for (int i = 0; i < 10; i++) {
                    System.out.println("子线程1执行输出:" + i);
                }
            }
        };
        Thread t = new Thread(target);
        t.start();

        new Thread(new Runnable() {
            @Override
            public void run() {
                for (int i = 0; i < 10; i++) {
                    System.out.println("子线程2执行输出:" + i);
                }
            }
        }).start();

        new Thread(() -> {
            for (int i = 0; i < 10; i++) {
                System.out.println("子线程3执行输出:" + i);
            }
        }).start();

        for (int i = 0; i < 10; i++) {
            System.out.println("主线程执行输出:" + i);
        }
    }
}

三、实现Callable接口

前2种线程创建方式都存在一个问题:
1、他们重写的run方法均不能直接返回结果
2、不适合需要返回线程执行结果的业务场景

JDK 5.0提供了Callable和FutureTask来实现,这种方式的优点是:可以得到线程执行的结果

执行步骤:
1、得到任务对象
定义类实现Callable接口,重写call方法,封装要做的事情
用FutureTask把Callable对象封装成线程任务对象
2、把线程任务对象交给Thread处理
3、调用Thread的start方法启动线程,执行任务
4、线程执行完毕后、通过FutureTask的get方法去获取任务执行的结果

优点:线程任务类只是实现接口,可以继续继承类和实现接口,扩展性强。
可以在线程执行完毕后去获取线程执行的结果
缺点:编码复杂一点

public class ThreadDemo3 {
    public static void main(String[] args) {
        // 3、创建Callable任务对象
        Callable<String> call = new MyCallable(100);
        // 4、把Callable任务对象 交给 FutureTask 对象
        //  FutureTask对象的作用1: 是Runnable的对象(实现了Runnable接口),可以交给Thread了
        //  FutureTask对象的作用2: 可以在线程执行完毕之后通过调用其get方法得到线程执行完成的结果
        FutureTask<String> f1 = new FutureTask<>(call);
        // 5、交给线程处理
        Thread t1 = new Thread(f1);
        // 6、启动线程
        t1.start();


        Callable<String> call2 = new MyCallable(200);
        FutureTask<String> f2 = new FutureTask<>(call2);
        Thread t2 = new Thread(f2);
        t2.start();

        try {
            // 如果f1任务没有执行完毕,这里的代码会等待,直到线程1跑完才提取结果。
            String rs1 = f1.get();
            System.out.println("第一个结果:" + rs1);
        } catch (Exception e) {
            e.printStackTrace();
        }

        try {
            // 如果f2任务没有执行完毕,这里的代码会等待,直到线程2跑完才提取结果。
            String rs2 = f2.get();
            System.out.println("第二个结果:" + rs2);
        } catch (Exception e) {
            e.printStackTrace();
        }
    }
}

/**
 1、定义一个任务类 实现Callable接口  应该申明线程任务执行完毕后的结果的数据类型
 */
class MyCallable implements Callable<String>{
    private int n;
    public MyCallable(int n) {
        this.n = n;
    }

    /**
     2、重写call方法(任务方法)
     */
    @Override
    public String call() throws Exception {
        int sum = 0;
        for (int i = 1; i <= n ; i++) {
            sum += i;
        }
        return "子线程执行的结果是:" + sum;
    }
}

#总结前三种方式

方式 优点 缺点
继承Thread类 编程比较简单,可以直接使用Thread类中的方法 扩展性较差,不能再继承其他的类,不能返回线程执行的结果
实现Runnable接口 扩展性强,实现该接口的同时还可以继承其他的类 编程相对复杂,不能返回线程执行的结果
实现Callable接口 扩展性强,实现该接口的同时还可以继承其他的类。可以得到线程执行的结果 编程相对复杂

四、利用线程池来创建线程

线程池就是一个可以复用线程的技术。

如果用户每发起一个请求,后台就创建一个新线程来处理,下次新任务来了又要创建新线程,而创建新线程的开销是很大的,这样会严重影响系统的性能。
因此线程池的线程复用技术就可以一定程度上解决过多线程占用过多内存的情况。

1、线程池的工作原理:

线程池内部会有一些核心线程(固定线程、不死亡)负责处理任务,任务队列有新任务加入时,会先处理完前面的任务,再去处理后面加入的任务。这样,可以实现利用较少线程处理较多任务,避免出现过多线程导致资源耗尽的风险。
创建多线程的四种方式 看完不懂,你来捶我!!!_第1张图片

2、线程池实现的API、参数

JDK 5.0起提供了代表线程池的接口:ExecutorService

得到线程池对象的方式:

方式一:使用ExecutorService的实现类ThreadPoolExecutor自创建一个线程池对象

方式二:使用Executors(线程池的工具类)调用方法返回不同特点的线程池对象
创建多线程的四种方式 看完不懂,你来捶我!!!_第2张图片

ThreadPoolExecutor构造器的参数说明:

int corePoolSize 指定线程池的线程数量(核心线程),不能小于0

int maximumPoolSize 指定线程池可支持的最大线程数,最大数量 >= 核心线程数量

long keepAliveTime 指定临时线程的最大存活时间,不能小于0

TimeUnit unit 指定存活时间的单位(秒、分、时、天)

BlockingQueue workQueue 指定任务队列,不能为null

ThreadFactory threadFactory 指定用哪个线程工厂创建线程,一般使用默认的,不能为null

RejectedExecutionHandler handler 指定线程忙,任务满的时候,新任务来了怎么办,不能为null

临时线程创建的条件:

新任务提交时发现核心线程都在忙,任务队列也满了,并且还可以创建临时线程,此时才会创建临时线程

执行拒绝任务的条件:

核心线程和临时线程都在忙,任务队列也满了,新的任务过来的时候才会开始任务拒绝

ExecutorService的常用方法:

void execute(Runnable command) 执行任务/命令,没有返回值,一般用来执行 Runnable 任务

Future submit(Callable task) 执行任务,返回未来任务对象获取线程结果,一般拿来执行 Callable 任务

void shutdown() 等任务执行完毕后关闭线程池

List shutdownNow() 立刻关闭线程池,停止正在执行的任务,并返回队列中未执行的任务

3、使用线程池(使用ExecutorService为例)

使用Executors工具类创建线程池

Executors得到线程池对象的常用方法:
ExecutorService newCachedThreadPool() 线程数量随着任务增加而增加,如果线程任务执行完毕且空闲了一段时间则会被回收掉

ExecutorService newFixedThreadPool​(int nThreads) 创建固定线程数量的线程池,如果某个线程因为执行异常而结束,那么线程池会补充一个新线程替代它

ExecutorService newSingleThreadExecutor () 创建只有一个线程的线程池对象,如果该线程出现异常而结束,那么线程池会补充一个新线程

ScheduledExecutorService newScheduledThreadPool​(int corePoolSize) 创建一个线程池,可以实现在给定的延迟后运行任务,或者定期执行任务

大型并发系统环境中使用Executors如果不注意可能会出现系统风险。创建多线程的四种方式 看完不懂,你来捶我!!!_第3张图片
所以,一般使用ExecutorService创建线程池,各个参数可控。

使用ExecutorService创建线程池

public class ThreadPoolDemo1 {
    public static void main(String[] args) {
        /**
         public ThreadPoolExecutor(int corePoolSize,
         int maximumPoolSize,
         long keepAliveTime,
         TimeUnit unit,
         BlockingQueue workQueue,
         ThreadFactory threadFactory,
         RejectedExecutionHandler handler)
         */
        ExecutorService pool = new ThreadPoolExecutor(3, 5 ,
                6, TimeUnit.SECONDS, new ArrayBlockingQueue<>(5) , Executors.defaultThreadFactory(),
                new ThreadPoolExecutor.AbortPolicy() );
   }
 }

线程池处理Runnable类型任务

创建Runnable类型任务,使得每个线程处理完任务后进行休眠

public class MyRunnable implements Runnable{
    @Override
    public void run() {
        for (int i = 0; i < 5; i++) {
            System.out.println(Thread.currentThread().getName() + "输出了:HelloWorld" + i);
        }
        try {
            System.out.println(Thread.currentThread().getName() + "本任务与线程绑定了,线程进入休眠了~~~");
            Thread.sleep(10000000);
        } catch (Exception e) {
            e.printStackTrace();
        }
    }
}

把任务交给线程池处理

public class ThreadPoolDemo1 {
    public static void main(String[] args) {
        /**
         public ThreadPoolExecutor(int corePoolSize,
         int maximumPoolSize,
         long keepAliveTime,
         TimeUnit unit,
         BlockingQueue workQueue,
         ThreadFactory threadFactory,
         RejectedExecutionHandler handler)
         */
        ExecutorService pool = new ThreadPoolExecutor(3, 5 ,
                6, TimeUnit.SECONDS, new ArrayBlockingQueue<>(5) , Executors.defaultThreadFactory(),
                new ThreadPoolExecutor.AbortPolicy() );
                
        Runnable target = new MyRunnable();
        pool.execute(target);
        pool.execute(target);
        pool.execute(target);    //此时3个核心线程处理完3个任务后已休眠

        pool.execute(target);
        pool.execute(target);
        pool.execute(target);
        pool.execute(target);
        pool.execute(target);    //此时任务队列满了

        // 创建临时线程
        pool.execute(target);
        pool.execute(target);    //此时线程池有5个线程,达到最大线程数量

//        pool.execute(target);   // 不创建,拒绝策略被触发!!!

        // pool.shutdownNow(); // 立即关闭,即使任务没有完成,会丢失任务的!
        pool.shutdown(); // 会等待全部任务执行完毕之后再关闭(建议使用的)
    }
}

线程池处理Callable类型任务

创建Callable类型任务

/**
 定义一个任务类 实现Callable接口  应该申明线程任务执行完毕后的结果的数据类型
 */
public class MyCallable implements Callable<String> {
    private int n;
    public MyCallable(int n) {
        this.n = n;
    }

    /**
     重写call方法(任务方法)
     */
    @Override
    public String call() throws Exception {
        int sum = 0;
        for (int i = 1; i <= n ; i++) {
            sum += i;
        }
        return Thread.currentThread().getName()
                + "执行 1-" + n+ "的和,结果是:" + sum;
    }
}

把任务交给线程池处理

public class ThreadPoolDemo2 {
    public static void main(String[] args) throws Exception {
        // 1、创建线程池对象
        /**
         public ThreadPoolExecutor(int corePoolSize,
         int maximumPoolSize,
         long keepAliveTime,
         TimeUnit unit,
         BlockingQueue workQueue,
         ThreadFactory threadFactory,
         RejectedExecutionHandler handler)
         */
        ExecutorService pool = new ThreadPoolExecutor(3, 5 ,
                6, TimeUnit.SECONDS, new ArrayBlockingQueue<>(5) , Executors.defaultThreadFactory(),
                new ThreadPoolExecutor.AbortPolicy() );

        // 2、给Callable任务线程池处理。
        Future<String> f1 = pool.submit(new MyCallable(100));
        Future<String> f2 = pool.submit(new MyCallable(200));
        Future<String> f3 = pool.submit(new MyCallable(300));
        Future<String> f4 = pool.submit(new MyCallable(400));
        Future<String> f5 = pool.submit(new MyCallable(500));

        System.out.println(f1.get());
        System.out.println(f2.get());
        System.out.println(f3.get());
        System.out.println(f4.get());
        System.out.println(f5.get());

        pool.shutdown(); // 会等待全部任务执行完毕之后再关闭线程池
    }
}

你可能感兴趣的:(java知识点总结,java,开发语言)