目录

Java 多线程(一) 基本多线程用法

Thread

基本用法:

1
2
Thread thread = new Thread(runnable);
thread.start();

其中 runable 是一个 Runnable 对象, 只需要用以下方式创建并在其中填入方法即可:

1
2
3
4
5
6
Runnable runnable = new Runnable() {
    @Override
    public void run() {
		// do something...
    }
};

在 Java8, 事实上我们可以使用 lambda 语法

1
2
3
static Runnable runnable = () -> {
	// do something...
};

结合一下:

1
2
3
new Thread(() -> {
    // do something...
}).start();

相关方法如下, 下面 run 和 join 看起来一样, 实际上:

run 就是你放下手头事情去修手机,修完继续工作, start 之后 join, 就相当于你把手机给别人去修, 然后等到什么时候工作需要手机了, 你执行 join 方法, 等到手机修好或者手机已经修好了, 继续工作.

1
2
3
4
5
thread.run(); // 这玩意没用, 相当于没切线程
thread.join(); // 阻塞当前线程, 等待 runnable 执行完毕
thread.start(); // 启动 Thread
thread.stop(); // 无视 runnable 状态, 立即停止 Thread
thread.interrupt(); // 给 Thread 标记: 等死吧, 然后留给 runnable 自行中断

另外还有个方法: Thread.sleep(), 字面意思, 阻塞当前线程, 可以填入阻塞多少毫秒, 然后没了

其余方法就相对简单或者没必要了解了(作为 Android 工程师的话), 跳过. 事实上做 Android 估计是很少用到Thread 这种底层的 api 了, 我更多会使用协程或者 Executor, 也可也适当考虑 Handler.

Executor

Executor 是 Java 的一个线程工具, 一个接口, 其中只有一个方法 execute, 绝大多数情况下我们如果要使用, 需要使用其实现类 ExecutorService

1
2
3
public interface Executor {
    void execute(Runnable command);
}

那它怎么用呢? 首先我们需要创建一个 Executor, 然后传入 runnable 就行了

1
2
3
ExecutorService executor = Executors.newCachedThreadPool();
executor.execute(runnable0);
executor.execute(runnable1); // 可以同时插入多个Runnable 对象

其实你也可以自己去实现 Executor 接口, 但是使用又不是很方便, 直接从 Executors 里面取它不香吗? 具体详见 Executor 接口上面的注释

不过需要注意的是使用上面方法创建出来的 Executor 并不会立即停止, 以方便之后进来的线程重用部分对象, 避免内存抖动. 不过其实这只是个入口方法罢了, 点开内部我们可以看到:

1
2
3
4
5
public static ExecutorService newCachedThreadPool() {
    return new ThreadPoolExecutor(0, Integer.MAX_VALUE, // 最小线程池容量, 最大线程池容量
                                  60L, TimeUnit.SECONDS, // 闲置多长时间回收一个线程
                                  new SynchronousQueue<Runnable>());
}

Executors 里面还有很多其他方法, 就不一一例举了, 必要性不大, 举几个:

1
2
Executors.newSingleThreadExecutor(); // 线程池容量 1, 永不自动关闭线程
Executors.newFixedThreadPool(int); // 指定线程池容量, 永不自动关闭线程

那么问题来了, 我们创建的线程如果永不自动关闭, 那应该怎么办呢? 其实有如下 api

1
2
executor.shutdown(); // 发出关闭线程指令催线程池全部线程结束, 并且不再接受新线程加入线程池
executor.shutdownNow(); // 立即关闭线程池

Handler

  • 干啥的: 在指定的运行中的线程执行任务

  • Looper: 就是个死循环

    • prepare(): 通过 ThreadLocal 创建一个 Looper

    • loop(): 取出 Looper 对象中的MessageQueue进行消息循环

      Message 本身其实是个链表, 有 what / arg1 / arg2 / obj / callback / next等

  • 分发消息: 先看 Message 有没有 callback 其次调用 handleMessage 最后查看子类是否 override handleMessage

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
//分发消息
public void dispatchMessage(Message msg) {
    if (msg.callback != null) {
 		// 当 Message 存在回调方法,回调方法 msg.callback.run()
        handleCallback(msg);
    } else {
        // 当 Handler 存在 Callback 成员变量时,回调方法 mCallback.handleMessage();
        if (mCallback != null) {
            if (mCallback.handleMessage(msg)) {
                return;
            }
        }
        // Handler 子类通过覆写该方法来完成具体的逻辑
        handleMessage(msg);
    }
}

有个坑: Handler 容易内存泄露, 但事实上所有的内部类都会这样, 此时我们应该选择持有外部类的弱引用并使用 WeakReference<>.get() 来调用并判断是否为 null