基础线程机制

1、Executor

Executor 管理多个异步任务的执行,而无需程序员显式地管理线程的生命周期。这里的异步是指多个任务的执行互不干扰,不需要进行同步操作。

主要有三种 Executor:
* FixedThreadPool:所有任务只能使用固定大小的线程;
* CachedThreadPool:一个任务创建一个线程;
* SingleThreadExecutor:相当于大小为 1 的 FixedThreadPool。

1、FixedThreadPool创建一个可重用固定线程数的线程池,以共享的无界队列方式来运行这些线程:

ExecutorService executorService = Executors.newFixedThreadPool(5);
for (int i = 0; i < 20; i++) {
    Runnable syncRunnable = new Runnable() {
        @Override
        public void run() {
            Log.e(TAG, Thread.currentThread().getName());
        }
    };
    executorService.execute(syncRunnable);
}

运行结果:总共只会创建5个线程, 开始执行五个线程,当五个线程都处于活动状态,再次提交的任务都会加入队列等到其他线程运行结束,当线程处于空闲状态时会被下一个任务复用

2、CachedThreadPool创建一个可缓存线程池,如果线程池长度超过处理需要,可灵活回收空闲线程

ExecutorService executorService = Executors.newCachedThreadPool();
for (int i = 0; i < 100; i++) {
    Runnable syncRunnable = new Runnable() {
        @Override
        public void run() {
            Log.e(TAG, Thread.currentThread().getName());
        }
    };
    executorService.execute(syncRunnable);
}

运行结果:可以看出缓存线程池大小是不定值,可以需要创建不同数量的线程,在使用缓存型池时,先查看池中有没有以前创建的线程,如果有,就复用.如果没有,就新建新的线程加入池中,缓存型池子通常用于执行一些生存期很短的异步型任务

3、SingleThreadExecutor创建一个单线程化的线程池,它只会用唯一的工作线程来执行任务,保证所有任务按照指定顺序(FIFO, LIFO, 优先级)执行

ExecutorService executorService = Executors.newSingleThreadExecutor();
for (int i = 0; i < 20; i++) {
    Runnable syncRunnable = new Runnable() {
        @Override
        public void run() {
            Log.e(TAG, Thread.currentThread().getName());
        }
    };
    executorService.execute(syncRunnable);
}

运行结果:只会创建一个线程,当上一个执行完之后才会执行第二个

为什么引入Executor线程池框架?
new Thread() 的缺点
* 每次 new Thread() 耗费性能
* 调用 new Thread() 创建的线程缺乏管理,被称为野线程,而且可以无限制创建,之间相互竞争,会导致过多占用系统资源导致系统瘫痪。
* 不利于扩展,比如如定时执行、定期执行、线程中断

采用线程池的优点
* 重用存在的线程,减少对象创建、消亡的开销,性能佳
* 可有效控制最大并发线程数,提高系统资源的使用率,同时避免过多资源竞争,避免堵塞
* 提供定时执行、定期执行、单线程、并发数控制等功能

Executor 的中断操作
调用 Executor 的 shutdown() 方法会等待线程都执行完毕之后再关闭,但是如果调用的是 shutdownNow() 方法,则相当于调用每个线程的 interrupt() 方法。
以下使用 Lambda 创建线程,相当于创建了一个匿名内部线程。

public static void main(String[] args) {
    ExecutorService executorService = Executors.newCachedThreadPool();
    executorService.execute(() -> {
        try {
            Thread.sleep(2000);
            System.out.println("Thread run");
        } catch (InterruptedException e) {
            e.printStackTrace();
        }
    });
    executorService.shutdownNow();
    System.out.println("Main run");
}
Main run
java.lang.InterruptedException: sleep interrupted
    at java.lang.Thread.sleep(Native Method)
    at ExecutorInterruptExample.lambda$main$0(ExecutorInterruptExample.java:9)
    at ExecutorInterruptExample$$Lambda$1/1160460865.run(Unknown Source)
    at java.util.concurrent.ThreadPoolExecutor.runWorker(ThreadPoolExecutor.java:1142)
    at java.util.concurrent.ThreadPoolExecutor$Worker.run(ThreadPoolExecutor.java:617)
    at java.lang.Thread.run(Thread.java:745)

如果只想中断 Executor 中的一个线程,可以通过使用 submit() 方法来提交一个线程,它会返回一个 Future<?> 对象,通过调用该对象的 cancel(true) 方法就可以中断线程。

Future<?> future = executorService.submit(() -> {
    // ..
});
future.cancel(true);

2、Daemon(守护线程)

Java 中有两类线程:User Thread (用户线程)、Daemon Thread (守护线程)
* 用户线程即运行在前台的线程,而守护线程是运行在后台的线程。 守护线程作用是为其他前台线程的运行提供便利服务,而且仅在普通、非守护线程仍然运行时才需要,比如垃圾回收线程就是一个守护线程。当 JVM 检测仅剩一个守护线程,而用户线程都已经退出运行时,JVM 就会退出,因为没有如果没有了被守护者,也就没有继续运行程序的必要了。如果有非守护线程仍然存活,JVM 就不会退出。
* 守护线程并非只有虚拟机内部提供,用户在编写程序时也可以自己设置守护线程。用户可以用 Thread 的 setDaemon(true) 方法设置当前线程为守护线程。
* 虽然守护线程可能非常有用,但必须小心确保其他所有非守护线程消亡时,不会由于它的终止而产生任何危害。因为你不可能知道在所有的用户线程退出运行前,守护线程是否已经完成了预期的服务任务。一旦所有的用户线程退出了,虚拟机也就退出运行了。 因此,不要在守护线程中执行业务逻辑操作(比如对数据的读写等)。

另外有几点需要注意:
* setDaemon(true) 必须在调用线程的 start() 方法之前设置,否则会抛出 IllegalThreadStateException 异常。
* 在守护线程中产生的新线程也是守护线程。
* 不要认为所有的应用都可以分配给守护线程来进行服务,比如读写操作或者计算逻辑。

特点:
* 守护线程是程序运行时在后台提供服务的线程,不属于程序中不可或缺的部分。
* 当所有非守护线程结束时,程序也就终止,同时会杀死所有守护线程。
* main() 属于非守护线程。

使用 setDaemon() 方法将一个线程设置为守护线程。

public static void main(String[] args) {
    Thread thread = new Thread(new MyRunnable());
    thread.setDaemon(true);
}
转载声明:写作不易,商业转载请联系作者获得授权,非商业转载请注明出处,并附上原文链接,感谢!