Java——常见的创建编程的手段(Thread、Callable、Executors)

1、创建线程有哪几种方式?

  • 继承 Thread 类;
  • 实现 Runnable 接口;
  • 实现 Callable 接口;
  • 使用 Executors工具类创建线程池。

1.1、继承Thread

1.1.1、步骤

  1. 定义一个Thread类的子类,重写run方法,将相关逻辑实现,run()方法就是线程要执行的业务逻辑方法;
  2. 创建自定义的线程子类对象;
  3. 调用子类实例的star()方法来启动线程。

1.1.2、代码

public class MyThread extends Thread {
    @Override
    public void run() {
        System.out.println(Thread.currentThread().getName() + " run()方法正在执行...");
    }
}
public class TheadTest {
    public static void main(String[] args) {
        MyThread myThread = new MyThread();
        myThread.start();
        System.out.println(Thread.currentThread().getName() + " main()方法执行结束");
    }
}

1.1.3、执行结果

main main()方法执行结束
Thread-0 run()方法正在执行...

1.2、实现Runnable接口

1.2.1、步骤

  1. 定义Runnable接口实现类MyRunnable,并重写run()方法;
  2. 创建MyRunnable实例myRunnable,以myRunnable作为target创建Thread对象,该Thread对象才是真正的线程对象;
  3. 调用线程对象的start()方法。

1.2.2、代码

public class MyRunnable implements Runnable {
    @Override
    public void run() {
        System.out.println(Thread.currentThread().getName() + " run()方法执行中...");
    }
}
public class RunnableTest {
    public static void main(String[] args) {
        MyRunnable myRunnable = new MyRunnable();
        Thread thread = new Thread(myRunnable);
        thread.start();
        System.out.println(Thread.currentThread().getName() + " main()方法执行完成");
    }
}

1.2.3、执行结果

main main()方法执行完成
Thread-0 run()方法执行中...

1.3、实现 Callable 接口

1.3.1、步骤

  1. 创建实现Callable接口的类myCallable
  2. myCallable为参数创建FutureTask对象
  3. FutureTask作为参数创建Thread对象
  4. 调用线程对象的start()方法

1.3.2、代码

public class MyCallable implements Callable<Integer> {
    @Override
    public Integer call() {
        System.out.println(Thread.currentThread().getName() + " call()方法执行中...");
        return 1;
    }
}

public class CallableTest {
    public static void main(String[] args) {
        FutureTask<Integer> futureTask = new FutureTask<Integer>(new MyCallable());
        Thread thread = new Thread(futureTask);
        thread.start();
        try {
            Thread.sleep(1000);
            System.out.println("返回结果 " + futureTask.get());
        } catch (InterruptedException e) {
            e.printStackTrace();
        } catch (ExecutionException e) {
            e.printStackTrace();
        }
        System.out.println(Thread.currentThread().getName() + " main()方法执行完成");
   }
}

1.3.3、执行结果

Thread-0 call()方法执行中...
返回结果 1
main main()方法执行完成

1.4、使用Executors 工具类创建线程池

答:Executors提供了一系列工厂方法用于创先线程池,返回的线程池都实现了ExecutorService接口。主要有newFixedThreadPoolnewCachedThreadPoolnewSingleThreadExecutornewScheduledThreadPool

1.4.1、代码

public class MyRunnable implements Runnable {
    @Override
    public void run() {
        System.out.println(Thread.currentThread().getName() + " run()方法执行中...");
    }
}
public class SingleThreadExecutorTest {
    public static void main(String[] args) {
        ExecutorService executorService = Executors.newSingleThreadExecutor();
        MyRunnable runnableTest = new MyRunnable();
        for (int i = 0; i < 5; i++) {
            executorService.execute(runnableTest);
        }
        System.out.println("线程任务开始执行");
        executorService.shutdown();
    }
}

1.4.2、执行结果

线程任务开始执行
pool-1-thread-1 is running...
pool-1-thread-1 is running...
pool-1-thread-1 is running...
pool-1-thread-1 is running...
pool-1-thread-1 is running...

2、说一下 runnablecallable 有什么区别?

2.1、相同点

  • 都是接口;
  • 都可以编写多线程程序;
  • 都采用Thread.start()启动线程;

2.2、不同点

  • Runnable 接口 run() 方法无返回值Callable 接口 call 方法有返回值,是个泛型,和Future、FutureTask配合可以用来获取异步执行的结果;
  • Runnable 接口 run() 方法只能抛出运行时异常,且无法捕获处理;Callable 接口 call 方法允许抛出异常,可以获取异常信息;

Callalbe接口支持返回执行结果,需要调用FutureTask.get()得到,此方法会阻塞主进程的继续往下执行,如果不调用不会阻塞。

3、线程的 run()start()有什么区别?

  • 每个线程都是通过某个特定Thread对象所对应的方法run()来完成其操作的,run()方法称为线程体。通过调用Thread类的start()方法来启动一个线程。
  • start()方法用于启动线程,run() 方法用于执行线程的运行时代码。run() 可以重复调用,而start()只能调用一次。
  • start()方法来启动一个线程,真正实现了多线程运行。调用start()方法无需等待run()方法体代码执行完毕,可以直接继续执行其他的代码; 此时线程是处于就绪状态,并没有运行。 然后通过此Thread类调用方法run()来完成其运行状态, run()方法运行结束, 此线程终止。然后**CPU**再调度其它线程
  • run()方法是在本线程里的,只是线程里的一个函数,而不是多线程的。 如果直接调用run(),其实就相当于是调用了一个普通函数而已,直接待用run()方法必须等待run()方法执行完毕才能执行下面的代码,所以执行路径还是只有一条,根本就没有线程的特征,所以在多线程执行时要使用start()方法而不是run()方法。

4、为什么我们调用 start() 方法时会执行run()方法,为什么我们不能直接调用run() 方法?

  • new一个Thread,线程进入了新建状态。调用 start()方法,会启动一个线程并使线程进入了就绪状态,当分配到时间片后就可以开始运行了。start()会执行线程的相应准备工作,然后自动执行 run() 方法的内容,这是真正的多线程工作。
  • 而直接执行 run() 方法,会把run方法当成一个main 线程下的普通方法去执行,并不会在某个线程中执行它,所以这并不是多线程工作。

总结: 调用 start 方法方可启动线程并使线程进入就绪状态,而 run 方法只是 thread 的一个普通方法调用,还是在主线程里执行

5、什么是 CallableFuture?

  • Callable 接口类似于Runnable,从名字就可以看出来了,但是 Runnable 不会返回结果,并且无法抛出返回结果的异常,而 Callable 功能更强大一些,被线程执行后,可以返回值,这个返回值可以被Future 拿到,也就是说,Future 可以拿到异步执行任务的返回值。
  • Future接口表示异步任务,是一个可能还没有完成的异步任务的结果。所以说 Callable用于产生结果,Future 用于获取结果

6、什么是 FutureTask

答:FutureTask 表示一个异步运算的任务FutureTask 里面可以传入一个 Callable 的具体实现类,可以对这个异步运算的任务的结果进行等待获取、判断是否已经完成、取消任务等操作。只有当运算完成的时候结果才能取回,如果运算尚未完成 get 方法将会阻塞。一个 FutureTask对象可以对调用了 CallableRunnable 的对象进行包装,由于 FutureTask 也是Runnable 接口的实现类,所以 FutureTask 也可以放入线程池中。

7、FutureFutureTask的区别

 /**
   Future的使用方法
 **/
 ExecutorService service = Executors.newSingleThreadExecutor();
        Future<String> future = service.submit(new Callable<String>() {
            @Override
            public String call() throws Exception {
                return "say helloWorld!!!";
            }
 });
 System.out.println(future.get());// 通过get返回结果
 
 /**
   FutureTask
 **/
ExecutorService service = Executors.newSingleThreadExecutor();
FutureTask<String> futureTask = new FutureTask<>(new Callable<String>() {
     @Override
     public String call() throws Exception {
         return "futureTask say HelloWorld!!!";
     }
});
service.execute(futureTask);
System.out.println(futureTask.get());

7、常见的线程池及使用场景

  • newSingleThreadExecutor 创建一个单线程化的线程池,它只会用唯一的工作线程来执行任务,保证所有任务按照指定顺序(FIFO LIFO,优先级)执行。
  • newFixedThreadPool 创建一个定长线程池,可控制线程最大并发数,超出的线程会在队列中等待。
  • newCachedThreadPool 创建一个可缓存线程池,如果线程池长度超过处理需要,可灵活回收空闲线程,若无可回收,则新建线程。
  • newScheduledThreadPool 创建一个周期性线程池,支持定时及周期性任务执行。

8、线程池的核心参数

  • corePoolSize 表示当前线程池的核心线程数大小,即最小线程数(初始化线程数),线程池会维护当前数据的线程在线程池中,即使这些线程一直处于闲置状态,也不会被销毁;
  • maximumPoolSize 表示线程池中允许的最大线程数;后文中会详细讲解
  • keepAliveTime 表示空闲线程的存活时间,当线程池中的线程数量大于核心线程数且线程处于空闲状态,那么在指定时间后,这个空闲线程将会被销毁,从而逐渐恢复到稳定的核心线程数数量;
  • unit 当前unit表示的是keepAliveTime存活时间的计量单位,通常使用TimeUnit.SECONDS秒级;
  • workQueue 任务工作队列;后文会结合maximumPoolSize一块来讲
  • threadFactory 线程工厂,用于创建新线程以及为线程起名字等
  • handler 拒绝策略,即当任务过多无法及时处理时所需采取的策略;
    • AbortPolicy:丢弃任务并抛出 RejectedExecutionException 异常。(默认这种)
    • DiscardPolicy:也是丢弃任务,但是不抛出异常
    • DiscardOldestPolicy:丢弃队列最前面的任务,然后重新尝试执行任务(重复此过程)
    • CallerRunsPolicy:调用线程处理该任务,即创建了线程池的线程来执行被拒绝的任务

9、初始化线程池时线程数的选择

  • IO 密集型: 一般线程数需要设置2倍CPU数以上,以此来尽量利用CPU资源。
  • CPU 密集型: 一般线程数量只需要设置CPU数加1即可,更多的线程数也只能增加上下文切换,不能增加CPU利用率。

10、线程池都有哪几种工作队列

  • ArrayBlockingQueue 是一个基于数组结构的有界阻塞队列,此队列按 FIFO(先进先出)原则对元素进行排序。
  • LinkedBlockingQueue 一个基于链表结构的阻塞队列,此队列按FIFO(先进先出) 排序元素,吞吐量通常要高于ArrayBlockingQueue。静态工厂方法Executors.newFixedThreadPool()使用了这个队列
  • SynchronousQueue 一个不存储元素的阻塞队列。每个插入操作必须等到另一个线程调用移除操作,否则插入操作一直处于阻塞状态,吞吐量通常要高于LinkedBlockingQueue,静态工厂方法Executors.newCachedThreadPool使用了这个队列。
  • PriorityBlockingQueue 一个具有优先级的无限阻塞队列。

你可能感兴趣的:(Java,java,开发语言)