1、 线程的生命周期

一个线程的生命周期有6个状态,其对应的Thread的内部类State。相关状态转换如下:

  • NEW:Thread还未start时
  • RUNNABLE:Thread已经start,此时该线程处于系统得线程调度器中;
  • BLOCKED:线程因为未获取锁而处于阻塞状态;
  • WAITING:线程处于等待状态,等待其他线程唤醒它;
  • TIMED_WAITING:因为sleep而处于线程等待状态;
  • TERMINATED:中断状态,此时线程已经执行完毕;

2、线程对象Thread

1、介绍

java中,Thread是唯一代表线程的类。其他RunnableCallable都不是线程。每个Thread类的实例只要start()后,还没结束,就代表JVM中的一个线程。

2、多线程的难理解

java中,每次执行Thread.start()方法后,JVM就会增加一个线程。即:

  • 一个代码执行流;
  • 一套方法栈;

而不同执行流的同步执行是一切线程问题的根源。

(1)只有Thread才代表一个新线程

    public static void main(String[] args) {
        new Thread(() -> {
            while (true){
                System.out.println("这是一个新线程");
            }
        }).start();

        while (true){
            System.out.println("这是主线程");
        }
    }

这里很容易明确哪段代码在哪个线程执行

(2)线程池的缺点

public class Demo {
    static ExecutorService threadPool = Executors.newFixedThreadPool(5);

    public static void main(String[] args) {
        threadPool.submit(() -> {
            doSomething();// 这段代码被哪个线程执行了???
        });

        doSomething();
    }

    private static void doSomething() {
        System.out.println("做了一点事");
    }
}

是不是会觉得那段代码会被新线程执行??其实那段可能会被新线程执行,也有可能还是被主线程执行。

这是因为Thread.start()和线程池提交一个任务后,这个任务具体会被哪个线程执行是由线程调度器来决定的。默认情况下,如果这个线程池中没有空闲线程的话,那么那段代码还是会被发起的线程来执行的。

(3)多个线程的同时执行 -- 线程安全问题

public class Demo {
    static int i = 0;

    public static void main(String[] args) {
        new Thread(() -> {
            doSomething();
        }).start();

        doSomething();
    }

    private static void doSomething() {// 俩个执行流|人同时在执行这段代码
        i++;
    }
}

这也是多线程的可怕地方,程序执行顺序不确定了。

(4)异常处理的反直觉性

public class Demo {
    static int i = 0;

    public static void main(String[] args) {
        try {
            new Thread(() -> {
                throw new RuntimeException();
            }).start();

            doSomething();
        } catch (Exception e) {
            // 这里只能捕捉到当前线程跑出来的异常;上面那个新线程的异常是无法抛到这里的
        }
    }

    private static void doSomething() {
        i++;
    }
}

在子线程发生的异常最多只能抛到当前线程的方法栈中。子线程的异常是没法跨线程抛的,也就是说父线程是无法获取子线程的异常。

3、Runnable、Callable

jdk1.0的时候,Thread被设计成线程,Runnable设计成可以被线程执行的任务。但是Runnable有俩个限制:

  • 不能直接返回值
  • 不能抛出异常

这也是在jdk1.5时候引入Callable的原因,它就解决了这俩个问题。

1、Runnable的俩个限制

Runnable之所以有这俩个限制,完全是其抽象方法限制的。

public interface Runnable {
    public abstract void run();
}

它本身没有返回值,也不抛出异常。这就导致后续重写这个方法时,也不能返回值,也不能抛出异常。

2、在使用Runnable时,如何能获取到线程结果的返回值呢?

设置一个共享Map变量,每个线程对应其中的元素。用线程id作为key,当线程结束后,在这个Map对应的key中设置value。以此来达到获取线程结果返回值的目的。伪代码如下:

        List<Runnable> tasks = new ArrayList<>();
        Map<Long, Object> result = new ConcurrentHashMap<>();

        for (Runnable task : tasks) {
            new Thread(() -> {// 这里指的是每个task的run方法
                // 执行任务
                //...
                //...
                result.put(Thread.currentThread().getId(), "result")
            }).start();
        }

results matching ""

    No results matching ""